支持 10 亿个设备、100 台服务器节点,TDengine 3.0 架构详解

在 8 月 13 日的 TDengine 开发者大会上,TDengine 分布式系统架构师关胜亮带来题为《支持 10 亿时间线、100 个节点的 TDengine 分布式系统架构设计》的主题演讲,详细阐述了 TDengine 3.0 新的架构设计思路。本文根据此演讲整理而成。

点击【这里】查看完整演讲视频

TDengine Database

在物联网大数据时代,随着企业业务的不断扩张,需要采集的设备数量急剧上升,通常要达到百万甚至千万的级别,采集频率也从以前的 15 分钟一个点逐渐演变到现在的 1 分钟、30 秒、15 秒,甚至 1 秒的一个点,这么多数据还要源源不断地发往云端,如何进行更好地写入、存储及查询,成为企业亟待解决的难题。

在测点数暴涨、数据采集频次不断提高的大数据时代,传统实时数据库暴露出下列问题:

  • 没有水平扩展能力,如果数据量增加,只能依靠硬件 scale up
  • 技术架构陈旧,使用磁盘阵列,大多运行在 Windows 环境下
  • 数据分析能力偏弱,不支持现在流行的各种大数据分析接口
  • 不支持云端部署,更无法支持 PaaS

在此背景下,依靠 Hadoop 架构形成的一系列通用数据存储解决方案,成为一众企业的首选,为了能够适配更多的业务场景,还要集成如 Kafka、Redis、Spark 等众多第三方开源软件,在一次次的实践中,也暴露出诸多问题,如开发效率低、运行效率差、运维复杂以及应用推向市场慢等。

在此基础上,时序数据库(Time Series Database)应运而生了。虽然新兴的时序数据库之间竞争激烈,但因为业务场景的复杂性和数据的多样性,还没有哪一款产品真正成为物联网场景的领跑者,而大部分产品还都存在以下三点难以忽视的问题:

  • 功能单一,需要集成其他软件,提升系统负重及运维难度
  • 不是标准 SQL,学习成本高
  • 没有实现真正的云原生化,水平扩展能力有限

在调研了数百个业务场景的基础上,TDengine 完成了 3.0 版本的迭代,集群支持 10 亿条以上的时间线、100 台服务器节点,成为一款真正的云原生时序数据库,具有极强的弹性伸缩能力,且无需再集成 Kafka、Redis、Spark、Flink 等软件,可以大幅降低系统架构的复杂度。

TDengine 分布式系统架构设计

TDengine Database

如上图所示,在数据节点(Dnode)、虚拟节点(Vnode)、管理节点(Mnode)之上,TDengine 3.0 集群新增了弹性计算节点(Qnode)和流计算节点(Snode)。其中 Qnode 主要在运行查询计算任务中起作用,当一个查询执行时,依赖执行计划,调度器会安排一个或多个 Qnode;Snode 主要负责运行流计算任务,可以同时执行多个。

TDengine Database

针对数据库级别的元数据管理,3.0 版本通过两阶段提交来保证关键操作的一致性,以上图为例,假设我们要创建一张超级表,系统首先会把 SQL 语句先解析成请求发给 Mnode,之后 Mnode 会把这个请求发给各个不同的 Vnode( Vnode1 和 Vnode2),最后我们会等这些请求都结束之后再做一个持久化,然后把它返回给客户。

TDengine Database

但在实际操作中,数据库级别的元数据,量是比较少的,更多的还是数据表级别的元数据管理。在以前的版本中,元数据都是集中管理,而 TDengine 3.0 会把元数据都放到 Vnode 里,在放弃中心节点之后,水平扩展能力显著提升。这样一来,当应用要将数据插入到一张表或对一张表做查询操作时,我们就可以基于表名的特定 hash 值,将请求直接发送到对应的 Vnode 里,性能可以得到进一步提升。

数据表中大量标签数据的处理也是一个重点,比如说一个设备的标签有 1K,那 100 万个设备合在一起的话可能就会产生 1GB 的元数据,如果把它们都放在内存里,启动速度自然就会变得比较慢。

为了解决这个问题,新的版本采用了 LRU 机制,只有使用比较频繁的数据才会被放在内存中。在全内存的情况下,对于千万级别规模的标签数据 TDengine 能做到毫秒级的返回,那在内存资源不足的情况下,我们仍然能够支持数千万张表快速的查询。同时 TDengine 还提供了 TTL 机制,方便对一些只需要存储一个月或半年的数据,进行删除过期数据表的操作。

凭借着 LRU 机制,TDengine 的每一个 Vnode 都能支持十万乃至百万以上数据表的管理。不同于 Prometheus,TDengine 的每个数据表可以有很多个字段,这样的话假设说一个表有十个字段,我们就可以认为每个 Vnode 可以支持百万的时间线。

此外,数据表路由的信息也是非常重要的。此前 TDengine 每一条数据表的路由信息都需要在 Mnode 做登记,再把登记的信息转给 Vnode,我们想要做的事务频率会越来越多,那可能就会导致每一个系统的 warmup 时间越来越长。而且在刚开始部署一套系统时,就需要先把这些表都创建好,压力非常大。

TDengine Database

为了解决这个问题,TDengine 3.0 做了一个特殊的创新,即数据表的路由采用一致性 hash 算法,具体来说就是把数据表的名称压缩到 2 的 32 次方长度的一个环形空间,在创建 Database 时就创建指定数目的 Vnode,系统就会按照等分的方法,把它拆解到这一环形空间中,每一个 Vnode 就负责其中某一个 hash 范围的数据表,并由 Mnode 来负责 hash 范围的管理。

