车联网场景下的数据建模示例

本文档为“时序数据建模”章节的第三篇。YMatrix 认为,数据模型的设计会直接影响到数据消费与使用的价值。因此,除技术介绍外,我们尝试通过整个章节使你对时序数据模型(Time-series Data Model)的概念、应用及发展都有清晰的理解。

  • 首篇为“时序数据模型是什么?”,通过回答几个层层深入的问题,最终使你对时序数据模型概念本身有清晰的理解。
  • 第二篇“时序建模思路”将尝试从理论指导的角度给出 YMatrix 关系模型设计思路参考。
  • 第三、四篇为车联网场景(即此文档)及智能家居场景下的数据建模示例,以“时序建模思路”为指导,给出 YMatrix 中不同时序场景的建模最佳实践。

注意!
此篇仅为参考示例,正式设计建模前建议至少完整详读 YMatrix 架构及组件原理。组件原理见“参考指南”章节。

1 什么是车联网?

车联网是物联网(IoT,Internet of Things)的典型应用场景之一。 据中国香港应用科技研究院所述,车联网技术是指车辆与车辆、车辆与行人、车辆与道路基建以及车辆与云端之间的低时延通讯系统。通过车联网系统中的实时信息传递,实现人、车与道路基建互相协调配合,即时对道路使用者进行路况汇报与警示,藉以加强道路安全和辅助驾驶。同时,车联网技术可应用在实时交通监察、事故管理,和行车路线规划等方面以提高交通效率。长远而言,车联网能配合自动驾驶技术的发展,协助自动驾驶判断隐藏风险,提升道路安全。

车联网的主要分类如下表:

通信端 技术实现 应用场景
车辆与车辆 车辆与车辆之间实现信息交流与信息共享,包括车辆位置、行驶速度等车辆状态信息 判断道路车流状况
车辆与行人 用户可以通过Wi-Fi、蓝牙、蜂窝等无线通信手段与车辆进行信息沟通 使用户能通过对应的移动终端设备监测并控制车辆
车辆与道路基建 借助地面道路固定通信设施实现车辆与道路间的信息交流 监测道路路面状况,引导车辆选择最佳行驶路径
车辆与云端 车辆通过卫星无线通信或移动蜂窝等无线通信技术实现与车联网服务平台的信息传输 车辆接受平台下达的控制指令;云端实时共享车辆数据
车内设备之间 车辆内部各设备间的信息数据传输 对车内设备状态进行实时检测与运行控制,建立数字化的车内控制系统

2 车联网建模最佳实践

可以设想一下,假如你是一个车端应用研发人员,负责管理、维护你就职企业的车辆指标平台 A。现在,你想要就此平台的业务在 YMatrix 中进行建模设计,可能的思路如下:

序号 步骤
1 需求调研
2 建模设计与实施
3 模型测试

2.1 需求调研

  • 车辆指标平台 A 的数据特征:

    • 数据规模巨大:PB 级别。
    • 设备数量大:可能会出现上千台设备同时采集传输数据的情况。
    • 车端指标:比较固定但也需要有动态扩展指标的空间。
    • 指标值类型:
      1. int 类指标:以整数 0 - 100 表示 0 - 100% 的电量等。
      2. float 类指标:车辆累计里程等。
      3. boolean 类指标:展示左转灯开关状态等。
      4. text 类指标:设备唯一标识号等。
  • 车辆指标平台 A 的查询特点:

    • 主要为基于设备的点查及明细查询。

注意!
此场景的“设备”语义指每辆车辆上的不同传感器,每一个传感器称作一个“设备”。

  • 完整数据流:

车端预计算完毕的传感器数据 / 埋点数据 -> 云端 -> YMatrix -> 监控页面

结论
经过仔细、全面的前期调研,我们认为在 YMatrix 中,车辆指标平台 A 的指标确定性较高,但仍需要创建一个动态列,以备动态容纳新增或变化的指标。数据规模、设备数量大,应使用结构化+半结构化的宽表变式模型,选择压缩性能良好的 MARS2 存储引擎为基础建表。

2.2 建模设计与实施

2.2.1 表结构

据需求调研结果,表结构设计示例如下:

动态列可以选用 MXKV 数据类型,使用此类型需要首先创建扩展。

