整理 | 尔悦
嘉宾 | 廖浩均
小 T 导读:近年来,随着各种新兴技术的发展,物联网、工业互联网等行业获得了快速发展,由此产生的时序数据量也越来越庞大,通用大数据方案越来越难以为继,各种时序数据库产品应运而生,迁移也成为企业面临的重难点操作之一。
相比于 TDengine,OpenTSDB 算是入场更早一些的“玩家”,基于 Hbase 的产品模式有利也有弊:为有 Hbase 基础服务的企业降低了门槛的同时,过度依赖 Hbase 也为性能、压缩效果加设了一个瓶颈。随着企业业务规模的不断扩大,监控系统的部署成本和运行效率都会呈现增长的态势,随着时间的推移,OpenTSDB 的缺陷所带来的负面效应将完全超越正面效果,顺丰科技的业务案例便印证了这一点。
此前,顺丰科技使用 OpenTSDB 作为全量监控数据存储方案,不仅运维和使用成本很高,性能上也越来越难以满足需求——日常大跨度和高频次查询已经无法满足当前业务发展的需求,查询返回结果甚至需要十几秒。随着用户量的增加,支持较低 QPS 的 OpenTSDB 非常容易崩溃,一旦崩溃将导致整个服务都变为不可用状态。随后在调研过市面的时序数据库产品后,顺丰科技便决定向 TDengine 进行迁移。
为了帮助和顺丰科技一样有迁移需求的企业,我们举办了 TDengine 技术公开课,本文基于涛思数据联合创始人廖浩均主讲的《TDengine 技术内幕分享——兼容 OpenTSDB》整理,从具体的操作层面为大家答疑解惑。
一、从 OpenTSDB 迁移到 TDengine 的注意事项
在做出迁移决定之前,一些同学可能比较关注的是 TDengine 对 OpenTSDB 的兼容度如何?这个问题要从两个维度来看,一是写入协议的全兼容,二是查询功能的全覆盖。前者来看,你无需改变任何一行代码就可以将数据完全写入到 TDengine 之中,非常简单方便。后者的话,我们提供了 OpenTSDB 查询功能的完全覆盖,但是有两点需要注意,一是不提供 OpenTSDB 的查询语法支持,二是不提供等效元数据查询支持。
之所以不提供 OpenTSDB 的语法支持,原因总结起来主要在于三点:
首先,围绕上图 OpenTSDB 的一个查询表达式来看,整体语义相对比较复杂,且查询语法表意能力弱,无法使用其映射出完整的查询能力。同时这套语法规则也不太符合使用 SQL 的习惯,无法满足一些非常复杂的应用查询,比如对于下方的 SQL 语句的查询:这是一个 from 词句的非相关查询,通过 Json 这种方式是没有办法表达出来的。
SELECT id, SUM(avg_conn)
FROM
(
SELECT AVG(connection) as avg_conn, id
FROM t1 INTERVAL(10s)
GROUP BY id
)
GROUP BY id
其次,相比于 OpenTSDB,TDengine 提供的查询功能、聚合函数更加丰富,还提供如 TWA、TRATE、LEASTSQUARES、TOP、BOTTOM、LAST_ROW 等,如果要匹配其查询语法的话,就不能够完整地去使用 TDengine 所提供的所有查询功能了。第三,OpenTSDB 还在查询过程中提供了一种特别的插值机制,导致 TDengine 很难提供完全一致的查询结果。
除了语法层面不兼容外,在数据查询功能上,TDengine 对于元数据查询功能和部分其他的功能也并不提供支持,具体原因如下:
- OpenTSDB 使用接口/api/stats 来监控集群的服务状态。而 TDengine 也有一套针对自身集群状态的监控机制,其与 OpenTSDB 的集群服务状态监测并不相同。
- /api/tree 是 OpenTSDB 特有的一个时间线/时间序列(time series)的层级化组织结构。TDengine 则采用了 “数据库→超级表→子表” 的层级来组织和维护时间线:即归属于同一个超级表的所有时间线、所有子表在系统内部物理上归属于同一个层级,即不同的子表之间并没有层级或者从属关系。如果你确实需要 OpenTSDB 的这种场景,可以通过标签(tags)的逻辑设计,来建立不同子表之间的逻辑层级关系。
除了上述的两点外,还有一个有区别的地方是 OpenTSDB 提供的 “Rollup And PreAggregates” 的特殊功能,简单来讲就是一个自动化的数据降采样机制,将写入系统的数据按照预设的时间窗口自动进行聚合,并将结果重新写入系统中,该新写入结果对用户可见。这个机制的提出是为了解决原始数据查询性能问题,帮助用户降低查询处理开销和响应时间。
利用这个功能,用户可以将应用中相当比例的 standing query (为了周期性获得上报信息或监控面板刷新展示而发出的采样或聚合查询)转向查询降采样后的结果,因而能够获得极大的性能提升。因为其将查询转化为数据读取操作。但是一个显而易见的问题是,这种策略对应用具有侵入性,在某些场景下,比如说降采样结果并不满足查询要求的时候,又会要求应用去读取原始数据,再按照需求发出查询请求得到结果。
由此可见,“Rollup And PreAggregates”的缺点是对应用不透明,使应用处理逻辑变得极为复杂且缺乏移植性,是时序数据库无法提供高性能聚合查询情况下的妥协与折中,TDengine 在系统功能层面暂不支持这种自动的降采样。
那 TDengine 的查询性能优化策略又是如何实现的?
TDengine 拥有内嵌、对用户(应用)透明的块级别预计算模块(SMA),能够提供很高性能的查询响应,而且让应用查询处理逻辑更加简单。
上图为预计算的一个流程,红色和绿色为两个时间线,数据写入时是两个时间线混在一起进行的,进入到系统之后同一个时间线的数据被归属到了一起,每一个数据块在落盘时都会经过这样一个预计算的过程,这个过程是由落盘的线程负责驱动执行的。预计算的类型很简单,如图所示共有 count/max/min/sum 这四种,每一个 block 前面都有一个小的预计算模块,这个结构对用户和应用来说是透明的。
当我们要发出一个查询时,举个例子,查询时间区间从第 3 秒到第 13 秒,即上图中红线框住的部分,因为预计算本身是针对数据块级别来建立的,所以在读取预计算结果时,只有中间两块完整的数据块是有意义的,这里只会读取预计算的块,而两侧的红框则需要读取真实的数据,计算引擎才会相应计算出一个结果。
但是真实数据相对于预计算部分来说规模非常大,如果一个数据块中有 4000 条记录,在读取真实数据时要把这 4000 条都读出来,而如果有预计算可以使用的话,可能就只需要一个数字,比如说上图中的 Max,这种方式可以让 I/O 性能获得极大的提升,从而可以有效降低查询过程中 I/O 压力。
TDengine 以这种内嵌支持机制保障高效的查询处理性能,而非聚焦在应用层、将复杂性留给用户。 总而言之,从 OpenTSDB 迁移到 TDengine,你只需调整应用的读取部分代码和实现逻辑,就能够获得立竿见影的效果和收益,不仅能够使用更多更高效的查询与计算功能、还能获得更快的响应时间和更低的存储资源开销,极大地降低软硬件部署成本。
二、你需要了解的两种写入方式
前文中提到,无需改变任何一行代码就可以将写入 OpenTSDB 的过程调整为写入到 TDengine 之中,那这一点是如何做到的呢?这里有两种方式:一种是高级语言直接使用跨语言方案调用 C 接口写入,一种是直接调用 HTTP 接口采用 RESTful 方式写入。
如上图所示,如果你的应用是使用 C#、Java、Rust、Python 等高级语言编写并写入数据,那你需要通过 TDengine 提供的库先进行下重新编译,再通过高级语言的连接器(connector)连接 TDengine 的驱动(driver),将承载的逻辑封装到 driver 中,接下来通过一个名为 taos_schemaless_insert
的 C 语言接口处理所有的 OpenTSDB 的写入协议,采用结构化的方式将数据直接写入 TDengine 中。
看到这里可能有一些同学会有疑问,为什么逻辑要封装到 driver 中,而不是放到高级语言中? 原因很简单,所有的高级语言都是直接跨语言调用接口,TDengine 并不提供各种高级语言原生的接口,如果在高级语言层面实现了 OpenTSDB 到 TDengine 语法的兼容解析以及转换写入的话,那么针对不同的高级语言都需实现如此复杂的一套逻辑。而且考虑到在生产环境下的具体应用场景,多线程的环境使问题变得更加复杂,由此会产生很多边界情况需要处理。
因此我们决定把这个逻辑下沉到 C 语言端,让所有的高级语言直接去驱动 C 语言的接口来实现调用,如此一来不仅可以极大地减轻高级语言连接器的复杂程度,更有利于简化整个架构,从而加快演进的速度。
以上就是高级语言采用本地化接口方式写入数据的实现原理。如果你决定要采用 RESTful 的方式,那就更简单了,你只需要改一下配置文件、端口和 IP 地址,同时部署一个名为“taosAdapter”的组件。
taosAdapter 是我们最近开源的、用 Go 语言开发的一套 HTTP 服务,如上图所示,从用户侧角度来讲,当你写入各种 OpenTSDB 协议之后,可以直接 post 到 taosAdapter 开放的端口,taosAdapter 再利用底层的 driver 连接 TDengine,直接完成数据写入的操作过程,无需用户再做任何操作。
三、使用 taosadapter 进行数据写入的具体实现
用户可以通过 JDBC-RESTful 包来使用 taosAdapter 提供的服务, 也可以直接使用其提供的 HTTP 接口。具体的结构如上图所示,最底层是驱动层,依次向上是 TDengine 的 go connector、connection pool、HTTP 模块。
taosAdapter 有以下的技术特点:
- 可与 TDengine 分离部署
在介绍这个技术特点前,大家要思考一个问题。我们都知道 TDengine 本身是提供 HTTP 服务的,那为什么我们又要开发出 taosAdapter 这样一个组件来接收 OpenTSDB 的数据?
首先,在数据写入过程中,耗费 CPU 资源最多的是数据的解析与转换(SQL → 二进制数据)操作,当然你可以使用动态绑定在一定程度上降低 CPU 的消耗,但是这个程度非常有限。当你使用 Native 的方式写入时,SQL 的解析以及二进制的转换操作在客户端上就完成了,以 HTTP 的方式去写入则是将一个 SQL 语句直接 post 到服务器里去。为了降低服务器的负担,我们想到把这个操作从 TDengine 服务中分离出来。
这样一来,不仅可以有效地降低服务器的负载压力,还可以按需部署任意多台 taosAdapter 去写入数据 —— 比如你部署了两台 TDengine,采用内嵌方式的话实际上也只能提供两个 HTTP 服务,但是却能够部署 4 台乃至 5 台 taosAdapter 进行数据写入。结合实际的写入负载,可以不受限于 TDengine 集群节点的数量,弹性地调整 taosAdapter 部署的节点数量,以最大程度满足写入的实际需求。
同时,由于 taosAdapter 本身是一个无状态的协议转换系统,它是把 HTTP 的服务转换成 TDengine 内部的一个交换协议,并把数据写入到系统内部的,这种无状态的操作使得它的 scale out 非常方便,能够高效率地进行 taosAdapter 集群弹性部署。 此外,还能极大地降低 TDengine 服务器本身的负载,节约更多的服务资源来支撑更大规模的查询处理。
- 支持 OpenTSDB 写入协议
采用 HTTP 方式写入,对于写入应用来说是完全透明的,你甚至不需要去做重新编译之类的任何操作,只需要调整一下写入应用的 IP:PORT ,就可以将数据无缝写入到 TDengine。这里提到的 IP:PORT 是 taosAdapter 的服务端口,而非 TDengine 的,TDengine 内嵌的 HTTP 服务不支持 OpenTSDB 协议, 这是很重要的一点,在此跟大家说明一下。
- 支持 OpenTSDB 数据分库并行写
众所周知,OpenTSDB 写入过程是没有数据库选择概念的,TDengine 需要将数据写入到一个 database 里,这就延伸出一个问题——应该往哪个库里写,我们提出了一个解决方案,通过 taosAdapter 支持 OpenTSDB 数据分库并行写。
具体的操作如上面两张图所示。通过端口映射的方式,taosAdapter 让不同的应用将数据写入到不同的数据库。在 taosAdapter 的配置文件中可以配置多个端口,每个端口映射到不同的 TDengine 数据库,可以将应用的数据自动写入到不同的数据库中,匹配上读写权限之后就可以有效控制不同的系统数据了。
上述的操作中,数据库需要由管理员预先手动建立(对写入应用透明),同时去调整配置文件,这个过程完成以后就能启动起来了,此时你的应用只需调整设置到正确的 IP 端口,数据就可以写入到正确的 DB 中了。
上图为分库并行写的详细流程,首先在 taosAdapter 中建立好端口到 DB 的映射配置表:6046:db1、6047:db2 。以 6046 为例,系统先将通过端口 6046 拿到的数据直接映射到 DB1,之后 taosAdapter 会设置与 TDengine 的连接,将其所连接的数据库切换到 DB1 数据库,继而进行数据写入,写入完成之后会对当前连接关联的 DB 信息清除,即把此连接恢复成没有指定任何 DB 的状态。写入 6047 时是同样的原理,只不过数据是自动写入到 DB2,这就是分库并行写的具体实现。
以上就是对 taosAdapter 几个特点的分析。接下来我们看一下 TDengine 的无模式写入与普通 SQL 语句写入性能的差异。
下图是我们做的一个简单对比,在 1000 万数据写入情况下,taosAdapter 的写入性能大概是普通 SQL 写入性能的 74.97%,也就是说如果你用普通 SQL 写入时能达到每秒 100 万的写入速度,替换成 taosAdapter 大概能够达到每秒 75 万这个级别。有这样一个对比的话,大家就能够评估大概部署哪种规模的系统来支撑自身业务吞吐量了。
下面这张图展示的是在写入过程中 taosAdapter 的负载情况,黄色线条是 taosd,绿色线条是 taosAdapter。可以看到,taosAdapter 的 CPU 消耗比较高,这主要是由于在做字符串相关的一些数值格式的转换变换所导致的。
四、写在最后
taosAdapter 当前支持的协议和数据写入格式也很丰富,不仅支持 OpenTSDB 的 Json 和 telnet 协议,同时还支持 InfluxDB v1、statsd、collectd、tcollect、Icinga2、node exporter。 后面我们会继续探索 taosAdapter 对于其他的 exporter 的支持。
此外,未来我们还会进一步优化 taosAdapter 的性能,目前 taosAdapter 对 CPU 的消耗还较高,后面将在可行的范围内优化架构设计及性能:首先,通过内部的部分同步接口升级为异步接口调用,以提升服务的性能,支持更高性能处理;其次,进一步优化它的处理逻辑,降低写入流程中的 CPU 开销。