基于 Tablestore 时序模型构建车联网数据存储

李鸿楠
  • 收获赞:15
  • 擅长领域:擅长领域:数据库、存储

本文基于车辆网场景,介绍如何使用表格存储时序模型搭建车联网存储架构。

背景

最近几年,物联网得到了飞速的发展。在车联网、设备监控、网络监控、快递跟踪等物联网典型场景下,海量监控数据、轨迹数据、传感器数据被生产出来。这些数据产生频率高、数据量大、严重依赖采集时间,是典型的时序数据。传统的数据库是无法应对这种高写入的海量实时数据的,需要使用能够支持时序模型的时序数据库对这些数据进行储存和分析。

表格存储时序模型是专门针对时序数据特点,为物联网、车联网等场景设计的。本文将展示如何使用表格存储时序模型简单、快速的搭建车辆网存储架构。

需求背景

车联网是物联网下的典型场景,不论是共享汽车、电动车监控,还是物流车联网 GPS 或者未来的无人驾驶系统,车辆网都将在其中发挥重要作用。车辆的实时监控数据,如位置、车速、油量、车内温度等,会实时且高频的持久化进入下游数据库。而在此类场景中,至少存在以下数据使用需求:

  • 元数据检索,比如查找车牌为“浙A*****”的车辆基础信息;比如统计某型号的车辆的数量。

  • 车辆监控数据检索,比如统计在某一时间段内,到达过某区域的车辆有哪些;查询车辆在具体时间段内的车辆状态。

在海量的时序数据下,数据存储方案面临挑战,架构设计者需要关注以下几点:

  • 如何在海量数据中对元数据进行高效的检索。

  • 如何支持快速的、低成本的时序数据存储和分析。

  • 如何设计数据存储模型,以符合业务灵活扩展的需求。

解决方案

传统方案

一般采用关系型数据库如 MySQL 存储车辆元数据。而实时数据会存放在分布式开源数据库如 HBase 中,为了支持检索需求,需要使用 ES 或者 Solr 对接 HBase。这样的架构如图所示:

image.png
说明

若其中存在数据 ETL 的需求,可能还需要引入流式数据计算引擎。

这套架构存在以下几个问题:

  • 架构复杂。

  • 运维成本高,难度大。

  • 可扩展性与规模受限制。

  • 稳定性差,数据延迟高。

表格存储时序模型方案

表格存储时序模型方案如图。

image.png

在这个架构下,无需引入其他组件,即可实现检索、流式数据处理、元数据管理等功能。其特点有:

  • 架构简单,使用表格存储时序表即可完成需求功能,同时支持元数据检索、时序数据检索。

  • 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);
    }

写入后可以在控制台看到对应数据如图。

image.png

控制台 SQL 查询

表格存储时序支持 SQL 功能,可以直接在控制台中通过 SQL 对数据进行检索。操作简单,学习成本低。

车辆基础信息检索

通过如下 SQL 查询元数据表,查找“苏A”牌照的车辆的记录。

select * from `test::meta` where tag_value_at(_tags, "liscense") like "苏A%"

可以看到检索结果如下

image.png

车辆实时状态数据检索

通过如下 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

可以看到检索结果如下

image.png

Grafana查询

表格存储同样支持通过 Grafana 查询时序表中的数据。

经过配置,我们可以在 Grafana 面板中选择需要查询的车辆,然后就可以看到其各个时序维度上如总里程、油量、温度、时速,数值随时间变化的曲线图。

表格存储的 SQL 支持时序元数据检索,我们利用这一能力在 Grafana 中增加一个车辆 ID 的变量,如下图左上角名称为 _data_source 的变量。切换这一变量,选择不同车辆 ID,可以看到不同车辆的数据。这样,通过 Grafana,使用者可以根据车辆 ID 快速定位到车辆的时序数据,并且可以通过图形页面直观的进行监测。

image.png

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 计算功能。