背景
最近几年,物联网得到了飞速的发展。在车联网、设备监控、网络监控、快递跟踪等物联网典型场景下,海量监控数据、轨迹数据、传感器数据被生产数来。这些数据产生频率高、数据量大、严重依赖采集时间,是典型的时序数据。传统的数据库是无法应对这种高写入的海量实时数据的,需要使用能够支持时序模型的时序数据库对这些数据进行储存和分析。
表格存储时序模型是专门针对时序数据特点,为物联网、车联网等场景设计的。本文基于车辆网场景,介绍如何使用表格存储时序模型搭建车联网存储架构。
需求背景
车联网是物联网下的典型场景,不论是共享汽车、电动车监控,还是物流车联网 GPS 或者未来的无人驾驶系统,车辆网都将在其中发挥重要作用。车辆的实时监控数据,如位置、车速、油量、车内温度等,会实时且高频的持久化进入下游数据库。而在此类场景中,至少存在以下数据使用需求:
- 元数据检索,比如查找车牌为“浙A****”的车辆基础信息;比如统计某型号的车辆的数量。
- 车辆监控数据检索,比如统计在某一时间段内,到达过某区域的车辆有哪些;查询车辆在具体时间段内的车辆状态。
在海量的时序数据下,数据存储方案面临挑战,架构设计者需要关注以下几点:
- 如何在海量数据中对元数据进行高效的检索。
- 如何支持快速的、低成本的时序数据存储和分析。
- 如何设计数据存储模型,以符合业务灵活扩展的需求。
解决方案
传统方案
一般采用关系型数据库如 MySQL 存储车辆元数据。而实时数据会存放在分布式开源数据库如 HBase 中,为了支持检索需求,需要使用 ES 或者 Solar 对接 HBase。这样的架构如图所示:
若其中存在数据 ETL 的需求,可能还需要引入流式数据计算引擎。
这套架构存在以下几个问题:
- 架构复杂
- 运维成本高,运维难度高
- 可扩展性与规模受限制
- 稳定性以及数据可见延迟
表格存储时序模型方案
表格存储时序模型方案如图。
在这个架构下,无需引入其他组件,即可实现检索、流式数据处理、元数据管理等功能。其特点有
- 架构简单,使用表格存储时序表即可完成需求功能,同时支持元数据检索、时序数据检索。
- PB 级数据存储,支持每秒千万级的高写入。
- 可扩展性强,自动扩容,运维简单。
- 基于表格存储底层功能,兼容其功能,比如,支持数据生命周期、存储计算分离等特性。
表格存储车联网方案实现
模型设计
表格存储支持标准的时序建模方式。时序模型见官网说明:概述。
在车联网中,会需要通过车牌照定位车辆,也会根据车辆颜色搜索车辆,因此车辆牌照、车辆颜色、标准载客数这些车辆的基础信息是需要进行记录的,车辆 ID 与行程 ID 作为车辆和行程的唯一标识,也需要进行记录;而车辆行驶过程中,车辆的实时监控数据如车辆位置、车速、车内温度、油箱油量剩余、车辆行驶总里程数,同样需要进行存储。车辆基础数据如车辆牌照、车辆颜色,这些数据是元数据信息;而车辆实时位置、车速等是时序数据中的数据数据信息。
这里将车辆 ID 记为数据源,即时序表中的 _data_source 字段,将车辆牌照、车辆颜色、标准载客数、行程 ID 作为标签字段即时序表中的 _tag 字段。将车辆行驶过程中的车辆位置、车速、车内温度、油箱油量剩余、车辆行驶总里程数作为时间线数据进行记录。
因此时序表模型设计如下:
参数字段 |
说明 |
Tablestore 时序表中对应数据 |
measurement |
记录类型,本例中使用" vehicle"作为这个参数的值 |
_m_name |
vehicle |
车辆ID |
_data_source |
tripId |
行程D |
_tag中的tripId字段 |
color |
车辆颜色 |
_tag中的 color字段 |
license |
车辆牌照 |
_tag中的 license字段 |
capacity |
标准载客数 |
_tag中的 capacity字段 |
timestamp |
当前时间戳 |
_time |
temperature |
车内温度 |
field中的数据 |
location |
地理位置经纬度,格式为"x,y" |
field中的数据 |
miles |
总里程 |
field中的数据 |
speed |
速度 |
field中的数据 |
oil |
油量 |
field中的数据 |
环境准备
完成以下准备工作。可以参考官网文章:时序模型
- 在阿里云官网开通表格存储。
- 在控制台创建时序实例。
- 创建时序表,可以通过控制台创建,也可以通过 SDK 创建。
写入数据
使用 SDK 向时序表模拟写入 100 辆汽车的数据,每辆汽车每秒钟生成一条数据,代码如下:
private void send(VehicleRecord record) {
PutTimeseriesDataRequest request = new PutTimeseriesDataRequest("test");
Map<String, String> map = new HashMap<>();
map.put("tripId", record.getTripId());
map.put("color", record.getColor());
map.put("liscense", record.getLiscense());
map.put("capacity", record.getCapacity() + "");
TimeseriesKey key = new TimeseriesKey("vehicle", record.getVehicle(), map);
TimeseriesRow r = new TimeseriesRow(key, System.currentTimeMillis() * 1000);
r.addField("location", ColumnValue.fromString(record.getLocation()));
r.addField("miles", ColumnValue.fromDouble(record.getMiles()));
r.addField("oil", ColumnValue.fromDouble(record.getOil()));
r.addField("speed", ColumnValue.fromDouble(record.getSpeed()));
r.addField("temperature", ColumnValue.fromDouble(record.getTemperature()));
request.addRow(r);
asyncTimeseriesClient.putTimeseriesData(request,null);
}
写入后可以在控制台看到对应数据如图。
控制台 SQL 查询
表格存储时序支持 SQL 功能,可以直接在控制台中通过 SQL 对数据进行检索。操作简单,学习成本低。
车辆基础信息检索
通过如下 SQL 查询元数据表,查找“苏A”牌照的车辆的记录。
select * from `test::meta` where tag_value_at(_tags, "liscense") like "苏A%"
可以看到检索结果如下
车辆实时状态数据检索
通过如下 SQL 查询数据表,查找车辆编号为"vehicle55"的车辆的位置信息。
select _m_name,_data_source,_tags,_field_name,_string_value,_time from `test` where _data_source = "vehicle55" and _field_name = "location" order by _time asc
可以看到检索结果如下
Grafana查询
表格存储同样支持通过 Grafana 查询时序表中的数据。
经过配置,我们可以在 Grafana 面板中选择需要查询的车辆,然后就可以看到其各个时序维度上如总里程、油量、温度、时速,数值随时间变化的曲线图。
表格存储的 SQL 支持时序元数据检索,我们利用这一能力在 Grafana 中增加一个车辆 ID 的变量,如下图左上角名称为 _data_source 的变量。切换这一变量,选择不同车辆 ID,可以看到不同车辆的数据。这样,通过 Grafana,使用者可以根据车辆 ID 快速定位到车辆的时序数据,并且可以通过图形页面直观的进行监测。
SDK SQL 查询
SDK 中提供了接口,可以支持通过 SQL 的方式对数据进行访问。
车辆基础信息检索
在 Tablestore SDK 中可以直接通过 SQL 来对元数据进行检索。
若需要检索元数据中车牌为“苏A”的相关数据,代码如下
public void testSQLMetaQuery() {
String sql = "select * from `test::meta` where tag_value_at(_tags, \"liscense\") like \"苏A%\"";
SQLQueryRequest request = new SQLQueryRequest(sql);
SQLQueryResponse response = syncClient.sqlQuery(request);
SQLResultSet set = response.getSQLResultSet();
SQLTableMeta meta = response.getSQLResultSet().getSQLTableMeta();
while (set.hasNext()) {
SQLRow row = set.next();
for (SQLColumnSchema schema : meta.getSchema()) {
System.out.println(row.get(schema.getName()));
}
}
}
车辆实时状态数据检索
在 Tablestore SDK 中同样可以直接通过 SQL 来对时序数据进行检索。
若需要获得车辆 ID 为"vehicle55" 的车辆过去一段时间的位置数据,代码如下。
public void testSQLDataQuery() {
String sql = "select _m_name,_data_source,_tags,_field_name,_string_value,_time from `test` where _data_source = \"vehicle55\" and _field_name = \"location\" order by _time asc";
SQLQueryRequest request = new SQLQueryRequest(sql);
SQLQueryResponse response = syncClient.sqlQuery(request);
SQLResultSet set = response.getSQLResultSet();
SQLTableMeta meta = response.getSQLResultSet().getSQLTableMeta();
while (set.hasNext()) {
SQLRow row = set.next();
for (SQLColumnSchema schema : meta.getSchema()) {
System.out.println(row.get(schema.getName()));
}
}
}
SDK 时序 API 查询
Tablestore SDK 提供了根据主键列(_m_name, _data_source, _tag)以及时间范围查询数据的接口,代码实例如下:
public void testQuery() {
GetTimeseriesDataRequest request = new GetTimeseriesDataRequest("test");
Map<String, String> map = new HashMap<>();
map.put("tripId", "57");
map.put("color", "black");
map.put("liscense", "浙C4949*");
map.put("capacity", "4");
TimeseriesKey key = new TimeseriesKey("vehicle", "vehicle57", map);
request.setTimeseriesKey(key);
request.setTimeRange(1642417165547000L, 1642417246472000L);
Future<GetTimeseriesDataResponse> future = asyncTimeseriesClient.getTimeseriesData(request,null);
try {
GetTimeseriesDataResponse response = future.get();
List<TimeseriesRow> rows = response.getRows();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
总结
表格存储时序模型为物联网场景提供了一种新的解决方案。本文模拟车联网场景,展示了如何使用表格存储时序模型搭建车联网下的时序存储系统。展示了时序数据的写入,元数据、时序数据的检索能力。时序模型除了在 Sdk 中提供对应功能接口外,还支持 SQL 查询,极大降低了接入难度。
目前,表格存储团队正在针对时序模型开发多值绑定模型,可以支持更复杂的 SQL 计算功能。
希望本次分享对你的时序设计架构有所帮助,如果希望继续交流,可以加入我们的开发者技术交流群,可搜索群号『11789671』或『23307953』,亦可直接扫码加入。