车联网场景下海量车辆状态数据存储实践

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDS DuckDB + QuickBI 企业套餐,8核32GB + QuickBI 专业版
简介: 随着通信技术、计算机技术的不断发展,移动通信正在从人与人(H2H)向人与物(H2M)以及物与物(M2M)的方向发展,“万物互联”的概念正在逐步覆盖到各行各业中,例如智能家居、智能农业、智能交通、智能物流等领域。目前,车联网技术已经先行一步,在行车安全、交通管理、生活服务等方面得到充分应用。车联网技术包括了车辆终端、云端、无线通信等方面。车辆终端实时产生大量车辆状态数
作者:李欣

背景

随着通信技术、计算机技术的不断发展,移动通信正在从人与人(H2H)向人与物(H2M)以及物与物(M2M)的方向发展,“万物互联”的概念正在逐步覆盖到各行各业中,例如智能家居、智能农业、智能交通、智能物流等领域。目前,车联网技术已经先行一步,在行车安全、交通管理、生活服务等方面得到充分应用。

车联网技术包括了车辆终端、云端、无线通信等方面。车辆终端实时产生大量车辆状态数据,例如里程、油量、胎压、坐标、温度、速度和操作等等。通过对这些数据的检索、分析,可以在环境感知、驾驶决策、行驶安全、出行规划等各个方面发挥作用。但是在车联网场景下,技术实现上也会面临几个难题:
● 数据写入并发度高:数据写入并发数取决于行驶中的车辆数。
● 数据检索延迟敏感:若数据检索出现延迟,则无法感知到最新的车辆状态信息,可能出现无法预料的后果。
● 数据规模庞大:每个车辆终端会记录几十个甚至更多维度的状态数据,周期性保存车辆的状态信息获取车辆状态时序数据。数据规模轻松达到亿行,甚至百亿行以上。

方案

MySQL + LogStash + Elasticsearch 方案

MySQL 自身具备强事务,可作为车辆状态数据存储库。数据量在一定级别内时,可满足业务的写需求。然而 MySQL 中的多列索引需要满足前缀匹配才能发挥效果,当查询条件不符合多列索引匹配规则时,可能会退化成全表扫描。这样一个慢 SQL 会增加服务端负载,使得 MySQL 服务性能降低。因此很容易想到需要一个新的索引引擎来做检索类查询的分流,将一些复杂的查询、分析类需求放到索引引擎中来完成。

Elasticsearch 是一款强大开源搜索和分析引擎,支持丰富的索引类型,通过引入 Elasticsearch 来提供检索、分析的能力,可以有效降低 MySQL 服务端负载。MySQL 数据通过 LogStash 或 Canal 工具同步到 Elasticsearch中。

方案的整体架构如下图:
c1.png

  1. MySQL:作为数据存储主库,需能够支持高并发的状态数据变更写入,支持平台侧基于主键查询的能力。
  2. LogStash:作为 MySQL 与 Elasticsearch 之间的桥梁,负责将 mysql binlog 转换成 Elasticsearch 的数据结构并写入Elasticsearch。
  3. Elasticsearch:作为存储系统索引引擎,负责承载检索、分析聚合类的请求流量。

MySQL + Elasticsearch的方案很好地解决了检索、分析类的业务需求。不过整个存储系统仍然存在一些问题:

  1. Elasticsearch 集群搭建与运维复杂度高,一旦出现问题非常难排查。
  2. Elasticsearch 成本较高。需要根据业务规模预测机器数,无法做到弹性扩容。

MySQL + Canal + Tablestore 方案

Tablestore 是阿里云自研的一款多模型结构化数据存储,能够支持 PB 级存储、千万 TPS 写入以及强大的数据检索、分析能力。Tablestore 具备两种存储引擎,数据表基于 LSM-tree 架构能支持高并发低延迟的读写能力,多元索引基于倒排索引、空间索引能支持丰富的数据检索方式,例如多列组合查询、模糊查询、匹配查询、范围查询等等。

在车辆网场景中,Tablestore 的数据表可支持千万级的并发读写,可实现车辆状态实时更新。多元索引引擎提供了百亿行数据毫秒级的检索的能力,可实现根据车辆多种状态查询。Tablestore 服务端实现了数据表与多元索引之间自动数据同步,保证了两者的最终一致性。Tablestore 方案实现车联网存储系统的架构图如下:

