作者:段雪林
小 T 导读:SENSORO(北京升哲科技有限公司)是一家当先的物联网与人工智能独角兽企业。作为城市级数据服务提供商,公司在新一代信息技术畛域领有外围研发能力,在国内首次实现物联网与人工智能畛域端到端、一体化的技术与产品能力,蕴含自研物联网通信芯片、通信基站、智能感知终端和智能视觉终端及外围数据平台等。SENSORO 面向城市基础设施与外围因素提供全域数字化服务计划,通过将多项外围自研关键性技术深刻利用到智慧城市、农村振兴、区域治理、社会民生等畛域,打造物联网与人工智能利用的数字利用标杆,赋能我国城乡数字经济的高质量倒退。
建设城市级传感器网络所波及的传感器品种非常多样,由此产生的数据量也非常宏大,如果只是应用 MySQL、PostgreSQL 等 OLTP 零碎进行数据的简略存储,不仅会产生很多问题,而且其程度扩大能力也无限,同时也因为没有专门针对物联网数据进行优化而不足足够的压缩成果,数据存储老本很高。
在零碎开发初期,联合之前的教训咱们先是抉择了 Apache Druid 作为存储传感数据的数据库,然而在应用过程中却遇到了各种各样的问题,这使得咱们将眼光转移到了 TDengine 这款时序数据库。事实上,在 TDengine 开源之初咱们就留神到了这个新兴的时序数据库,浏览过后公布的白皮书与性能测试报告时惊艳感由衷而生,随即联系到了涛思的同学们,进行了更深刻的交换与测试。
但因为平台波及的非凡数据模型,单干便始终搁置了下来。次要问题在于数据有 A、B 两个维度且是多对多关系还会随工夫变动,基于 A 创立子表(此时无奈将 B 设置成 tag 列)就无奈通过 B 进行聚合查问,还须要破费较大的工夫与精力革新成 TDengine 特有的超级表构造。之后 TDengine 也通过了多个版本迭代,反对了 join 查问,而咱们的数据模型也产生了变动,迁徙到 TDengine 时不再须要做出很多的零碎模块改变。
一、基于 Apache Druid 现存零碎的问题
基于 Apache Druid,零碎最大的问题就是保护老本了。Druid 划分了 Coordinator、Overlord、Broker、Router、Historical、MiddleManager 六个过程,要实现残缺的集群性能,其还须要 Deep Storage(反对 S3 和 HDFS),Metadata Storage(典型如 MySQL、PGSQL),以及为实现服务发现与选主性能而须要的 ZooKeeper,由此也能够看出 Druid 是一套极为简单的零碎。
同时,Druid 对外部的各种依赖也导致运维同学在解决一些问题时,会间接或间接地影响到它的运行,比方咱们将 S3 的 AccessKey 进行规范化解决——由以前的全局通用改成某个 bucket 惟一,或者将 PGPool 降级,都会影响到 Druid。而且 Druid 针对每一个过程和内部依赖都有厚厚的几页配置项,且从 JVM 本身来看,不同过程、配置、MaxDirectMemorySize 都会重大影响写入查问性能。如果你要从官网文档的配置页面从顶划到底,可能会把手指划抽筋。
基于 Apache Druid 的零碎架构(Druid 每个过程都独自的部署并有不同的配置)
为了节俭存储老本,咱们在部署 Druid 集群时对于 Historical 节点采纳了多种不同的机器配置,在近期数据的解决上,机器装备 SSD 硬盘并设置较多正本数。这导致数量最多的 Data Server 节点,有一些不能与 Middle Manager 共享,同时不同的节点因为装备了不同核数 CPU 与内存,对应的 JVM 配置和其余线程池配置也不同,进一步加大了运维老本。
另外,因为 Druid 的数据模型分为 Primary timestamp、Dimensions、Metrics,而 Metrics 列只能在启用 Druid 的 Rollup 时才会存在,而 Rollup 意味着写入时聚合且数据会有肯定水平的失落。这种状况下,想把每行数据都原原本本地记录下来,只能把数据全都记录在 Dimensions 列,不应用 Metrics,而这也会影响数据压缩以及某些场景的聚合查问性能。
此外还有一些问题如 Druid 的 SQL 编译性能问题、原生查问简单的嵌套构造等在此便不再一一列举,总之基于上述问题咱们决定再次具体测试一下 TDengine。
二、与 Druid 的比照
导入雷同的两份数据到 Druid 和 TDengine 中,以下为在三节点(8c16g)环境下,100 万个传感设施、每个传感设施是 40 列(6 个字符串数据列、30 个 double 数据列以及 4 个字符串 tag 列),总计 5.5 亿条记录的后果。这里要留神一点,因为数据很多为随机生成,数据压缩率个别会比真实情况要差。
- 资源比照:
- 响应工夫比照:
1. 随机单设施原始数据查问
1) 查问后果集 100 条
2) 反复 1000 次查问,每次查问设施随机指定
3) 查问工夫区间别离为:1 天、7 天、1 月,
4) 统计查问耗时的最大值、最小值、平均值
5) SELECT * FROM device_${random} LIMIT 100
2. 随机单设施聚合查问
1) 聚合计算某列的工夫距离的平均值
2) 反复 1000 次查问,每次查问设施随机指定
3) 查问工夫区间别离为:1 天、7 天、7 天、1 月,对应聚合工夫为 1 小时、1 小时、7 天,7 天。
4) 统计查问耗时的最大值、最小值、平均值
5) SELECT AVG(col_1) FROM device_${random} WHERE ts >= ${tStart} and ts < ${tEnd} INTERVAL(${timeslot})
3. 随机多设施聚合查问
1) 聚合计算某列的工夫距离的总和
2) 反复 1000 次查问,每次查问设施约 10000 个
3) 查问工夫区间别离为:1 天、7 天、7 天、1 月,对应聚合工夫为 1 小时、1 小时、7 天,7 天。
4) 统计查问耗时的最大值、最小值、平均值
5) 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 的区别便不再显著,能够说是各有优劣。
总之,TDengine 与 Druid 在物联网数据方面的比照,前者的性能、资源应用方面均有较大当先。再联合 TDengine 装置部署配置上的便利性(咱们会波及到一些私有化利用的部署场景,这点对咱们来说十分重要),及相较于 Apache 社区其所提供的更牢靠与及时的商业服务,咱们最终决定将传感数据迁徙到 TDengine 中。
三、迁徙后的零碎
- 建表与迁徙
因为咱们零碎内接入的设施品种十分多,所以一开始数据存储便以大宽表的形式存储:50 列 double 类型、20 列 binary 类型、10 列 bool 以及额定的几列通用列,同时还额定保护了一份记录了每列理论列名的映射表。这种存储模式在基于 Druid 的零碎中便曾经实现了,在 TDengine 中咱们也创立了同样构造的超级表,列名如: number_col1, number_col2, ..., number_col50, str_col1, str_col2, ..., str_col10
。
在本来的数据写入服务中,会将 {"foo": 100, "bar": "foo"}
转换成 {"number_col1": 100, "str_col1": "foo"}
,同时记录一份 [foo=> number_col1, bar=>str_col1]
的映射关系(每一型号的设施共用雷同的映射),而后将解决后的数据写入 Kafka 集群中。
当初要将数据写入到 TDengine 中,也只须要基于本来要写入 Kafka 的数据来生成对应的 insert SQL,再通过 TAOSC 写入 TDengine 即可,且在数据查问时也会主动从映射关系中读取对应的实在列名返回给调用方。这样对下层利用来说,输入输出的数据保障了对立且无需变动,同时即使咱们零碎频繁的减少新的设施类型,基本上也不再须要手动创立新的超级表。
基于 TDengine 后的零碎架构
当然期间也遇到了一些小问题,次要就是在依据设施建表时,某些前缀加设施惟一标识形成表名,但设施惟一标识外面可能会蕴含减号”-“这种特殊字符。对于过后的 TDengine 版本来说,这种非凡或保留字符是无奈作为表名或列名的,所以额定解决了一下。此外列名无奈辨别大小写也使得咱们本来“fooBar”这种驼峰形式的命名须要批改成“foo_bar”这种下划线分隔。不过 TDengine 2.3.0.0 之后反对了转义字符“`”后,这些问题就都失去了解决。
- 迁徙后成果
迁徙后,TDengine 为咱们零碎里的各式各样的传感器提供了对立的数据存储服务,通过两头数据层的封装,咱们下层的业务根本无需批改便能够顺利地迁徙过去。相比于 Druid 须要部署各种各样的 Server,TDengine 仅须要部署 DNode 即可,也不再须要部署 PG、ZK、Ceph 等内部依赖。
迁徙后的利用接口响应工夫 P99 也从 560 毫秒左右升高到 130 毫秒(波及屡次外部 RPC 调用与 DB 的查问并不单纯示意 TDengine 查问响应工夫):
某历史数据查问接口响应工夫
某数据聚合接口响应工夫
对于开发人员来说,TDengine 让咱们不须要再破费太多工夫与精力去钻研查问怎么样更高效(只有不间接应用数据列做过滤条件并指定正当的查问时间段后,大部分查问都能失去称心的响应工夫),能够更多地聚焦于业务性能实现上。同时咱们的运维同学们也得以从 Druid 的各个简单模块中解脱进去,在操作任何中间件时都不须要再对 Druid 的状况进行确认。
且值得一提的是,在理论业务环境中,以下面形容的形式创立多列的超级表,尽管会存在大量的空列,但得益于 TDengine 的优化,能达到恐怖的 0.01 的压缩率,简略计算下来大概须要 3.67GB 每亿条。另外一张超级表(约 25 列数据列)针对传感器数据进行独自建模(不会存在空列的状况),压缩率也有 0.2,计算一下空间应用约合 3.8GB 每亿条。 这样看来应用宽表这种存储形式联合 TDengine 的弱小压缩能力也不会带来很多额定的硬件老本开销,但却能显著的升高咱们的保护老本。
四、将来布局
目前咱们基于 TDengine 次要还是存储传感器设施上传的数据,后续也打算将基于传感数据分析出的事件数据迁徙过去,甚至还打算将 AI 辨认算法剖析出的结构化数据也存储到 TDengine 中。 总之,经验了此次单干,咱们会把 TDengine 作为数据中台里重要的一种存储引擎应用,而非简略地存储传感器数据。将来,置信在涛思同学们的反对下,咱们能为客户提供更加优质的服务,打造物联网与人工智能利用的数字标杆。
作者简介
段雪林,北京升哲高级后端开发工程师,次要负责升哲灵思物联网中台的设计开发工作。
想理解更多 TDengine 的具体细节,欢送大家在 GitHub 上查看相干源代码。