在系统缩容、扩容时还可以通过动态调整 hash 范围来实现,比如上面这个环形,我们想把 Vnode 数量从 5 个变成 6 个,只需要调整其中某一个 Vnode 的 hash 范围就可以做到了,这样一来,TDengine 就能够支持在 100 个数据节点上创建 Vnode 了。

大家可能会有一个疑问,如果用 hash 会不会产生比较严重的数据倾斜,如果你只有一百个设备或者一千个设备,这种倾斜可能是比较严重的,这时你可以创建数目较少的 vgroup,最少可以设置一个,一个 vgroup 包含 1-1000 个数据表时,性能差别不大。当你达到一万个设备时,这种倾斜我们基本都看不出来了,数据是非常均匀分布的状态。

TDengine 支持特定查询场景的性能调优

所有的查询性能也好,写入性能也好,本质上都是为了降低对硬盘的读写次数,如果存储量降下来了,那读取速度自然就可以获得更大的提升。一般来讲,一个硬盘的写入速度大概在 70MB/s 左右,读取速度可能在 100MB/s,数据库没有办法突破这样一个物理上的瓶颈,那我们想要做到亿级数据或者十亿级数据这样一个秒级的查询计算,就必须想尽各种各样的方法对数据进行降采样,降低它的存储量。

从这一思路出发,在 TDengine 3.0 中,我们也做了很多查询性能上的优化,具体体现在如下的三个功能上:

1. Block-Wise SMA(写入过程中,保存数据块的预计算结果)

TDengine Database
  • 内存数据积累到一定大小触发写磁盘
  • 由行存储转化为列存储
  • 在磁盘中以数据块 Block 形式组织
  • 把 Block 中各列数据的聚合结果,例如 max、min、sum、count 等存储到 sma 文件
  • 根据查询计划选择使用哪些聚合结果

2. Rollup SMA(原始数据自动进行降采样存储)

TDengine Database
  • 支持三个不同的数据保存层级,指定每层数据的聚合周期和保存时长
  • level 1 存储原始数据,可以在 level 1 中存储一条或者多条数据,level 2 和 3 存储聚合数据
  • Rollup 目前支持 avg, sum, min, max, last, first
  • 适用于 DevOps 等关注数据趋势的场景,本质是降低存储开销,加速查询,同时抛弃原始数据

3. Time-Range-Wise SMA(按照聚合结果降采样存储)

TDengine Database
  • 适用于高频使用 interval 的查询场景
  • 采用与普通流计算一样的逻辑,允许用户通过设定 watermark 应对延时数据,相对应的实际的查询结果也会有一定的延迟

标签索引

我们在 3.0 里也做了很多标签索引的优化,新实现了一个 TDB 模块,特征就是把标签数据进行了 LRU 的存储,这在前面也提到过。TDB 的查询适用于标签变化不频繁或者说是有结构化标签的设备,例如文本型和数值类型。

对于 JSON 类型的标签,特点是标签量很大但是数据内容很小,TDengine 采用的就是 FST 的索引方式。索引的重建一直是 FST 里面比较复杂的部分,TDengine 采用异步方式重建索引数据,因而不会让用户在创建表或者修改表时等待太长的时间。

对于数据缓存,虽然 TDengine 2.0 也可以缓存最新的数据,但内存的消耗量会比较大,而在 3.0 中用基础的 LRU 库就可以把最近的数据放在内存里面,让用户即便用很小的内存,也可以查到最近使用比较频繁的数据。

计算资源弹性扩充

TDengine Database

如上图所示,当客户端要进行明细数据查询时,需要先从磁盘里或者内存里源源不断地把原始数据读出来,这时我们肯定不希望这个数据要在不同节点里进行大量的数据交换,增加网络或磁盘开销,从这一点出发,TDengine 将明细数据查询放在了 Vnode 中进行。

但在做一些聚合查询(数据的计算、合并、插值等)时,比如说在车联网场景下要观察所有车辆每一天发动多长时间,这一类查询都是在 Qnode 中进行,根据不同场景大家可以选择配置不同数量的 Qnode。 

在时序场景下降低对 Kafka、Flink、Spark 的依赖

TDengine Database

在时序场景下,TDengine 降低了对 Kafka 的依赖,我们的 Vnode 可以允许不同的消费者同时消费数据,所有的数据都是结构化的数据,用户可以只订阅自己关注的这部分数据,比如说我只想关注电流里面超限的数据,使用 TDengine 进行订阅时的数据传输总量非常小,但用 Kafka 进行数据订阅时很可能需要从服务器拉取全部的数据,然后在客户端中进行数据筛选,这时两者的性能就完全不在一个量级上了。

此外,如果是常用的一些计算,还可以把它们放到 Snode 里,Snode 会将流计算的结果放到单独 Vnode 里面去做存储,有了这样一个流计算的引擎,就大大降低了对 Flink 和 Spark 的依赖。

结语

TDengine Database

TDengine 3.0 的架构改进,从关键特性上来看,除了水平扩展性之外,还增强了弹性和韧性,可作为一个极简的时序数据处理平台,能够让大家以更低的成本完成时序数据平台的搭建。接下来我们还会陆续发布 TDCloud 云服务版本、TDLite 嵌入式版本,提供边云协同软件包、数据预测软件包,进一步提升系统高可用、高可靠、计算能力。

大家可以马上下载体验 TDengine 3.0,也欢迎大家多提宝贵意见。