c2.png

下面会重点给大家介绍下如何实现:MySQL + Canal + Tablestore 的方案。

实现

开通 Tablestore 服务和创建表

  1. 创建按量模式实例,填写实例名。详情请参考 如何开通表格存储服务、如何创建 Tablestore 实例

c3.png

  1. 创建 auto_mobile 表。保存车辆状态数据信息。详情请参考 如何创建 Tablestore 数据表

c4.png

  1. 创建 his_auto_moile 表。保存车辆状态数据时序信息。

c5.png

准备数据:car-ots-demo(可选步骤)

本文中测试场景中模拟创建了 auto_mobile、his_auto_mobile 两张表,若需要同步其他业务表,请忽略此步骤。

  1. 以linux为例,运行命令 shell tar -zxvf car-ots-demo.tar.gz 解压,解压后目录中包含 application.yml 和 test-1.0-SNAPSHOT.jar。
  2. 配置 application.yml 文件。

    1. server.port:压测项目运行端口号。如:8082
    2. spring.datasource.jdbc:MySQL数据源地址,如:rm-bp13dz7yedd2d9zyr.mysql.rds.aliyuncs.com。
    3. spring.datasource.user: MySQL 数据源账号。
    4. spring.datasource.password: MySQL 数据源密码。
  3. 运行命令 shell nohup java -jar -Dspring.config.location=application.yml test-1.0-SNAPSHOT.jar& 启动压测程序。
  4. 运行命令 shell curl "localhost:8082/car/prepareTable" -X POST 创建MySQL表。

MySQL 实时同步 Tablestore

可以通过两种方案来将 MySQL 数据实时同步到 Tablestore 中。

  1. 数据传输服务 DTS 同步

关于 DTS 迁移方案可参考文档 MySQL 同步至 Tablestore。

  1. Canal 工具同步。Canal 是阿里巴巴开源 CDC 工具,可基于解析 MySQL Binlog 将增量数据传输到下游,表格存储团队实现了 TablestoreAdapter 用于写入数据到 Tablestore。
    a. 下载同步工具 Canal。运行命令 shell unzip canal.zip 解压压缩包。
    b. 配置 deployer 和 canal-adapter。
    c. 启动 deployer 和 canal-adapter。

说明:Canal 同步任务配置参数不在本文章中介绍,关于同步任务的配置项、启动步骤说明。请参考 CarmobileExample中的 READ.ME。

MySQL 测试数据写入

数据压入 jar 包 car-ots-demo 中已提供了测试数据写入接口,运行命令 shell curl "localhost:8082/car/press?carNum=10&point=10" -X POST 即可。其中 carNum 表示车辆数,point 表示车辆状态记录次数。本文测试场景carNum=1000000, point=10。

  1. 运行写入命令:

c6.png

  1. 写入MySQL日志(car-ots-demo/logs/example.log):

c7.png

  1. Canel数据同步日志(canal-adapter/logs/adapter/adapter.log):

c8.png

  1. 之前已经建立了同步链路,这个时候增量数据会自动同步到 Tablestore:

c9.png

Tablestore 创建多元索引

登录表格存储控制台,进入索引管理页面,点击创建多元索引。多元索引创建后,数据表中的存量和增量数据将自动同步到索引中。更多关于创建多元索引的介绍请参考 创建和使用多元索引
c10.png

c11.png

Tablestore 查询功能展示

  • 案例一。查询油量小于 120.0 并且胎压小于 2.3 的车辆 ID,取 3 条记录。

查询代码:

//查询油量小于 120.0 并且胎压小于 2.3 的车辆ID,取 3 条记录
public static void searchDemo01(SyncClient client){
        SearchRequest searchRequest = SearchRequest.newBuilder()
                .tableName("auto_mobile")
                .indexName("auto_mobile_index")
                .searchQuery(SearchQuery.newBuilder()
                        .query(QueryBuilders.bool()
                                .must(QueryBuilders.range("oil").lessThan(120.0))
                                .must(QueryBuilders.range("typepressure").lessThan(2.3))
                        )
                        .limit(3)
                        .build())
                .addColumnsToGet(Arrays.asList("car_ID"))
                .build();
        long sys1 = System.currentTimeMillis();
        SearchResponse searchResponse = client.search(searchRequest);
        long sys2 = System.currentTimeMillis();
        System.out.println("cost time: " + (sys2-sys1) + "ms");
        for(Row row : searchResponse.getRows()){
            System.out.println(row);
        }
        client.shutdown();
    }

