整体架构
集群与基本逻辑单元
TDengine 的设计是基于单个硬件、软件系统不可靠,基于任何单台计算机都无法提供足够计算能力和存储能力处理海量数据的假设进行设计的。因此 TDengine 从研发的第一天起,就按照分布式高可靠架构进行设计,是支持水平扩展的,这样任何单台或多台服务器发生硬件故障或软件错误都不影响系统的可用性和可靠性。同时,通过节点虚拟化并辅以负载均衡技术,TDengine 能最高效率地利用异构集群中的计算和存储资源降低硬件投资。
主要逻辑单元
TDengine 分布式架构的逻辑结构图如下:
一个完整的 TDengine 系统是运行在一到多个物理节点上的,逻辑上,它包含数据节点(dnode)、TDengine 应用驱动(taosc)以及应用(app)。系统中存在一到多个数据节点,这些数据节点组成一个集群(cluster)。应用通过 taosc 的 API 与 TDengine 集群进行互动。下面对每个逻辑单元进行简要介绍。
物理节点(pnode ): pnode 是一独立运行、拥有自己的计算、存储和网络能力的计算机,可以是安装有 OS 的物理机、虚拟机或 Docker 容器。物理节点由其配置的 FQDN(Fully Qualified Domain Name)来标识。TDengine 完全依赖 FQDN 来进行网络通讯。
数据节点(dnode): dnode 是 TDengine 服务器侧执行代码 taosd 在物理节点上的一个运行实例。在一个 TDengine 系统中,至少需要一个 dnode 来确保系统的正常运行。每个 dnode 包含零到多个逻辑的虚拟节点(vnode),但管理节点、弹性计算节点和流计算节点各有 0 个或 1 个逻辑实例。
dnode 在 TDengine 集群中的唯一标识由其实例的 endpoint(EP)决定。endpoint 是由dnode 所在物理节点的 FQDN 和配置的网络端口组合而成。通过配置不同的端口,一个pnode(无论是物理机、虚拟机还是 Docker 容器)可以运行多个实例,即拥有多个 dnode。
虚拟节点(vnode): 为了更好地支持数据分片、负载均衡以及防止数据过热或倾斜,TDengine 引入了 vnode(虚拟节点)的概念。虚拟节点被虚拟化为多个独立的 vnode 实例(如上面架构图中的 V2、V3、V4 等),每个 vnode 都是一个相对独立的工作单元,负责存储和管理一部分时序数据。
每个 vnode 都拥有独立的运行线程、内存空间和持久化存储路径,确保数据的隔离性和高效访问。一个 vnode 可以包含多张表(即数据采集点),这些表在物理上分布在不 同的 vnode 上,以实现数据的均匀分布和负载均衡。
当在集群中创建一个新的数据库时,系统会自动为该数据库创建相应的 vnode。一个dnode 上能够创建的 vnode 数量取决于该 dnode 所在物理节点的硬件资源,如 CPU、内存和存储容量等。需要注意的是,一个 vnode 只能属于一个数据库,但一个数据库可以包含多 个 vnode。
除了存储时序数据以外,每个 vnode 还保存了其包含的表的 schema 信息和标签值等元数据。这些信息对于数据的查询和管理至关重要。
在集群内部,一个 vnode 由其所归属的 dnode 的 endpoint 和所属的 vgroup ID 唯一标识。管理节点负责创建和管理这些 vnode,确保它们能够正常运行并协同工作。
管理节点(mnode): mnode(管理节点)是 TDengine 集群中的核心逻辑单元,负责监控和维护所有 dnode的运行状态,并在节点之间实现负载均衡(如图 15-1 中的 M1、M2、M3 所示)。作为元数据(包括用户、数据库、超级表等)的存储和管理中心,mnode 也被称为 MetaNode。
为了提高集群的高可用性和可靠性,TDengine 集群允许有多个(最多不超过 3 个)mnode。这些 mnode 自动组成一个虚拟的 mnode 组,共同承担管理职责。mnode 支持多副本,并采用 Raft 一致性协议来确保数据的一致性和操作的可靠性。在 mnode 集群中,任何数据更新操作都必须在 leader 节点上执行。
mnode 集群的第 1 个节点在集群部署时自动创建,而其他节点的创建和删除则由用户通过 SQL 手动完成。每个 dnode 上最多有一个 mnode,并由其所归属的 dnode 的endpoint 唯一标识。
为了实现集群内部的信息共享和通信,每个 dnode 通过内部消息交互机制自动获取整个集群中所有 mnode 所在的 dnode 的 endpoint。
计算节点(qnode): qnode(计算节点)是 TDengine 集群中负责执行查询计算任务的虚拟逻辑单元,同时也处理基于系统表的 show 命令。为了提高查询性能和并行处理能力,集群中可以配置多个 qnode,这些 qnode 在整个集群范围内共享使用(如图 15-1 中的 Q1、Q2、Q3 所示)。
与 dnode 不同,qnode 并不与特定的数据库绑定,这意味着一个 qnode 可以同时处理来自多个数据库的查询任务。每个 dnode 上最多有一个 qnode,并由其所归属的 dnode 的endpoint 唯一标识。
当客户端发起查询请求时,首先与 mnode 交互以获取当前可用的 qnode 列表。如果在集群中没有可用的 qnode,计算任务将在 vnode 中执行。当执行查询时,调度器会根据执行计划分配一个或多个 qnode 来共同完成任务。qnode 能够从 vnode 获取所需的数据,并将计算结果发送给其他 qnode 进行进一步处理。
通过引入独立的 qnode,TDengine 实现了存储和计算的分离。
流计算节点(snode): snode(流计算节点)是 TDengine 集群中专门负责处理流计算任务的虚拟逻辑单元(如上架构图 中的 S1、S2、S3 所示)。为了满足实时数据处理的需求,集群中可以配置多个 snode,这些 snode 在整个集群范围内共享使用。
与 dnode 类似,snode 并不与特定的流绑定,这意味着一个 snode 可以同时处理多个流的计算任务。每个 dnode 上最多有一个 snode,并由其所归属的 dnode 的 endpoint 唯一标识。
当需要执行流计算任务时,mnode 会调度可用的 snode 来完成这些任务。如果在集群中没有可用的 snode,流计算任务将在 vnode 中执行。
通过将流计算任务集中在 snode 中处理,TDengine 实现了流计算与批量计算的分离,从而提高了系统对实时数据的处理能力。
虚拟节点组(VGroup):
vgroup(虚拟节点组)是由不同 dnode 上的 vnode 组成的一个逻辑单元。这些 vnode之间采用 Raft 一致性协议,确保集群的高可用性和高可靠性。在 vgroup 中,写操作只能在 leader vnode 上执行,而数据则以异步复制的方式同步到其他 follower vnode,从而在多个物理节点上保留数据副本。
vgroup 中的 vnode 数量决定了数据的副本数。要创 建一个副本数为 N 的数据库,集群必须至少包含 N 个 dnode。副本数可以在创建数据库时通过参数 replica 指定,默认值为 1。利用 TDengine 的多副本特性,企业可以摒弃昂贵的硬盘阵列等传统存储设备,依然实现数据的高可靠性。
vgroup 由 mnode 负责创建和管理,并为其分配一个集群唯一的 ID,即 vgroup ID。如果两个 vnode 的 vgroup ID 相同,则说明它们属于同一组,数据互为备份。值得注意的是,vgroup 中的 vnode 数量可以动态调整,但 vgroup ID 始终保持不变,即使 vgroup 被删除,其 ID 也不会被回收和重复利用。
通过这种设计,TDengine 在保证数据安全性的同时,实现了灵活的副本管理和动态扩展能力
Taosc
taosc(应用驱动)是 TDengine 为应用程序提供的驱动程序,负责处理应用程序与集群之间的接口交互。taosc 提供了 C/C++ 语言的原生接口,并被内嵌于 JDBC、C#、Python、Go、Node.js 等多种编程语言的连接库中,从而支持这些编程语言与数据库交互。
应用程序通过 taosc 而非直接连接集群中的 dnode 与整个集群进行通信。taosc 负责获取并缓存元数据,将写入、查询等请求转发到正确的 dnode,并在将结果返回给应用程序之前,执行最后一级的聚合、排序、过滤等操作。对于 JDBC、C/C++、C#、Python、Go、Node.js 等接口,taosc 是在应用程序所处的物理节点上运行的。
此外,taosc 还可以与 taosAdapter 交互,支持全分布式的 RESTful 接口。这种设计使得 TDengine 能够以统一的方式支持多种编程语言和接口,同时保持高性能和可扩展性。
节点之间的通信
通信方式:
TDengine 集群内部的各个 dnode 之间以及应用驱动程序与各个 dnode 之间的通信均通过 TCP 方式实现。这种通信方式确保了数据传输的稳定性和可靠性。
为了优化网络传输性能并保障数据安全,TDengine 会根据配置自动对传输的数据进行压缩和解压缩处理,以减少网络带宽的占用。同时,TDengine 还支持数字签名和认证机制,确保数据在传输过程中的完整性和机密性得到保障。
FQDN 配置:
在 TDengine 集群中,每个 dnode 可以拥有一个或多个 FQDN。为了指定 dnode 的 FQDN,可以在配置文件 taos.cfg 中使用 fqdn 参数进行配置。如果没有明确指定,dnode 将自动获取其所在计算机的 hostname 并作为默认的 FQDN。
虽然理论上可以将 taos.cfg 中的 FQDN 参数设置为 IP 地址,但官方并不推荐这种做法。因为 IP 地址可能会随着网络环境的变化而变化,这可能导致集群无法正常工作。当使用 FQDN 时,需要确保 DNS 服务能够正常工作,或者在节点和应用程序所在的节点上正确配置 hosts 文件,以便解析 FQDN 到对应的 IP 地址。此外,为了保持良好的兼容性和可移植性,fqdn 参数值的长度应控制在 96 个字符以内。
端口配置:
在 TDengine 集群中,每个 dnode 对外提供服务时使用的端口由配置参数 serverPort 决定。默认情况下,该参数的值为 6030。通过调整 serverPort 参数,可以灵活地配置 dnode 的对外服务端口,以满足不同部署环境和安全策略的需求。
集群对外连接:
TDengine 集群可以容纳单个、多个甚至几千个数据节点。应用只需要向集群中任何一个数据节点发起连接即可。这种设计简化了应用程序与集群之间的交互过程,提高了系统的可扩展性和易用性。
当使用 TDengine CLI 启动 taos 时,可以通过以下选项来指定 dnode 的连接信息。
- -h :用于指定 dnode 的 FQDN。这是一个必需项,用于告知应用程序连接到哪个dnode。
- -P :用于指定 dnode 的端口。这是一个可选项,如果不指定,将使用 TDengine 的配置参数 serverPort 作为默认值。
通过这种方式,应用程序可以灵活地连接到集群中的任意 dnode,而无须关心集群的具体拓扑结构。
集群内部通讯:
在 TDengine 集群中,各个 dnode 之间通过 TCP 方式进行通信。当一个 dnode 启动时,它会首先获取 mnode 所在 dnode 的 endpoint 信息。其次,新启动的 dnode 与集群中的 mnode 建立连接,并进行信息交换。
这一过程确保了 dnode 能够及时加入集群,并与 mnode 保持同步,从而能够接收和执行集群层面的命令和任务。通过 TCP 连接,dnode 之间以及 dnode 与 mnode 之间能够可靠地传输数据,保障集群的稳定运行和高效的数据处理能力。
获取 mnode 的 endpoint 信息的步骤如下:
- 第 1 步:检查自己的 dnode.json 文件是否存在,如果不存在或不能正常打开以获得mnode endpoint 信息,进入第 2 步。
- 第 2 步,检查配置文件 taos.cfg,获取节点配置参数 firstEp、secondEp(这两个参数指定的节点可以是不带 mnode 的普通节点,这样的话,节点被连接时会尝试重定向到 mnode 节点),如果不存在 firstEP、secondEP,或者 taos.cfg 中没有这两个配置参数,或者参数无效,进入第 3 步。
- 第 3 步,将自己的 endpoint 设为 mnode endpoint,并独立运行。
获取 mnode 的 endpoint 列表后,dnode 发起连接,如果连接成功,则成功加入工作的集群;如果不成功,则尝试 mnode endpoint 列表中的下一个。如果都尝试了,但仍然连接失败,则休眠 几秒后再次尝试。
Mnode 的选择:
在 TDengine 集群中,mnode 是一个逻辑上的概念,它并不对应于一个单独执行代码的实体。实际上,mnode 的功能由服务器侧的 taosd 进程负责管理。
在集群部署阶段,第 1 个 dnode 会自动承担 mnode 的角色。随后,用户可以通过 SQL 在集群中创建或删除额外的 mnode,以满足集群管理的需求。这种设计使得 mnode 的数量和配置具有很高的灵活性,可以根据实际应用场景进行调整。
新数据节点的加入:
一旦 TDengine 集群中有一个 dnode 启动并运行,该集群便具备了基本的工作能力。为了扩展集群的规模,可以按照以下两个步骤添加新节点。
- 第 1 步,首先使用 TDengine CLI 连接现有的 dnode。其次,执行 create dnode 命令来 添加新的 dnode。这个过程将引导用户完成新 dnode 的配置和注册过程- 。
- 第 2 步,在新加入的 dnode 的配置文件 taos.cfg 中设置 firstEp 和 secondEp 参数。这两个参数应分别指向现有集群中任意两个活跃 dnode 的 endpoint。这样做可以确保新dnode 能够正确加入集群,并与现有节点进行通信。
重定向:
在 TDengine 集群中,无论是新启动的 dnode 还是 taosc,它们首先需要与集群中的mnode 建立连接。然而,用户通常并不知道哪个 dnode 正在运行 mnode。为了解决这个问题,TDengine 采用了一种巧妙的机制来确保它们之间的正确连接。
具体来说,TDengine 不要求 dnode 或 taosc 直接连接到特定的 mnode。相反,它们只需要向集群中的任何一个正在工作的 dnode 发起连接。由于每个活跃的 dnode 都维护着当前运行的 mnode endpoint 列表,因此这个连接请求会被转发到适当的 mnode。
当接收到来自新启动的 dnode 或 taosc 的连接请求时,如果当前 dnode 不是 mnode,它会立即将 mnode endpoint 列表回复给请求方。收到这个列表后,taosc 或新启动的dnode 可以根据这个列表重新尝试建立与 mnode 的连接。
此外,为了确保集群中的所有节点都能及时获取最新的 mnode endpoint 列表,TDengine 采用了节点间的消息交互机制。当 mnode endpoint 列表发生变化时,相关的更新会通过消息迅速传播到各个 dnode,进而通知到 taosc。
一个典型的消息流程
为解释 vnode、mnode、taosc 和应用之间的关系以及各自扮演的角色,下面对写入数据这个典型操作的流程进行剖析。
- 应用通过 JDBC 或其他 API 接口发起插入数据的请求。
- taosc 会检查缓存,看是否保存有该表所在数据库的 vgroup-info 信息。如果有,直接到第 4 步。如果没有,taosc 将向 mnode 发出 get vgroup-info 请求。
- mnode 将该表所在数据库的 vgroup-info 返回给 taosc。Vgroup-info 包含数据库的 vgroup 分布信息(vnode ID 以及所在的 dnode 的 End Point,如果副本数为 N,就有 N 组 End Point),还包含每个 vgroup 中存储数据表的 hash 范围。如果 taosc 迟迟得不到 mnode 回应,而且存在多个 mnode,taosc 将向下一个 mnode 发出请求。
- taosc 会继续检查缓存,看是否保存有该表的 meta-data。如果有,直接到第 6 步。如果没有,taosc 将向 vnode 发出 get meta-data 请求。
- vnode 将该表的 meta-data 返回给 taosc。Meta-data 包含有该表的 schema。
- taosc 向 leader vnode 发起插入请求。
- vnode 插入数据后,给 taosc 一个应答,表示插入成功。如果 taosc 迟迟得不到 vnode 的回应,taosc 会认为该节点已经离线。这种情况下,如果被插入的数据库有多个副本,taosc 将向 vgroup 里下一个 vnode 发出插入请求。
- taosc 通知 APP,写入成功。
对于第二步,taosc 启动时,并不知道 mnode 的 End Point,因此会直接向配置的集群对外服务的 End Point 发起请求。如果接收到该请求的 dnode 并没有配置 mnode,该 dnode 会在回复的消息中告知 mnode EP 列表,这样 taosc 会重新向新的 mnode 的 EP 发出获取 meta-data 的请求。
对于第四和第六步,没有缓存的情况下,taosc 无法知道虚拟节点组里谁是 leader,就假设第一个 vnodeID 就是 leader,向它发出请求。如果接收到请求的 vnode 并不是 leader,它会在回复中告知谁是 leader,这样 taosc 就向建议的 leader vnode 发出请求。一旦得到插入成功的回复,taosc 会缓存 leader 节点的信息。
上述是插入数据的流程,查询、计算的流程也完全一致。taosc 把这些复杂的流程全部封装屏蔽了,对于应用来说无感知也无需任何特别处理。
通过 taosc 缓存机制,只有在第一次对一张表操作时,才需要访问 mnode,因此 mnode 不会成为系统瓶颈。但因为 schema 有可能变化,而且 vgroup 有可能发生改变(比如负载均衡发生),因此 taosc 会定时和 mnode 交互,自动更新缓存。