=# CREATE EXTENSION matrixts;
=# CREATE TABLE V2X ( 
     ts timestamp with time zone, 
     device_id text,
     vehicle_type text,
     longitude float,
     latitude float,
     altitude float,
     speed float,
     left_turn_signal boolean,
     right_turn_signal boolean,
     emergency_flashers boolean,
     power int,
     gas int,
     windshield_wiper boolean,
     mileage float,
     signal_strength text,
     power_mode text,
     control_mode text,
     charging_status text,
     mxkv mxkv_int4
 )
 USING MARS2 
 DISTRIBUTED BY (device_id,vehicle_type) 
 PARTITION BY range(ts)
 (
  START ('2023-01-15') INCLUSIVE 
  END ('2023-01-30') EXCLUSIVE 
  EVERY (interval '1 day'), 
  DEFAULT PARTITION default_p
 );
 =# CREATE INDEX ON V2X USING mars2_btree (device_id,ts,vehicle_type);

此示例中:

  • 标签为 device_id,vehicle_type 两列;
  • 指标为 longitude,latitude,altitude,speed,left_turn_signal,right_turn_signal,emergency_flashers,power,gas,windshield_wiper,mileage,signal_strength,power_mode,control_mode,charging_status 十五列;
  • 以上为结构化数据列,半结构化数据列则为 mxkv 列,用于存储动态扩展的指标及数据;
  • 指标类型为 int,float,text,boolean 四种;
  • 以 device_id,vehicle_type 作为分布键;
  • 以 device_id,ts,vehicle_type 作为排序键;
  • 以 ts 作为分区键;
  • 2023-01-152023-01-30 每一天为一个分区。

2.2.2 模型测试

序号 测试计划 SQL 语句
1 查看最新 10 条数据 SELECT * FROM <表名> ORDER BY ts DESC LIMIT 10
2 查询总行数 SELECT COUNT(*) FROM <表名>
3 查询某一设备最新上报的全部指标 SELECT * FROM <表名> WHERE <设备标签列名> = '<设备标签值>' ORDER BY <时间戳列名> DESC LIMIT 1
...

为结构化指标插入 100 条测试语句。

=# INSERT INTO V2X (ts, device_id, vehicle_type, longitude, latitude, altitude, speed, left_turn_signal, right_turn_signal, emergency_flashers, power, gas, windshield_wiper, mileage, signal_strength, power_mode, control_mode, charging_status)
SELECT 
    timestamp '2023-01-15 00:00:00+00' + (random() * (timestamp '2023-01-30 00:00:00+00' - timestamp '2023-01-15 00:00:00+00')) AS ts, 
    'device_' || (random() * 100 + 1)::int AS device_id, 
    CASE (random() * 3)::int 
        WHEN 0 THEN 'car'
        WHEN 1 THEN 'truck'
        WHEN 2 THEN 'motorcycle'
        ELSE 'others'
    END AS vehicle_type, 
    -180 + random() * 360 AS longitude, 
    -90 + random() * 180 AS latitude, 
    random() * 500 AS altitude, 
    random() * 100 AS speed, 
    random() < 0.5 AS left_turn_signal, 
    random() < 0.5 AS right_turn_signal, 
    random() < 0.5 AS emergency_flashers, 
    random() * 100 AS power, 
    random() * 100 AS gas, 
    random() < 0.5 AS windshield_wiper, 
    random() * 10000 AS mileage, 
    CASE (random() * 3)::int 
        WHEN 0 THEN 'low'
        WHEN 1 THEN 'medium'
        WHEN 2 THEN 'high'
        ELSE 'others'
    END AS signal_strength, 
    CASE (random() * 3)::int 
        WHEN 0 THEN 'normal'
        WHEN 1 THEN 'eco'
        WHEN 2 THEN 'sport'
        ELSE 'others'
    END AS power_mode, 
    CASE (random() * 3)::int 
        WHEN 0 THEN 'auto'
        WHEN 1 THEN 'manual'
        ELSE 'others'
    END AS control_mode, 
    CASE (random() * 3)::int 
        WHEN 0 THEN 'not_charging'
        WHEN 1 THEN 'charging'
        WHEN 2 THEN 'charged'
        ELSE 'others'
    END AS charging_status
FROM generate_series(1, 100);

测试结果:

  1. 查看最新 10 条数据
=# SELECT * FROM V2X ORDER BY ts DESC LIMIT 10;
              ts               | device_id | vehicle_type |      longitude      |      latitude      |      altitude      |       speed        | left_turn_signal | right_turn_signal | emergency_flashers | power | gas | w
