SENSORO基于TDengine助力基层政府打造数字化应用标杆
建立城市级传感器网络所涉及的传感器种类十分多样,由此产生的数据量也十分庞大, 如果只是使用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都会严重影响写入查询性能。如果你要从官方文档的配置页面从顶划到底,可能会把手指划抽筋。
为了节省存储成本,我们在部署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. 随机单设备原始数据查询
查询结果集100条 重复1000次查询,每次查询设备随机指定 查询时间区间分别为:1天、7天、1月, 统计查询耗时的大值、小值、平均值 SELECT * FROM device_${random} LIMIT 100
2. 随机单设备聚合查询
聚合计算某列的时间间隔的平均值 重复1000次查询,每次查询设备随机指定 查询时间区间分别为:1天、7天、7天、1月,对应聚合时间为1小时、1小时、7天,7天。 统计查询耗时的大值、小值、平均值 SELECT AVG(col_1) FROM device_${random} WHERE ts >= ${tStart} and ts < ${tEnd} INTERVAL(${timeslot})
3. 随机多设备聚合查询
聚合计算某列的时间间隔的总和 重复1000次查询,每次查询设备约10000个 查询时间区间分别为:1天、7天、7天、1月,对应聚合时间为1小时、1小时、7天,7天。 统计查询耗时的大值、小值、平均值 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中。
迁移后的系统
建表与迁移
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集群中。
迁移后效果
某数据聚合接口响应时间
未来规划
相关文章