SENSORO 处理智慧城市海量感知数据的数据库应用
时间:2023-07-25 12:37:00
作为城市级数据服务提供商,SENSORO(北京盛哲科技有限公司)是物联网和人工智能的领先独角兽企业。
要建设城市级物联网感知网络,涉及的物联网设备种类繁多。例如,街道路灯、道路智能人孔盖、社区门禁、走廊智能空气开关、房间屋顶烟雾报警、商店门磁和红外人体探测器、仓库温湿度传感器、地下室管道、厨房可燃气体报警、水管智能水表等……基于业务需求,这些终端始终产生各种感知数据,这些数据需要正确存储。
第一阶段:强大但昂贵 Elasticsearch
如果有大量的数据需要存储和实现各种统计分析需求,首先想到的是将数据插入 ES 里。
业务初期没有问题,ES 优秀的写入性能、水平扩展能力、成熟的生态,加上强大的搜索查询 API,让我们的开发人员轻松应对产品经理的各种数据统计分析需求。
我们最初的使用方法相对简单粗暴,各种感知数据都写入 Kafka 集群中,通过 Logstash 消费 Kafka 将数据写入到 ES。同时,月拆分索引,方便数据清理。
1.1 无法写入数据 —— Mapping Explosion
几个月后,我们遇到了第一个问题,有一天数据突然无法写入。我们数据的字段数很快就达到了 ES 默认的 index.mapping.total_fields.limit: 1000。虽然数据写入问题是通过临时扩展设置解决的,但毕竟是权宜之计,需要根据设备类型进行分拆 index 为了完全避免这个问题的再次发生,幸运的是使用它 ES 我们仍然不需要自己维护每个设备 Schema。
1.2 ES 数据膨胀和存储成本
ES 作为一个分布式系统,数据通常保存副本,以确保可靠性。副本有利于增加数据的可靠性,但也会增加存储成本。除原始数据外,ES 应用编码压缩等技术后,还需要存储索引、存储数据等 10%。同时 ES 还需要一定空间用于 segment 合并、ES Translog、日志等。大体而言 ES 集群使用空间约为源数据 × (1 副本数量) × 1.45。
哦,别忘了能不能让集群节点磁盘满了。我们通常在节点磁盘上 80~85% 集群扩容将考虑增加节点。因此,空间占用可能成为源数据 × (1 副本数量) × 1.6.以默认副本数 1 就存储使用而言,它已经成为原始数据 3.2 倍。
所以简单来说,ES 功能强大,使用方便,大多数云服务提供商提供相应的服务。但缺点是显而易见的,长期存储大量数据太贵了。
第二阶段:操作和维护复杂 Apache Druid
考虑到成本,我们决定选择一个时间序列数据库来存储系统的感知数据。理论上,依靠时间序列数据库的有针对性的优化,存储成本应该可以通过更高的压缩比大大降低。
打开 DB-Engines 的 Ranking 页面,分类选到 Time Series DBMS,下一步是做出选择。
db-engines 2022年4月时序数据库排名
完全开源,InfluxDB、Kdb pass;Prometheu 与 Graphite 都是为了监控,数据必须是Numeric data only”(RRDtool 同样是 Numberic data only)。基本上,我们考虑的是 TimescaleDB、Apache Druid 与 OpenTSDB 中选择。
原本 OpenTSDB Schema-free 特点很加分,但要看 HBase 部署的复杂性是另一个问题。TimescaleDB 基于 PG 扩展,同样的问题是数据压缩率不是很理想,我们查阅数据,学习了解相应系统的存储结构和相应的空间使用,以及系统架构设计的各个方面,我们仍然选择 Druid。
2.1 Schema 问题
选定 Druid 之后,我遇到了问题。我们的传感器种类太多了,每种类型至少有三到五种,超过十种细分型号,随着时间和业务的发展,类型和数据仍在增加。存储这么多型号为每个设备单独建模创建相应的表结构是不现实的。那么,为每种设备设计共用的大表行得通吗?至少根据我们目前的划分, 18 对于个子系统,至少需要创建 18 张表(至少是因为每个子系统实际上仍然包含不同类型的设备)。
对应到 Druid 的 Datasource,仅创建 18 个 indexing 任务是一大笔资源开支。
因此,我们需要设计一个通用的存储方案,简单地提供额外的类似性 ES 的 Auto Mapping 业务数据和存储数据结构的映射是通过程序实现的。我们称之为横向和纵向映射有两种不同的想法。
2.1.1 纵向
这个比较简单,就是把一个数据分成多个数据,每个数据变成相应的数据 Key-Value 存储结构 Druid 而言 Datasource 的 Schema 如下:
以下数据:
存储到 Druid 需要拆分成 4 条:
例如,我们需要查询传感器 “01020304” 以下句子可用于过去一天的最大温度:
这样的存储解决了 Schema 问题,但也会带来新的问题。首先,明显的数据量会增加几十倍,但设计时序数据库来处理这个场景是可以接受的,而这个结构 Druid 它还可以实现更高的数据压缩比。根据我们的使用情况,大约使用100亿条数据 110GB 左右存储空间。
另一个问题更麻烦,同一设备的数据被拆分并存储在不同的行中,但大多数情况下,特别是对于一些复杂类型的传感器,如电气传感器,通常会报告几十种感知数据。数据很容易拆卸和重新合并。以最简单的数据日志为例,如何使用一个 SQL 查询出来 “01020304” 过去 1 天报数据呢?
2.1.2 横向
另一方面,我们可以提前创建一个大的宽表,将原始数据字段的名称映射成一个统一的列,并存储在同一表的对应列中。大多数物联网感知数据都是 Number 我们将提前创建类型数据 50 列出并编号。
写入之前提到的温湿度数据 Druid 集群中的数据将变成:
处理数据可以解决纵向拆分数据带来的问题,但也会带来额外的数据映射管理复杂性。我们需要开发相应的程序,将原始数据转换为相应的格式,同时保持列与原始字段名的映射关系。每次查询时都要获取相应的映射信息,以决定查询聚合的列。同时,数据库中会有大量的空列,这就要求数据库能够有效地应对这种情况。
维护的映射信息还应考虑字段的增加。假设我们的温湿度传感器包含气压传感器,相应的大气压数据报告字段将通过固件升级增加。
映射数据最好包含相应的时间关系,反映什么时间段不存在 atmosphere 这个字段。
我们最终选择了水平存储数据,到目前为止基本上可以满足各种需求。但仍存在电气火灾、空气开关等难以处理的问题,都存在电压等感知数据。当您想统计某一区域的电压时,不容易实现相应的查询。
2.2 部署操作和维护的复杂性
使用 Druid 集群的另一个问题是部署和运维的复杂性。
Druid 划分了 Coordinator、Overlord、Broker、Router、Historical、MiddleManager 六个进程。实现完整的集群功能,Druid 还需要一个 Deep storage (支持 S3 和 HDFS),Metadata storage (典型如 mysql / pgsql),需要实现服务发现和选择功能 ZooKeeper。
从工程师的角度来看,Druid 架构设计可以说是清晰的,每项服务只承担单一的责任,同时也可以充分用现有的基础设施,支持灵活性 retention rules,可轻松实现多级存储,降低硬件成本,优化热数据查询性能。然而,对于运维同事来说,该系统太复杂,需要太多的组件,太多的外部依赖,以及简单启动集群所需的最小资源(对于开发测试环境)。
上图配置了最近两周的数据 Druid 集群中 hot (高性能配置 SSD 节点)保存 3 最近一个副本 1 月双副本,保留最近一年 default_tier 清理数据超过一年。
第三阶段:平衡成本和性能 TDengine
Druid 系统的复杂性一直困扰着我们,同时,随着数据量的增加,查询聚合的响应时间并不理想(大多数情况下,亚秒响应仍然可以实现,但响应时间波动很大)。然后我们想起了以前研究过的国内时间数据库 TDengine。
我们部署了一套 TDengine 结果很好:
我们导入了相同的两个数据 Druid 和 TDengine 以下是三个节点(8c16g),100 万传感备,40 列(6 个字符串数据列,30 个 double 数据列,以及 4 个字符串 tag 列),总计 5.5 亿条记录的结果(由于数据很多为随机生成,数据压缩率一般会比真实情况要差)。
3.1 资源对比
3.2 响应时间对比
1. 随机单设备原始数据查询
a. 查询结果集 100 条
b. 重复 1000 次查询,每次查询设备随机指定
c. 查询时间区间分别为:1 天、7 天、1 月,
d. 统计查询耗时的最大值、最小值、平均值
e. SELECT * FROM device_${random} LIMIT 100
2. 随机单设备聚合查询
a. 聚合计算某列的时间间隔的平均值
b. 重复 1000 次查询,每次查询设备随机指定
c. 查询时间区间分别为:1 天、7 天、7 天、1 月,对应聚合时间为 1 小时、1 小时、7 天,7 天。
d. 统计查询耗时的最大值、最小值、平均值
e. SELECT AVG(col_1) FROM device_${random} WHERE ts >= ${tStart} and ts < ${tEnd} INTERVAL(${timeslot})
3. 随机多设备聚合查询
a. 聚合计算某列的时间间隔的总和
b. 重复 1000 次查询,每次查询设备约 10000 个
c. 查询时间区间分别为:1 天、7 天、7 天、1 月,对应聚合时间为 1 小时、1 小时、7 天,7 天。
d. 统计查询耗时的最大值、最小值、平均值
e. SELECT SUM(col_1) FROM stable WHERE ts >= ${tStart} and ts < ${tEnd} AND device_id in (${deviceId_array}) INTERVAL(${timeslot})
TDengine 的空间占用只有 Druid 的 60%(没有计算 Druid 使用的 Deep storage)。针对单一设备的查询与聚和的响应时间比 Druid 有倍数的提升,尤其时间跨度较久时差距十倍以上,Druid 的响应时间方差也较大。然而针对多子表的聚合操作,TDengine 与 Druid 的表现没有明显区别,可以说是各有优劣。
总之,对比 Druid 在物联网感知数据方面,TDengine 的性能、资源使用方面均有较大领先。再加上 TDengine 安装部署配置上的简单方便(尤其是私有化应用的部署场景),以及相较于 Apache 社区支持来说更可靠与及时的商业服务,当前阶段我们的传感数据存储使用的便是 TDengine。
新型智慧城市建设过程中的挑战很多,城市级传感网络的数据存储只是其中重要的一部分。升哲科技基于技术的创新融合能力和场景落地的长效服务能力,已取得丰硕成果。延续数字化应用标杆的共建、共享,升哲科技希望将一些经验心得分享出来,以期让数字化建设成果惠及更多城市。接下来,我们还将对「支撑百万级传感器的延时队列」进行分享,敬请期待。