TDengine在得物的落地应用

2022-03-21 00:00:00 查询 数据 集群 数据库 写入

背景

作为一家互联网电商公司,得物有许多系统和场景都需要做流量的监控和防护,所以在深度定制化开源流控防护组件Sentinel时我们加入了许多功能,帮助提升各业务系统的流控防护。

在开发过程中,我们发现开源版本的Sentinel不支持流控数据持久化,而我们非常需要这样的功能:我们需要一款数据库,它能够承载大量的流量监控数据,并能对数据进行存储和高效查询。

目前在生产环境中,我们有数百个业务系统、数千台服务器接入了Sentinel,如此产生的流控数据无疑非常庞大。那么对于这个需求来说,选择一款适合的数据库无疑极为重要,一个好的选择能够达到事半功倍的效果。


数据库选型

首先粗略估算一下当前数据量的理论上限:目前生产环境拥有数千Sentinel Resources。而Sentinel的监控数据时间粒度按照秒来统计,那么一天理论上就能够产生数亿的数据,理论写入数据的速度也将达到万TPS,而且业务还在快速发展,可以预见的是数据量将会进一步爆炸,显而易见这个数据量级是无法使用传统关系数据库的。

因为内部有一些应用在使用TiDB,所以首先看了一下使用TiDB的可行性,但很快就放弃了,毕竟它作为一款分布式数据库,瞄准的完全不是监控数据这种时序特点非常强的场景。排除之后我们就将调研重心放在了时序数据库上。

主流时序数据库里面都各有优缺点:

  • InfluxDB,可能是应用范围广的时序数据库,场景也合适。但集群功能需要商业版。
  • OpenTSDB,基于HBase,对于目前简单的需求来说太重了。
  • Cassandra,从找到的几份对比报告来看性能不太满足要求。

当准备继续了解Clickhouse时,被同事安利了一款国产物联网大数据平台——TDengine。简单在网上了解了一下,发现风评不错,社区活跃度也高,后来就到官网上查阅了TDengine和其它数据库的对比报告,发现从性能上看也非常。于是我们就写了一个demo,简单使用了一下TDengine,整个过程中,在清晰的文档的帮助之下,学习成本也还好,所以我们终决定使用TDengine。

数据结构与建模方式

数据结构


首先我们看一下Sentinel的流量数据是如何呈现的,从上图中可以看出,左侧是应用列表,在每个应用的菜单中有独立的监控面板,在监控面板中又以资源的粒度,统计了所有资源的流量数据,例如通过QPS、拒绝QPS、响应时间等。所以从前端呈现的角度来看,数据的键应当是应用-资源。

然后我们在从内部实现角度看看数据的结构是怎么样的。Sentinel客户端在每台服务器上统计了所有资源的流量数据,以秒的维度进行聚合,再记录到本地日志中。控制台通过调用客户端暴露的接口,获取采集的流量数据,再以服务的维度,将所有单机的流量数据进行聚合,存储在内存中。所以我们需要存储的数据即是以应用-资源为属性落入数据库中的。


数据建模

TDengine官方文档中建议的数据建模方式如下:

为充分利用其数据的时序性和其他数据特点,TDengine要求对每个数据采集点单独建表。 采用一个数据采集点一张表的方式,能大程度的保证单个数据采集点的插入和查询的性能是优的。 在TDengine的设计里,表用来代表一个具体的数据采集点,超级表用来代表一组相同类型的数据采集点集合。当为某个具体数据采集点创建表时,用户使用超级表的定义做模板,同时指定该具体采集点(表)的标签值。与传统的关系型数据库相比,表(一个数据采集点)是带有静态标签的,而且这些标签可以事后增加、删除、修改。一张超级表包含有多张表,这些表具有相同的时序数据schema,但带有不同的标签值

可以看到官方文档中建议的数据建模方式完全契合本场景的数据特点:一个应用-资源即为一张表,所有的应用-资源放在一张超级表中,以便做聚合查询。所以在表结构的设计上,就使用了官方文档推荐的这种方式。

另外,在标签的选择上,虽然目前还没有聚合操作的需求,但是考虑到未来的聚合操作非常可能会以应用的维度来做,所以我们决定将一些应用的信息作为标签记录在表中。

整体架构

目前的整体架构图如上,各个接入了Sentinel的业务系统会向控制台定时发送心跳请求维持本机器的健康状态。而控制台会定时轮询所有机器,拉取Sentinel客户端在业务系统中记录的监控数据,经过聚合处理后再向TDengine集群批量写入。

由于场景简单且并非作为主要的监控系统,数据可以接受少量丢失,故没有设计过多的失败处理机制。

技术选型

Connector

在Connector选择上,公司的主要开发语言就是Java,相关生态也都更完善,所以很自然地选择了JDBC形式的Connector。另外JDBC的性能相较于HTTP方式也会更好一些,同时JDBC驱动还支持节点不可用时自动切换节点。不方便的是JDBC的方式则会强依赖本地库函数,需要在客户端的机器上也安装TDengine,这样在项目部署阶段会稍微麻烦一些。不过整体来说是利大于弊的。

近,官方更新了JDBC-RESTful的方式,支持了跨平台功能。由于公司服务器的操作系统都是Linux,没有跨平台的需求,所以还是继续使用JDBC-JNI的Connector。

注:图来自TDengine官网

数据库连接池与ORM

数据库连接池及ORM框架也选择了在公司内部主流的Druid+Mybatis,根据官网的Demo代码也能高效地完成接入。不过在Mybatis的使用上,只是在查询时使用了Mybatis,将ResultSet转为更方便处理的实体,而写入数据时则没有使用Mybatis,为了方便所以直接在内存中拼接好sql后进行执行。

总体来说,TDengine在适配主流框架方面很友好了,支持了HikariCPDruidSpring JdbcTemplateMybatis等,并且根据官网提供的Demo能够很快地实现接入,节省了大量时间,一些注意事项文档中都有清晰列出。

集群搭建

目前公司内TDengine集群有三个物理节点,均为16核/64G内存/1T存储。官方的集群搭建文档写的还是非常详尽的,直接按照文档进行傻瓜式操作就可以搭建起TDengine集群。

建库

在前期调研时发现,假定集群只有三台机器,如果数据量过大,副本数为3,相当于在每台机器上都存储了一份完整数据,以可能的数据量推测,存储和内存的压力都会较大,所以在建库时副本数选择设置为1。后续若集群规模扩大,TDengine也支持动态修改副本数,可以很轻松地完成到高可用集群的切换。

另外考虑到查询性能,将blocks设置为16,cache设置为64MB,如下所示:

CREATE DATABASE sentinel KEEP 365 DAYS 1 blocks 16 cache 64; 

相关文章