适用版本:TDengine v3.x(v3.3.x / v3.4.x) | 最后更新:2026-05-10
概述
TDengine 是一款专为时序数据场景设计的高性能分布式数据库。它不是在已有数据库上做的优化包装,而是从零开始自主研发的全栈时序数据处理平台,内置了数据库、缓存、流计算和数据订阅四大核心功能,一套系统即可替代传统方案中 Kafka + Redis + HBase + Flink 的复杂组合。
TDengine 的架构设计基于两个基本假设:
- 任何单台硬件/软件都不可靠------所以从第一天起就按分布式高可靠架构设计
- 任何单台计算机都无法处理海量数据------所以支持水平扩展,通过节点虚拟化和负载均衡高效利用异构集群资源
本文从进程层、逻辑层、通信层三个维度全面剖析 TDengine 的整体架构,包括源码级的数据结构和关键流程。
核心概念速查表
| 概念 | 说明 |
|---|---|
| dnode | 数据节点,taosd 进程在一台物理机上的一个运行实例,是部署和运维的最小单元 |
| vnode | 虚拟节点,dnode 内部的逻辑存储单元,负责一部分表的时序数据存储和查询 |
| vgroup | 虚拟节点组,由分布在不同 dnode 上的多个 vnode 组成,通过 Raft 协议保证高可用 |
| mnode | 管理节点,负责集群元数据管理(用户/数据库/超级表/vgroup分配等),最多 3 个 |
| qnode | 计算节点,专门执行查询计算任务,实现存储与计算分离 |
| snode | 流计算节点,专门处理流计算任务,实现流计算与批计算分离 |
| bnode | 备份节点(仅企业版),用于 S3 或外部存储的数据备份 |
| taosc | 客户端驱动,应用程序通过它与集群交互,负责路由、缓存和最后一级聚合 |
| taosAdapter | RESTful/WebSocket 网关,为不使用原生驱动的场景提供 HTTP 接口 |
详细架构解析
1. 进程架构:一个 taosd 进程,多种逻辑节点
TDengine 服务端只有一个进程------taosd。它不是单一功能的进程,而是一个节点容器,在一个进程内同时运行多种逻辑节点:
┌─────────────────────────────── taosd 进程 ──────────────────────────────┐
│ │
│ ┌──────────┐ ┌──────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ dnode │ │ mnode │ │ vnode │ │ qnode │ │ snode │ ... │
│ │ 管理模块 │ │ (0或1个) │ │(0~N个) │ │(0或1个)│ │(0或1个)│ │
│ └──────────┘ └──────────┘ └────────┘ └────────┘ └────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ RPC / Transport 层 │ │
│ │ serverRpc ─ clientRpc ─ statusRpc ─ syncRpc │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ TFS (分级文件系统) │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
关键设计:每个 dnode 上,mnode/qnode/snode 最多各一个;vnode 可以有多个(取决于硬件资源)。哪些节点在哪些 dnode 上运行,由 mnode 统一管理和调度。
1.1 taosd 启动流程
taosd 的启动入口在 source/dnode/mgmt/exe/dmMain.c 的 main() 函数,整体流程如下:
main()
→ dmParseArgs() // 解析命令行参数
→ dmInitSystem() // 初始化日志、信号处理
→ dmInit() // 核心初始化
│ → dmInitDnode() // 创建全局 SDnode 对象
│ │ ├── 注册各节点类型的管理函数 (openFp/closeFp/startFp/stopFp)
│ │ ├── 检查哪些节点需要在本 dnode 上部署
│ │ ├── dmInitStatusClient() // 心跳 RPC
│ │ └── dmInitSyncClient() // Raft 同步 RPC
│ └── dmRunDnode() // 运行所有节点
│ ├── dmOpenNodes() // 逐个打开所需节点
│ ├── dmStartNodes() // 逐个启动节点
│ └── while(!stop) taosMsleep(100) // 主循环等待退出信号
→ dmCleanup() // 退出清理
1.2 SDnode:顶层数据结构
c
// source/dnode/mgmt/node_mgmt/inc/dmMgmt.h
typedef struct SDnode {
int8_t once;
bool stop;
EDndRunStatus status; // INIT → RUNNING → STOPPED
SDnodeData data; // dnodeId, clusterId, endpoint 列表
SUdfdData udfdData; // UDF 守护进程
STfs *pTfs; // 分级文件系统
SMgmtWrapper wrappers[NODE_END]; // 7 种节点的管理包装器
SDnodeTrans trans; // RPC 传输层句柄
} SDnode;
其中 SMgmtWrapper 为每种节点类型提供统一的生命周期接口:
c
typedef struct SMgmtWrapper {
SMgmtFunc func; // openFp, closeFp, startFp, stopFp
void *pMgmt; // 指向具体节点管理对象
const char *name; // "dnode"、"mnode"、"vnode" 等
EDndNodeType ntype; // 节点类型枚举
bool deployed; // 是否已部署
bool required; // 是否需要在本 dnode 运行
NodeMsgFp msgFps[TDMT_MAX]; // 消息处理函数表
} SMgmtWrapper;
EDndNodeType 枚举定义了 7 种节点类型:
c
typedef enum {
DNODE = 0, // dnode 管理模块自身
MNODE = 1, // 管理节点
VNODE = 2, // 虚拟节点
QNODE = 3, // 计算节点
SNODE = 4, // 流计算节点
BNODE = 5, // 备份节点
XNODE = 6, // 扩展节点(AI分析等)
NODE_END = 7
} EDndNodeType;
2. VNode:数据存储的核心单元
VNode 是 TDengine 中最重要的逻辑概念------它是数据存储、查询和复制的基本单元。
2.1 VNode 内部模块
每个 VNode 内部包含 5 个核心子模块,各司其职:
┌─────────────────── VNode (vgId=2) ────────────────────┐
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐ │
│ │ META │ │ TSDB │ │ WAL │ │ TQ │ │
│ │ 元数据 │ │ 时序数据 │ │ 预写日志 │ │ 消息队列 │ │
│ │ (B+Tree) │ │ (LSM) │ │ │ │ (订阅) │ │
│ └─────────┘ └─────────┘ └─────────┘ └──────────┘ │
│ │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ SMA │ │ Buffer Pool │ │
│ │ 预聚合 │ │ (3段内存池) │ │
│ └──────────┘ └──────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Raft Sync 模块 │ │
│ │ (Leader 选举 / 日志复制 / 快照传输) │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
| 子模块 | 存储结构 | 职责 |
|---|---|---|
| META | B+Tree(自研 TDB 引擎) | 存储表的元数据:超级表 Schema、子表 Tag 值、表 UID 映射、Tag 索引 |
| TSDB | LSM-Tree(MemTable → SST 文件) | 存储时序数据,按列式存储和压缩,支持多级文件组织 |
| WAL | 顺序追加日志文件 | 写前日志,保证崩溃恢复;同时也是 Raft 日志和 TMQ 消费的数据源 |
| TQ | 基于 WAL/快照 | 管理数据订阅(TMQ)的消费进度和流计算任务的调度 |
| SMA | 独立 SMA 文件 | 预聚合引擎,支持 RSMA(Rollup SMA)多级降采样和 TSMA(用户自定义预聚合) |
2.2 SVnode 数据结构
c
// source/dnode/vnode/src/inc/vnodeInt.h
struct SVnode {
SVState state; // committed/applied 版本号和任期
char *path; // 数据目录路径
SVnodeCfg config; // vgId、dbname、hashRange、tsdbCfg、walCfg、syncCfg
SMsgCb msgCb; // 消息回调接口
// 三段内存缓冲池
SVBufPool *aBufPool[3]; // freeList / inUse / onCommit 循环使用
SVBufPool *inUse; // 当前写入使用的缓冲池
SVBufPool *onCommit; // 正在落盘的缓冲池
// 核心子模块
SMeta *pMeta; // 元数据引擎
STsdb *pTsdb; // 时序数据存储引擎
SWal *pWal; // 预写日志
STQ *pTq; // 消息队列(订阅 + 流)
SSma *pSma; // 预聚合引擎
int64_t sync; // Raft 同步句柄
};
2.3 VNode 的数据分片机制
当创建一个数据库时,mnode 会根据 vgroups 参数创建指定数量的 vgroup。每个 vgroup 负责一个 hash 范围:
数据库 "power" (vgroups=4):
vgroup 1: hashRange [0x00000000, 0x3FFFFFFF] → dnode1
vgroup 2: hashRange [0x40000000, 0x7FFFFFFF] → dnode2
vgroup 3: hashRange [0x80000000, 0xBFFFFFFF] → dnode3
vgroup 4: hashRange [0xC0000000, 0xFFFFFFFF] → dnode1
当写入数据时,taosc 对表名计算一致性 hash 值,然后确定该表属于哪个 vgroup,再将请求发送到对应的 vnode。
2.4 VNode 的 Buffer Pool(三段缓冲池)
VNode 使用一个 3 段循环缓冲池来管理内存写入:
┌───────────┐ ┌───────────┐ ┌───────────┐
│ BufPool-0 │───→│ BufPool-1 │───→│ BufPool-2 │──→ (循环)
│ (inUse) │ │(onCommit) │ │ (free) │
└───────────┘ └───────────┘ └───────────┘
↑ │
新数据写入 后台线程刷盘
- inUse:当前接收新写入的缓冲池
- onCommit:正在被后台线程刷写到磁盘的缓冲池
- free:已完成刷盘、等待被重新使用的缓冲池
当 inUse 的内存使用达到阈值时,触发切换:inUse → onCommit,free → inUse,然后后台 Commit 线程将 onCommit 中的数据持久化到 META(B+Tree)和 TSDB(SST 文件)。
2.5 VNode 的写入路径
一条数据从客户端到磁盘的完整路径:
客户端 INSERT SQL
↓
taosc 解析 SQL,计算表名 hash,定位 vgroup
↓
RPC 发送 TDMT_VND_SUBMIT 到 leader vnode
↓
vnode 收到请求 → vnodeProposeWriteMsg()
↓ [Raft Propose]
Raft 日志复制到 follower vnodes
↓ [多数派确认]
vnodeApplyWriteMsg() → vnodeProcessWriteMsg()
↓
┌──────────────────────────────────┐
│ 1. 写入 WAL(保证持久性) │
│ 2. 写入 MemTable(内存中的数据) │
│ - META 模块:更新表元数据 │
│ - TSDB 模块:插入时序数据 │
│ 3. 触发 SMA(如果配置了 RSMA) │
└──────────────────────────────────┘
↓ [当 MemTable 达到阈值]
后台 Commit 线程:MemTable → 磁盘文件
↓
标记对应 WAL 已落盘,可回收
3. MNode:集群的大脑
MNode 是集群的元数据管理中心,负责管理所有的全局信息。
3.1 MNode 管理的对象
MNode 使用自研的 SDB(State Database) 引擎存储元数据,SDB 本身也通过 Raft 协议在多个 mnode 之间复制,保证高可用。SDB 管理的主要对象包括:
| 类别 | 管理对象 | 说明 |
|---|---|---|
| 集群管理 | Cluster、DNode、MNode、QNode、SNode | 集群拓扑、节点注册和状态 |
| 数据管理 | Database、VGroup、Super Table、SMA | 数据库配置、分片分布、超级表Schema |
| 用户安全 | User、Account、Role、Auth、SecurityPolicy、Token | 用户认证、RBAC权限控制 |
| 流计算 | Stream、StreamTask | 流计算任务定义和调度 |
| 数据订阅 | Topic、Consumer、Subscribe、Offset | TMQ 主题和消费者管理 |
| 扩展功能 | Func(UDF)、Index、View、Compact、Grant | 自定义函数、索引、视图、压缩任务 |
| 事务 | Trans | 分布式事务状态机 |
3.2 MNode 的定时任务
MNode 内部运行一个定时器线程,周期性执行以下关键任务:
每隔数秒执行一次:
├── mndPullupTrans() // 推进未完成的分布式事务
├── mndCalMqRebalance() // TMQ 消费者负载均衡
├── mndPullupTtl() // 检查和删除过期(TTL)表
├── mndCheckDnodeOffline() // 检测 dnode 宕机,标记 vgroup 离线
├── mndPullupGrant() // 检查授权许可
├── mndPullupTelem() // 发送遥测数据
├── mndPullupArbHeartbeat() // 仲裁组心跳
├── mndPullupCompacts() // 推进 Compaction 状态机
└── mndPullupTrimDb() // WAL/数据修剪
3.3 MNode 的分布式事务
MNode 通过 Trans(事务)对象来实现跨节点的 DDL 操作原子性。例如 CREATE DATABASE 需要在多个 dnode 上创建 vnode,这个过程通过一个多阶段事务来保证:
CREATE DATABASE 事务流程:
1. mnode 创建 Trans 对象,记录所有操作步骤
2. Raft 复制 Trans 到所有 mnode 副本
3. Leader mnode 按步骤执行:
- 向 dnode1 发送 "创建 vnode1" 请求
- 向 dnode2 发送 "创建 vnode2" 请求
- 收集所有响应
4. 全部成功 → Trans 标记为 committed
任一失败 → 执行回滚(undoAction)
5. 定时器 mndPullupTrans() 会重试未完成的事务
4. QNode:存算分离的关键
QNode 是独立于 VNode 的纯计算节点。当集群配置了 QNode 后,查询引擎会将计算密集型的操作(如聚合、排序、JOIN)调度到 QNode 执行,VNode 只负责数据扫描和过滤。
无 QNode 的查询:
taosc → VNode(扫描 + 计算 + 返回结果)
有 QNode 的查询:
taosc → QNode(接收计算任务)
↓
QNode 向多个 VNode 请求数据
↓
QNode 执行聚合/排序/JOIN
↓
QNode 返回结果给 taosc
如果集群中没有可用的 QNode,所有计算任务将回退到 VNode 中执行。
5. SNode:流计算的专用节点
SNode 专门处理流计算(CREATE STREAM)任务。当集群配置了 SNode 后,MNode 会将流计算任务调度到 SNode 执行,从而避免流计算占用 VNode 的写入和查询资源。
流计算任务分配:
有 SNode → MNode 调度流任务到 SNode
无 SNode → 流任务在 VNode 中执行(与数据存储共享资源)
6. Taosc:智能客户端驱动
Taosc 不是一个简单的连接库------它是一个智能路由和缓存引擎,封装了所有分布式系统的复杂性。
6.1 Taosc 的核心能力
┌───────────────────── taosc ─────────────────────────┐
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ 元数据缓存 │ │ 路由和重定向 │ │ SQL 解析 │ │
│ │ vgroup-info │ │ 自动发现 │ │ 和规划 │ │
│ │ table-meta │ │ mnode/leader │ │ │ │
│ └─────────────┘ └──────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ 任务调度 │ │ 结果合并 │ │ 心跳管理 │ │
│ │ Scheduler │ │ 最后一级聚合 │ │ HbMgr │ │
│ └─────────────┘ └──────────────┘ └─────────────┘ │
└───────────────────────────────────────────────────────┘
6.2 关键数据结构
c
// source/client/inc/clientInt.h
// 全局单例:每个进程一个
SAppInfo {
pInstMap // instKey → SAppInstInfo (每个集群的连接信息)
}
// 每个集群连接的实例
SAppInstInfo {
clusterId // 集群 ID
pQnodeList // QNode 列表(用于查询分发)
pTransporter // RPC 传输器
pAppHbMgr // 心跳管理器
mgmtEp // MNode endpoint 集合
}
// 每个用户连接
STscObj {
user // 用户名
db // 当前使用的数据库
connId // 连接 ID
pAppInfo // 所属集群实例
}
// 每个 SQL 查询
SRequestObj {
requestId // 请求 ID
sqlstr // SQL 文本
pQuery // 解析和规划后的查询对象
pTscObj // 所属连接
}
6.3 连接建立流程
taos_connect(host, user, pass, db, port)
↓
taosc 创建 STscObj
↓
发送 TDMT_MND_CONNECT 到配置的 firstEp
↓
如果该 dnode 不是 mnode → 返回 mnode EP 列表 → 重定向
↓
mnode 返回 SConnectRsp:
- dnodeId // 当前 dnode ID
- clusterId // 集群 ID
- svrTimestamp // 服务器时间戳
- epSet // mnode endpoint 集合
↓
taosc 缓存集群信息,启动心跳线程(每 1.5 秒)
↓
返回连接句柄给应用
6.4 查询执行全流程
应用调用 taos_query("SELECT avg(voltage) FROM meters WHERE location='Beijing'")
↓
1. taosc 解析 SQL → AST(抽象语法树)
↓
2. Catalog 查找(带缓存):
- 检查缓存中是否有 "meters" 表的 meta-data
- 如果没有 → 向 mnode 请求 vgroup-info
- 如果没有 → 向 vnode 请求 table-meta
↓
3. 语义分析 + 类型检查 + 权限校验
↓
4. 逻辑计划生成 → 物理计划生成 → 分布式计划拆分
(将查询拆分为多个子任务,每个子任务对应一个 vnode/qnode)
↓
5. Scheduler 分发子任务:
- TDMT_SCH_QUERY 发送到各 vnode/qnode
- 各节点并行执行扫描和局部聚合
↓
6. 结果合并:
- TDMT_SCH_FETCH 收集各节点结果
- taosc 执行最后一级合并/聚合
↓
7. 返回最终结果给应用
7. RPC 通信层
7.1 通信协议
TDengine 集群内所有通信均使用 TCP 协议,通信层(Transport)基于自研的 RPC 框架。
每个 dnode 维护 4 个独立的 RPC 句柄:
| RPC 句柄 | 用途 | 说明 |
|---|---|---|
| serverRpc | 监听外部请求 | 接收来自 taosc 和其他 dnode 的请求 |
| clientRpc | 发起通用请求 | 向其他 dnode 发送请求 |
| statusRpc | 状态心跳 | dnode 定期向 mnode 上报状态 |
| syncRpc | Raft 同步 | 专用于 Raft 日志复制和快照传输 |
7.2 消息类型体系
所有 RPC 消息类型定义在 include/common/tmsgdef.h 中,按模块分段:
| 段前缀 | 段起始值 | 说明 | 典型消息 |
|---|---|---|---|
TDMT_DND_* |
0<<8 | dnode 管理 | CREATE_MNODE, CREATE_VNODE, CREATE_QNODE |
TDMT_MND_* |
1<<8 | mnode 元数据操作 | CONNECT, HEARTBEAT, CREATE_DB, CREATE_STB, CREATE_USER |
TDMT_VND_* |
2<<8 | vnode 数据操作 | SUBMIT, CREATE_TABLE, DELETE, ALTER_CONFIG |
TDMT_SCH_* |
3<<8 | 查询调度 | QUERY, FETCH, CANCEL_TASK, DROP_TASK |
TDMT_STREAM_* |
4<<8 | 流计算 | TASK_DISPATCH, TASK_CHECKPOINT, TASK_PAUSE |
TDMT_SYNC_* |
5<<8 | Raft 同步 | HEARTBEAT, APPEND_ENTRIES, SNAPSHOT |
7.3 消息路由机制
当 taosd 收到一条 RPC 消息时的处理流程:
RPC 收到消息
↓
dmProcessRpcMsg()
↓
1. 版本兼容性检查
2. IP 白名单检查(如果启用)
3. 根据 msgType 查找路由表:
pHandle = &pTrans->msgHandles[TMSG_INDEX(msgType)]
→ 确定由哪种节点类型处理(defaultNtype)
↓
4. 获取对应的节点 Wrapper:
dmAcquireWrapper(pDnode, pHandle->defaultNtype)
→ 如果需要 mnode 但本 dnode 没部署 mnode → 返回重定向信息
↓
5. 分发到对应节点的消息处理函数:
pWrapper->msgFps[TMSG_INDEX(msgType)](pMgmt, pMsg)
↓
6. 消息进入对应的工作队列:
WRITE_QUEUE / QUERY_QUEUE / FETCH_QUEUE / SYNC_QUEUE 等
↓
7. 工作线程从队列取出消息并执行
7.4 工作队列体系
TDengine 使用多种工作队列来隔离不同类型的操作:
c
typedef enum {
QUERY_QUEUE, // 查询执行
FETCH_QUEUE, // 结果获取
READ_QUEUE, // 只读操作
WRITE_QUEUE, // 写入操作(Raft Propose)
APPLY_QUEUE, // Raft Apply(已提交的写入)
SYNC_QUEUE, // Raft 同步消息
STREAM_LONG_EXEC_QUEUE, // 流计算长时执行
STREAM_CHKPT_QUEUE, // 流计算 Checkpoint
// ...
} EQueueType;
8. VGroup 与 Raft 共识
8.1 VGroup 的作用
VGroup(虚拟节点组)是 TDengine 实现高可用的关键机制。一个 VGroup 由分布在不同 dnode 上的多个 VNode 组成,它们之间通过 Raft 一致性协议 保持数据同步。
vgroup 3 (replica=3):
┌──────────┐ ┌──────────┐ ┌──────────┐
│ VNode │ │ VNode │ │ VNode │
│ (Leader) │←──→│(Follower)│←──→│(Follower)│
│ dnode-1 │ │ dnode-2 │ │ dnode-3 │
└──────────┘ └──────────┘ └──────────┘
↑
写入请求
- 写操作 只能在 Leader VNode 上执行
- Leader 将 WAL 日志异步复制到 Follower
- 多数派确认后,数据才被认为已提交
- Leader 宕机时,剩余 Follower 自动选举新 Leader
8.2 副本数与 VGroup 数
- 副本数(replica):创建数据库时指定,默认 1,最大 3。决定每个 VGroup 中的 VNode 数量。
- VGroup 数(vgroups):创建数据库时指定,默认自动计算。决定数据被分成多少个分片。
- 约束:创建 N 副本的数据库,集群至少需要 N 个 dnode。
sql
-- 创建 3 副本、8 个 vgroup 的数据库
CREATE DATABASE power REPLICA 3 VGROUPS 8;
9. 集群拓扑示例
一个典型的 3 节点生产集群:
┌─────────── dnode-1 (192.168.1.101:6030) ──────────────┐
│ mnode (Leader) │ vnode(vg=1,Leader) │
│ qnode │ vnode(vg=4,Follower) │
│ snode │ vnode(vg=7,Leader) │
└───────────────────────────────────────────────────────┘
│ TCP
┌─────────── dnode-2 (192.168.1.102:6030) ──────────────┐
│ mnode (Follower) │ vnode(vg=1,Follower) │
│ qnode │ vnode(vg=5,Leader) │
│ │ vnode(vg=8,Follower) │
└───────────────────────────────────────────────────────┘
│ TCP
┌─────────── dnode-3 (192.168.1.103:6030) ──────────────┐
│ mnode (Follower) │ vnode(vg=1,Follower) │
│ qnode │ vnode(vg=6,Leader) │
│ │ vnode(vg=9,Leader) │
└───────────────────────────────────────────────────────┘
┌──── taosc ────┐
│ 元数据缓存 │
│ 路由表 │
┌──────────────┤ 心跳线程 ├──────────────┐
│ └──────────────┘ │
↓ ↓ ↓
dnode-1 dnode-2 dnode-3
(mnode Leader)
10. 端到端完整操作流程
10.1 写入流程(INSERT)
App:INSERT INTO d1001 USING meters TAGS('Beijing',2) VALUES(now, 10.3, 219, 0.31)
│
↓ (1)
Taosc:
├── 解析 SQL
├── 查缓存:meters 的 vgroup-info? → 无 → 向 mnode 请求
├── (2) → mnode 返回 vgroup-info(hash 范围、vnode 分布)
├── 计算 "d1001" 的 hash → 命中 vgroup 3
├── 查缓存:d1001 的 table-meta? → 无 → 向 vgroup 3 的 leader vnode 请求
├── (3) → vnode 返回 table-meta(schema 信息)
├── 如果 d1001 不存在 → 自动建表(因为用了 USING 子句)
├── (4) 编码数据,发送 TDMT_VND_SUBMIT 到 vgroup 3 的 leader vnode
│
↓ (5)
VNode (Leader, vgroup 3):
├── Raft Propose → 复制 WAL 到 Follower
├── 多数派确认 → Apply
├── 写 WAL → 写 MemTable
├── 返回成功
│
↓ (6)
Taosc:缓存 leader 信息,返回成功给 App
10.2 查询流程(SELECT)
App:SELECT avg(voltage) FROM meters WHERE location='Beijing' AND ts > now-1h
│
↓
Taosc:
├── 解析 SQL → AST
├── 从 Catalog 缓存获取 meters 的 schema + vgroup 分布
├── 语义分析 → 逻辑计划 → 物理计划
├── 拆分为分布式子计划:
│ ├── 子任务 1 → vgroup 1 的 vnode:扫描+过滤+局部聚合
│ ├── 子任务 2 → vgroup 2 的 vnode:扫描+过滤+局部聚合
│ ├── 子任务 3 → vgroup 3 的 vnode:扫描+过滤+局部聚合
│ └── 合并任务 → qnode(或 taosc 自身):合并各 vnode 的局部结果
├── Scheduler 并行分发 TDMT_SCH_QUERY 到各 vnode/qnode
├── 各节点执行:
│ ├── Tag 过滤:location='Beijing' → 筛选出符合条件的子表
│ ├── 时间过滤:ts > now-1h → 定位文件组和数据块
│ ├── 扫描数据 → 局部 avg 计算
│ └── 返回局部结果
├── taosc/qnode 合并所有局部结果 → 最终 avg
└── 返回给 App
11. 存储层概览
11.1 数据文件组织
/var/lib/taos/vnode/vnode2/
├── wal/ # WAL 预写日志
│ ├── 00000000.log
│ └── 00000001.log
├── meta/ # 元数据 B+Tree
│ ├── main.tdb # TDB 主文件
│ └── main.tdb-journal # TDB 日志文件
└── tsdb/ # 时序数据 (LSM)
├── fileset-0/ # 文件组 0(覆盖某个时间范围)
│ ├── xxxx.head # BRIN 索引(块范围索引)
│ ├── xxxx.data # 列式数据块
│ ├── xxxx.sma # 预计算统计信息(min/max/sum)
│ ├── xxxx.stt # 碎片数据(STT 文件)
│ └── xxxx.tomb # 删除记录
├── fileset-1/
└── ...
11.2 三层数据存储
| 层级 | 存储位置 | 管理模块 | 数据内容 |
|---|---|---|---|
| 数据库元数据 | MNode(SDB) | mnode | 集群拓扑、用户、数据库配置、超级表 Schema、VGroup 分布 |
| 表元数据 | VNode(META) | vnode/meta | 子表名 → UID 映射、Tag 值、表 Schema、Tag 索引 |
| 时序数据 | VNode(TSDB) | vnode/tsdb | 按 (ts, version) 排序的列式时序数据 |
12. 关键配置参数
| 参数 | 默认值 | 说明 |
|---|---|---|
fqdn |
hostname | dnode 的 FQDN,用于集群通信 |
serverPort |
6030 | taosd 监听端口 |
firstEp |
- | 集群第一个 dnode 的 endpoint,新节点加入时指定 |
secondEp |
- | 备用 endpoint,firstEp 不可达时使用 |
dataDir |
/var/lib/taos | 数据文件存储目录,支持多磁盘多级存储 |
tempDir |
/tmp | 临时文件目录 |
numOfVnodeFetchThreads |
CPU/4 | VNode Fetch 线程数 |
numOfVnodeQueryThreads |
CPU*2 | VNode Query 线程数 |
numOfVnodeStreamThreads |
CPU/4 | VNode 流计算线程数 |
supportVnodes |
CPU*2 | 单个 dnode 支持的最大 VNode 数 |
statusInterval |
1 秒 | dnode 向 mnode 上报状态的间隔 |
13. 线程模型概览
TDengine 的线程模型是多线程、多队列的架构:
taosd 进程中的主要线程组:
┌─ RPC 线程组 ──────────────────────────────────┐
│ Server IO 线程 × N (接收 RPC 请求) │
│ Client IO 线程 × N (发送 RPC 请求) │
└───────────────────────────────────────────────┘
┌─ VNode 线程组(每种 VNode 共享) ─────────────┐
│ Query Worker 线程池 (查询执行) │
│ Fetch Worker 线程池 (结果获取) │
│ Write Worker 线程池 (写入 Raft Propose) │
│ Apply Worker 线程池 (Raft Apply) │
│ Sync Worker 线程池 (Raft 同步) │
│ Stream Worker 线程池 (流计算) │
│ Commit 线程 × 1 (MemTable 刷盘) │
│ Merge/Compact 线程 × 1 (SST 文件合并) │
└───────────────────────────────────────────────┘
┌─ MNode 线程组 ────────────────────────────────┐
│ Read Worker 线程池 (元数据查询) │
│ Write Worker 线程池 (DDL 操作) │
│ Sync Worker 线程池 (Raft 同步) │
│ Timer 线程 × 1 (定时任务) │
└───────────────────────────────────────────────┘
性能考量
- MNode 不是瓶颈:taosc 会缓存 vgroup-info 和 table-meta,只有首次访问时才需要联系 mnode。后续操作直接路由到 vnode。
- 写入优化:WAL + MemTable 的追加写模式,配合列式存储和压缩,写入吞吐极高。
- 查询优化:BRIN 索引 + SMA 预计算 + Tag 索引 + 时间分区,多级过滤减少数据扫描量。
- 存算分离:通过 QNode 实现计算与存储的解耦,避免大查询影响写入性能。
- 多级存储:支持 SSD/HDD/S3 多级存储,热数据放 SSD,冷数据放 HDD 或 S3,降低成本。
FAQ
Q1: TDengine 的 vnode 和传统数据库的分片(shard)有什么区别?
VNode 本质上就是一个数据分片,但它更加自包含。每个 VNode 不仅存储时序数据(TSDB),还存储该分片内所有表的元数据(META)、预写日志(WAL)、预聚合数据(SMA)和消费进度(TQ)。这意味着一个 VNode 可以独立完成数据的写入、查询和恢复,不依赖外部的元数据服务。
Q2: MNode 最多只有 3 个,会不会成为单点瓶颈?
不会。MNode 管理的是"低频"的元数据操作(如建库、建表、DDL),这些操作占比很小。真正的高频操作(数据写入和查询)直接路由到 VNode,不经过 MNode。taosc 会缓存所有需要的路由信息,正常情况下只在首次访问时联系 MNode。
Q3: 如果 taosc 连接的 dnode 宕机了怎么办?
taosc 会自动重试连接集群中的其他 dnode。由于每个 dnode 都知道所有 mnode 的 endpoint,taosc 可以通过任意存活的 dnode 重新获取集群信息。对于多副本数据库,如果当前 leader vnode 所在的 dnode 宕机,taosc 会自动连接同一 vgroup 中新选举出的 leader。
Q4: QNode 和 VNode 中执行查询有什么区别?
在 VNode 中执行查询时,扫描和计算共享 VNode 的资源(内存、CPU),大查询可能影响写入性能。使用 QNode 后,VNode 只负责数据扫描和初步过滤,复杂的聚合、排序、JOIN 在 QNode 上执行,实现了存储和计算的资源隔离。
Q5: TDengine 的 Raft 实现有什么特点?
TDengine 自研了 Raft 共识协议实现(源码在 source/libs/sync/),支持:
- Leader 选举与自动故障转移
- 日志复制(基于 WAL)
- 快照传输(用于新节点追赶数据)
- 仲裁机制(2 副本场景下的脑裂处理)
MNode 和 VGroup 都使用同一套 Raft 实现,但独立运行各自的 Raft 组。
Q6: 一个 dnode 上可以同时运行多少种节点?
理论上一个 dnode 可以同时运行 mnode + 多个 vnode + qnode + snode + bnode。在小规模部署中,单个 dnode 可以承担所有角色。在大规模生产环境中,建议将 mnode 和 qnode 部署在独立的 dnode 上,避免资源争抢。
Q7: 为什么 TDengine 使用 FQDN 而不是 IP 地址进行通信?
FQDN 比 IP 地址更稳定。在容器化、云部署场景中,IP 地址可能频繁变化,而 FQDN 可以通过 DNS 动态解析。使用 FQDN 使集群能够适应 IP 变化而无需重新配置。
Q8: TDengine 的消息类型那么多(上百种),如何管理?
所有消息类型按模块分段编号(DND/MND/VND/SCH/STREAM/SYNC),每个 dnode 维护一个全局路由表 msgHandles[TDMT_MAX],在启动时由各节点模块注册。收到消息时,通过 TMSG_INDEX(msgType) 直接索引到对应的处理函数,复杂度 O(1)。
参考
- TDengine 官方文档 - 整体架构:https://docs.taosdata.com/tdinternal/arch/
- TDengine 官方文档 - 存储引擎:https://docs.taosdata.com/tdinternal/storage/
- TDengine GitHub 源码:https://github.com/taosdata/TDengine
- 源码关键路径:
- 进程入口:
source/dnode/mgmt/exe/dmMain.c - 节点管理:
source/dnode/mgmt/node_mgmt/src/dmMgmt.c - VNode 实现:
source/dnode/vnode/ - MNode 实现:
source/dnode/mnode/impl/ - 客户端驱动:
source/client/ - RPC 传输层:
source/libs/transport/ - Raft 共识:
source/libs/sync/ - 消息定义:
include/common/tmsgdef.h
- 进程入口:
关于 TDengine
TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。