结果展示:

cost time: 12ms
[PrimaryKey:]car_md5ID:0000104cd168386a335ba6bf6e32219d, car_ID:848775
[Columns:]
[PrimaryKey:]car_md5ID:000093856b4e947511870f3e10464129, car_ID:646434
[Columns:]
[PrimaryKey:]car_md5ID:0001181bf1ad8f82dcf59c7c18343bd5, car_ID:752608
[Columns:]

SQL查询:

select * from auto_mobile where oil < 120.0 and typepressure < 2.3 limit 3;

SQL查询结果:
c12.png

  1. 案例二。统计距离 car_md5ID = '0000104cd168386a335ba6bf6e32219d' 五公里内的车辆数。

查询代码:

//统计距离car_md5ID='0000104cd168386a335ba6bf6e32219d'五公里内的车辆数。
public static void searchDemo02(SyncClient client){
        SearchRequest searchRequest = SearchRequest.newBuilder()
                .tableName("auto_mobile")
                .indexName("auto_mobile_index")
                .searchQuery(SearchQuery.newBuilder()
                        .query(QueryBuilders.geoDistance("location")
                                .centerPoint("30.968772887652293,120.94512114660778")
                                .distanceInMeter(5000)
                        )
                        .getTotalCount(true)
                        .build())
                .build();
        long sys1 = System.currentTimeMillis();
        SearchResponse searchResponse = client.search(searchRequest);
        long sys2 = System.currentTimeMillis();
        System.out.println("cost time: " + (sys2-sys1) + "ms");
        System.out.println("车辆数:" + searchResponse.getTotalCount());
        client.shutdown();
    }

结果:

cost time: 18ms
车辆数:6026
  1. 案例三。查询所有车辆,统计车辆数。并按照里程段分组。

查询代码:

//查询所有车辆,统计车辆数。并按照里程段分组。
public static void searchDemo03(SyncClient client){
        SearchRequest searchRequest = SearchRequest.newBuilder()
                .tableName("auto_mobile")
                .indexName("auto_mobile_index")
                .searchQuery(SearchQuery.newBuilder()
                        .query(QueryBuilders.matchAll())
                        .addGroupBy(GroupByBuilders.groupByRange("groupByMileage","mileage")
                                .addRange(Double.MIN_VALUE,2000)
                                .addRange(2000,4000)
                                .addRange(6000,8000)
                                .addRange(8000,Double.MAX_VALUE)
                        )
                        .build())
                .build();
        long sys1 = System.currentTimeMillis();
        SearchResponse searchResponse = client.search(searchRequest);
        long sys2 = System.currentTimeMillis();
        System.out.println("cost time: " + (sys2-sys1) + "ms");
        GroupByRangeResult groupByRangeResult =  searchResponse.getGroupByResults().getAsGroupByRangeResult("groupByMileage");
        for(GroupByRangeResultItem groupByRangeResultItem : groupByRangeResult.getGroupByRangeResultItems()){
            System.out.println("["+groupByRangeResultItem.getFrom()+","+groupByRangeResultItem.getTo()+"] : "+groupByRangeResultItem.getRowCount());
        }
        client.shutdown();
    }

结果展示:

cost time: 98ms
[-Infinity,2000.0] : 199192
[2000.0,4000.0] : 200569
[4000.0,6000.0] : 199379
[6000.0,8000.0] : 200708
[8000.0,Infinity] : 200152

最后

表格存储 Tablestore 中多元索引功能可支持在大规模数据场景下实现毫秒级检索,以及轻量级分析的能力。通过表格存储控制台一键索引,自动完成全增量数据同步,省去了索引集群运维的烦恼。通过多元索引的范围查询、多条件组合查询、分组聚合等能力,实现了根据车辆多种状态检索聚合的功能。

