TDengine 整体架构全景 — 深度解析

适用版本:TDengine v3.x(v3.3.x / v3.4.x) | 最后更新:2026-05-10

概述

TDengine 是一款专为时序数据场景设计的高性能分布式数据库。它不是在已有数据库上做的优化包装,而是从零开始自主研发的全栈时序数据处理平台,内置了数据库、缓存、流计算和数据订阅四大核心功能,一套系统即可替代传统方案中 Kafka + Redis + HBase + Flink 的复杂组合。

TDengine 的架构设计基于两个基本假设:

  1. 任何单台硬件/软件都不可靠------所以从第一天起就按分布式高可靠架构设计
  2. 任何单台计算机都无法处理海量数据------所以支持水平扩展,通过节点虚拟化和负载均衡高效利用异构集群资源

本文从进程层、逻辑层、通信层三个维度全面剖析 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.cmain() 函数,整体流程如下:

复制代码
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          (定时任务)            │
└───────────────────────────────────────────────┘

性能考量

  1. MNode 不是瓶颈:taosc 会缓存 vgroup-info 和 table-meta,只有首次访问时才需要联系 mnode。后续操作直接路由到 vnode。
  2. 写入优化:WAL + MemTable 的追加写模式,配合列式存储和压缩,写入吞吐极高。
  3. 查询优化:BRIN 索引 + SMA 预计算 + Tag 索引 + 时间分区,多级过滤减少数据扫描量。
  4. 存算分离:通过 QNode 实现计算与存储的解耦,避免大查询影响写入性能。
  5. 多级存储:支持 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

TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。

相关推荐
Mahir081 小时前
MySQL 事务全解:从 ACID 特性到并发问题,再到底层实现与线上最佳实践
数据库·mysql·面试
m0_716255001 小时前
二、Hadoop 面试必背 | 三、Hive 面试必背
大数据·hadoop·面试
zz0723201 小时前
Elasticsearch
大数据·elasticsearch·搜索引擎
薪火铺子1 小时前
ElasticSearch 聚合查询与性能优化实战
大数据·elasticsearch·性能优化
SamDeepThinking1 小时前
你认为从0-1开发一个项目最难的地方是什么?
java·后端·架构
前进的李工2 小时前
高效索引优化:数据库查询提速指南(适合创建索引的11种情况)
数据库·mysql·面试
l1t2 小时前
DeepSeek总结的无需编译器:编写纯 SQL 的 Postgres 扩展
数据库·sql·postgresql
CableTech_SQH2 小时前
F5G全光网络二层扁平架构技术拆解:OLT+ODN+ONU全链路原理详解
大数据·网络·5g·信息与通信
2601_949936962 小时前
2026年职业资格证书趋势分析:专业化与数字化融合视角
大数据