indshield_wiper |      mileage       | signal_strength | power_mode | control_mode | charging_status | mxkv 
-------------------------------+-----------+--------------+---------------------+--------------------+--------------------+--------------------+------------------+-------------------+--------------------+-------+-----+--
----------------+--------------------+-----------------+------------+--------------+-----------------+------
 2023-01-29 21:53:27.754744+00 | device_58 | motorcycle   | -140.79258522765215 |  89.35868408629588 | 209.74325842947826 |  7.559001139516397 | t                | t                 | t                  |    93 |  65 | f
                |  5882.489034161899 | others          | sport      | others       | charging        | 
 2023-01-29 16:50:34.199873+00 | device_81 | car          |   81.53072026224788 |  68.19305944227892 |  148.1462308308661 | 13.198297428062489 | t                | f                 | t                  |    46 |  61 | t
                |   4511.35947286371 | medium          | sport      | others       | charging        | 
 2023-01-29 12:55:46.399362+00 | device_64 | truck        | -44.714334671379135 | -25.70332357303201 |  467.8799151243442 | 10.891007142353715 | f                | t                 | f                  |    84 |  35 | t
                | 2560.9704263982635 | high            | sport      | others       | charged         | 
 2023-01-28 20:57:47.338926+00 | device_76 | motorcycle   |  154.27746736812526 | 62.690265649507495 | 259.82523744619533 |  83.50521685533465 | f                | f                 | t                  |    89 |   8 | f
                | 1532.3430683284655 | high            | normal     | auto         | charged         | 
 2023-01-28 18:05:20.571742+00 | device_92 | car          |   83.76965429529218 |  8.049747667089093 | 143.73997285467334 | 12.555222600038718 | t                | f                 | f                  |    14 |  45 | f
                |   9997.57813287939 | medium          | eco        | others       | others          | 
 2023-01-28 08:45:11.965559+00 | device_31 | others       |   99.77044288198567 | 12.903015903288804 | 363.75041248912333 |  36.47226613770371 | f                | f                 | t                  |    69 |   3 | f
                |  3691.816009071509 | high            | others     | manual       | not_charging    | 
 2023-01-28 06:18:43.954116+00 | device_46 | motorcycle   |  -47.38049551213621 | -79.18208378659074 | 175.75280982307186 |  66.30605691081897 | t                | f                 | f                  |    45 |  54 | t
                |   9393.80450096177 | high            | others     | others       | others          | 
 2023-01-28 02:37:52.616967+00 | device_93 | truck        |   85.90194913335677 |  40.30649225935946 | 391.81388120428375 |  82.57330803350484 | f                | f                 | f                  |    26 |  75 | f
                | 3785.2133153634427 | medium          | eco        | others       | not_charging    | 
 2023-01-27 22:45:29.482177+00 | device_97 | truck        |  -84.89970873408637 | 44.363184540463294 | 194.22710732897565 |   75.3934265851349 | t                | f                 | f                  |    66 |  20 | t
                |  778.0037705985521 | medium          | eco        | manual       | charged         | 
 2023-01-27 21:58:16.378729+00 | device_86 | truck        | -160.41993080977775 | 29.832974328175652 |  481.3241481940853 |  29.14603485833851 | t                | t                 | f                  |    27 |  15 | t
                |  975.3916463929002 | high            | eco        | others       | charged         | 
(10 rows)
  1. 查询总行数
=# SELECT COUNT(*) FROM V2X;
 count
-------
   100
(1 row)
  1. 查询某一设备的某指标的最新非空值

查询设备 10 的最新车速。

=# SELECT last_not_null(speed,ts) AS last_speed FROM V2X WHERE device_id = 'device_86' LIMIT 1;
                      last_speed                       
-------------------------------------------------------
 ["29.14603485833851","2023-01-27 21:58:16.378729+00"]
(1 row)

在动态扩展列 mxkv 新增一个 MXKV 类型的指标 car_length,数据类型为 mxkv_int4,数据值为 350(单位 cm)。新增指标前,为了压缩与查询性能优化,需要首先利用 UDF mxkv_import_keys() 定义键值,来提前确定数据中包含的键值集合。

=# SELECT mxkv_import_keys('{"car_length": 350}');
 mxkv_import_keys
------------------
 car_length
(1 row)

插入这条数据。

=# INSERT INTO V2X(mxkv) values('{"car_length":350}');
INSERT 0 1

查询插入的此条键值。

=# SELECT mxkv->'car_length' as car_length FROM V2X;

注意!
MXKV 使用方法详见扩展数据类型

注意!
在此只给出了简单的测试示例,更多场景测试示例见图形化界面 轻松上手板块。在实际环境中,请根据具体的查询需求设计具体的测试语句。