相关实践学习
以电商场景为例搭建AI语义搜索应用
本实验旨在通过阿里云Elasticsearch结合阿里云搜索开发工作台AI模型服务,构建一个高效、精准的语义搜索系统,模拟电商场景,深入理解AI搜索技术原理并掌握其实现过程。
ElasticSearch 最新快速入门教程
本课程由千锋教育提供。全文搜索的需求非常大。而开源的解决办法Elasricsearch(Elastic)就是一个非常好的工具。目前是全文搜索引擎的首选。本系列教程由浅入深讲解了在CentOS7系统下如何搭建ElasticSearch,如何使用Kibana实现各种方式的搜索并详细分析了搜索的原理,最后讲解了在Java应用中如何集成ElasticSearch并实现搜索。 &nbsp;
目录
相关文章
|
存储 编译器 Linux
MinGW下载和安装教程
要知道,GCC 官网提供的 GCC 编译器是无法直接安装到 Windows 平台上的,如果我们想在 Windows 平台使用 GCC 编译器,可以安装 GCC 的移植版本。目前适用于 Windows 平台、受欢迎的 GCC 移植版主要有 2 种,分别为 MinGW 和 Cygwin。其中,MinGW 侧重于服务 W本节要重点给大家介绍的是 MinGw 的安装和使用。MinGw 全称 Minimalist GNU for Windows,应用于 Windows 平台,可以为我们提供一个功能有限的 Linux 系统环境以使用一些 GNU 工具,比如 GCC 编译器、gawk、bison 等等。
5926 0
MinGW下载和安装教程
|
7月前
|
存储 物联网 Serverless
理想汽车基于 Hologres + Flink 构建万亿级车联网信号实时分析平台
理想汽车携手阿里云Hologres+Flink,打造万亿级车联网实时分析平台。面对百万余辆智能车、每秒百万级信号上报的挑战,通过存算分离、冷热分层、流批一体等创新,实现写入性能提升200%、查询QPS超万、成本降低40%,支撑数字孪生、智能诊断等高实时业务,构建高可用、弹性伸缩、低成本的下一代数据底座。
672 4
|
存储 人工智能 自然语言处理
15.4K Star!Vercel官方出品,零基础构建企业级AI聊天机器人
"基于Next.js 14和AI SDK打造的Chat SDK,让开发者快速构建支持多模态交互、代码执行、文件共享的智能对话系统,5分钟完成全栈部署!" —— Vercel AI Chatbot项目核心宣言
775 5
|
JavaScript 前端开发
将base64格式的图片画到canvas上(js和vue两种)
将base64格式的图片画到canvas上(js和vue两种)
1207 1
|
SQL Java 数据库连接
Spring Boot联手MyBatis,打造开发利器:从入门到精通,实战教程带你飞越编程高峰!
【8月更文挑战第29天】Spring Boot与MyBatis分别是Java快速开发和持久层框架的优秀代表。本文通过整合Spring Boot与MyBatis,展示了如何在项目中添加相关依赖、配置数据源及MyBatis,并通过实战示例介绍了实体类、Mapper接口及Controller的创建过程。通过本文,你将学会如何利用这两款工具提高开发效率,实现数据的增删查改等复杂操作,为实际项目开发提供有力支持。
1454 0
|
JavaScript 前端开发 搜索推荐
【前端基础篇】JavaScript之DOM介绍1
【前端基础篇】JavaScript之DOM介绍
296 0
|
网络协议 数据安全/隐私保护 网络架构
|
负载均衡 Java Nacos
python flask服务如何注册到nacos
一文讲清楚python flask服务如何注册到nacos
686 2
python flask服务如何注册到nacos
|
前端开发 JavaScript 算法
|
存储 算法 C语言
【C 言专栏】用 C 语言开发游戏的实践
【5月更文挑战第5天】本文探讨了使用C语言开发游戏的实践,包括选择适合的游戏类型(如贪吃蛇、俄罗斯方块),设计游戏框架、图形界面和逻辑,以及音效添加。文章还强调了性能优化、测试调试、跨平台挑战及未来发展趋势。对于热衷于C语言的开发者,这是一次挑战与乐趣并存的探索之旅。
596 0
【C 言专栏】用 C 语言开发游戏的实践