
适用版本:TDengine v3.x(v3.3.x / v3.4.x) | 最后更新:2026-05-12
概述
VNode(虚拟节点)是 TDengine 中数据存储、查询和复制的基本单元。理解 VNode 的完整生命周期------创建、打开、配置变更、分裂、迁移、压缩和销毁------是深入掌握 TDengine 集群运行机制的关键。
本文逐步讲解 VNode 从诞生到消亡的每一个阶段,涵盖 MNode(编排层)和 DNode(执行层)的协作机制、状态转换、崩溃恢复策略以及实际操作的 SQL 示例。
核心概念速查
| 概念 | 说明 |
|---|---|
| VNode | 一个 VGroup 在某个 DNode 上的实例,包含 META/TSDB/WAL/TQ/SMA 五大子模块 |
| VGroup | 由 1~3 个分布在不同 DNode 上的 VNode 组成的逻辑组,通过 Raft 保持数据一致 |
| vnodes.json | DNode 级别的持久化文件,记录该 DNode 上所有 VNode 的状态(vgId、是否已删除等) |
| vnode.json | 每个 VNode 数据目录下的配置文件,记录该 VNode 的数据库配置、Raft 配置和提交状态 |
VNode 生命周期全景图
┌─────────────────────────────────────┐
│ MNode (编排层) │
│ │
│ 分配 VGroup / 变更配置 / 删除 VGroup │
│ 分裂 VGroup / 重分布 / 压缩 │
└──────────┬────────────┬───────────────┘
│ │
创建/变更请求 删除请求
│ │
┌──────────▼────────────▼───────────────┐
│ DNode (执行层) │
│ │
│ 管理 VNode 注册表(三个状态分组): │
│ 创建中 → 运行中 → 已关闭 │
│ │
│ vnodes.json(持久化所有 VNode 状态) │
└────────────────────────────────────────┘
VNode 状态流转:
创建中 → 运行中 → [配置变更/分裂/迁移/压缩] → 关闭中 → 已销毁
1. VNode 创建
1.1 触发条件
VNode 的创建由以下操作触发:
sql
-- 方式 1:创建数据库时自动创建
CREATE DATABASE power VGROUPS 4 REPLICA 3;
-- 这会创建 4 个 VGroup,每个 VGroup 3 个副本 → 共 12 个 VNode
-- 方式 2:重分布 VGroup 到新节点
REDISTRIBUTE VGROUP 5 DNODE 1 DNODE 4 DNODE 5;
-- 方式 3:分裂 VGroup(企业版)
SPLIT VGROUP 5;
1.2 MNode 侧:编排 VGroup 分配
当用户执行 CREATE DATABASE 时,MNode 执行以下流程:
第一步:划分 Hash 空间
系统将 32 位 hash 空间 [0, 0xFFFFFFFF] 均匀划分为 VGROUPS 段。例如 4 个 VGroup:
VGroup 1: [0x00000000, 0x3FFFFFFF]
VGroup 2: [0x40000000, 0x7FFFFFFF]
VGroup 3: [0x80000000, 0xBFFFFFFF]
VGroup 4: [0xC0000000, 0xFFFFFFFF]
第二步:为每个 VGroup 分配 DNode
MNode 从全局计数器递增分配唯一的 vgId,然后为每个 VGroup 选择 REPLICA 个 DNode。选择策略是负载均衡------优先选择已分配 VNode 数最少的 DNode。
第三步:创建分布式事务
MNode 为每个 VNode 副本创建一个"创建 VNode"的事务动作,包含数据库的所有配置参数(内存、存储、WAL、压缩、副本等)。事务策略为可重试 + 幂等------如果重试时目标 VNode 已存在,视为成功而不报错。
1.3 DNode 侧:执行创建
DNode 收到 MNode 的创建请求后,按以下步骤执行:
- 反序列化请求------提取 vgId、数据库名、hash 范围、所有配置参数、副本列表等
- 验证身份------检查请求中的 dnodeId/fqdn/port 与本节点是否匹配
- 幂等性检查------如果该 VNode 已存在且健康,直接返回成功
- 分配磁盘------选择 TFS(分级文件系统)中负载最低的磁盘
- 创建 VNode 数据目录 ------初始化
vnode.json配置文件 - 打开 VNode 引擎------按顺序初始化所有子模块(详见第 2 节)
- 注册到管理器------将 VNode 加入"运行中"状态组,分配工作队列
- 启动 Raft 同步------开始参与 Raft 组
- 持久化 vnodes.json------将新 VNode 写入本地节点列表
错误处理:如果步骤 5-8 中任何一步失败,DNode 会关闭已打开的子模块并删除数据目录,确保不会留下半初始化的 VNode。
1.4 创建时传递的关键参数
创建 VNode 时传递的参数来源于 CREATE DATABASE 的选项:
| 参数组 | 字段 | 说明 |
|---|---|---|
| 标识 | vgId, dbname | VGroup ID 和数据库名称 |
| Hash | hashBegin, hashEnd | 该 VNode 负责的 hash 范围 |
| 内存 | BUFFER, PAGES, PAGESIZE, CACHEMODEL, CACHESIZE |
内存缓冲池和缓存配置 |
| TSDB | DURATION, KEEP, MINROWS, MAXROWS, PRECISION |
时序数据存储参数 |
| WAL | WAL_LEVEL, WAL_FSYNC_PERIOD, WAL_RETENTION_PERIOD/SIZE |
预写日志配置 |
| 压缩 | COMP, STT_TRIGGER |
压缩级别和 STT 文件触发阈值 |
| 副本 | REPLICA, 副本列表 |
Raft 副本配置 |
2. VNode 打开(taosd 启动时加载)
2.1 启动时 VNode 加载流程
当 taosd 进程启动时,需要恢复之前运行的所有 VNode:
- 读取 vnodes.json------获取本节点上所有 VNode 的列表
- 遍历每个条目 :
- 如果
dropped == 1→ 清理残留目录(上次删除操作崩溃后的恢复) - 如果
toVgId != 0→ 执行分裂崩溃恢复(详见第 5 节) - 正常条目 → 打开 VNode 引擎并注册到管理器
- 如果
2.2 vnodes.json 格式
每个 DNode 在其数据目录下维护一个 vnodes.json 文件:
json
{
"vnodes": [
{
"vgId": 2,
"dropped": 0,
"vgVersion": 1,
"diskPrimary": 0,
"toVgId": 0
},
{
"vgId": 5,
"dropped": 0,
"vgVersion": 3,
"diskPrimary": 1,
"toVgId": 0
}
]
}
| 字段 | 说明 |
|---|---|
vgId |
VGroup ID |
dropped |
删除标记。1 表示已标记删除,下次启动时清理数据目录 |
vgVersion |
配置版本号,用于检测配置变更 |
diskPrimary |
主磁盘索引(多磁盘场景) |
toVgId |
目标 vgId,仅在分裂过程中非零,用于崩溃恢复 |
写入时机 :创建、删除、配置变更、分裂等操作都会重新写入 vnodes.json。写入采用原子方式(先写临时文件再重命名),确保崩溃安全。
2.3 VNode 引擎打开过程
VNode 引擎打开时按严格的依赖顺序初始化所有子模块:
VNode 打开的 10 步初始化序列:
① Buffer Pool --- 内存基础设施(3 段循环缓冲池)
② Meta --- 元数据 B+Tree(表 Schema、Tag 值、UID 映射)
③ Meta 升级 --- 跨版本兼容(如果有 schema 格式变化)
④ TSDB --- LSM 时序数据引擎(MemTable + SST 文件索引)
⑤ WAL --- 预写日志(恢复未落盘的数据)
⑥ Query --- 查询引擎初始化
⑦ TQ --- 消息队列(TMQ 订阅 + 流计算任务)
⑧ BSE --- Blob Store 引擎
⑨ Begin --- 事务准备,开始接收新写入
⑩ Sync --- Raft 同步模块(最后启动,因为要先有完整数据才能参与复制)
为什么顺序重要? 后续模块依赖前面的模块:TSDB 需要 Meta 提供表 Schema;WAL 回放需要 TSDB 的写入接口;Query 需要 Meta + TSDB;TQ 需要 WAL 进行消费回放。Sync 最后启动,因为在参与 Raft 复制之前必须先恢复完整的本地数据。
错误处理 :如果任何子模块打开失败,按反向顺序关闭已打开的模块并释放内存,确保不会泄漏资源。
3. VNode 配置变更(ALTER DATABASE)
3.1 触发条件
sql
ALTER DATABASE power BUFFER 512; -- 修改内存缓冲区大小
ALTER DATABASE power WAL_LEVEL 2; -- 修改 WAL 级别
ALTER DATABASE power KEEP 365; -- 修改数据保留期限
ALTER DATABASE power STT_TRIGGER 8; -- 修改 STT 触发阈值
ALTER DATABASE power CACHEMODEL 'both'; -- 修改缓存模式
3.2 处理流程
ALTER DATABASE power BUFFER 512
│
▼
MNode:
├── 创建可重试事务
├── 遍历该数据库的所有 VGroup
│ 对每个 VGroup 的每个 VNode:
│ └── 添加"修改配置"动作
└── 提交事务
│
▼
DNode(每个相关的 DNode):
├── 更新 VNode 内存中的配置
├── 持久化到 vnode.json
└── 返回成功
│
▼
MNode:收到所有响应后发送确认
哪些参数可以动态修改 :BUFFER、PAGES、CACHEMODEL、CACHESIZE、WAL_LEVEL、WAL_FSYNC_PERIOD、KEEP、STT_TRIGGER、MINROWS、COMP 等。
哪些参数不可修改 :PRECISION、DURATION 等结构性参数需要重建数据库。
4. VNode 副本变更
4.1 触发条件
sql
-- 将数据库从单副本改为三副本
ALTER DATABASE power REPLICA 3;
-- 或通过重分布指定具体节点
REDISTRIBUTE VGROUP 5 DNODE 1 DNODE 4 DNODE 5;
4.2 处理流程(单副本 → 三副本)
副本变更是 VNode 生命周期中最复杂的操作之一,因为需要在不停服的情况下改变 Raft 组的成员:
单副本 → 三副本的完整流程:
1. MNode 为 VGroup 选择 2 个新 DNode
↓
2. 在新 DNode 上创建 VNode(Learner 角色)
Learner 不参与投票,只接收数据
↓
3. 等待 Learner 追赶数据
通过 Raft 日志复制或快照传输
↓
4. 将 Learner 提升为 Voter
现在可以参与投票和选举
↓
5. 更新所有 VNode 的 Raft 配置为 3 副本
4.3 DNode 侧如何处理副本配置变更
TDengine 采用**"关闭-修改配置-重新打开"**的模式来变更 Raft 成员:
- 关闭当前 VNode(保留数据)
- 在磁盘上修改
vnode.json中的 Raft 配置(新增/移除 Voter 和 Learner 列表) - 以新的 Raft 配置重新打开 VNode
- 更新
vnodes.json
为什么不热修改? TDengine 的 Raft 实现不支持运行时热变更成员配置。这种"关闭-修改-重开"方式虽然简单,但会导致该 VNode 有短暂不可用窗口(通常秒级)。对于多副本场景,其他副本仍可服务,所以对用户基本无感知。
5. VNode 分裂(SPLIT VGROUP)
5.1 概念
分裂是 TDengine 实现水平扩展的核心机制(企业版功能)。当某个 VGroup 的数据量过大或负载过高时,可以将其分裂为两个 VGroup,每个负责原来一半的 hash 范围。
分裂前:
VGroup 5: hash [0x00000000, 0xFFFFFFFF] ← 负责所有表
分裂后:
VGroup 5: hash [0x80000000, 0xFFFFFFFF] ← 上半部分的表
VGroup 9: hash [0x00000000, 0x7FFFFFFF] ← 下半部分的表(新 VGroup)
分裂后,属于下半部分 hash 范围的表自动归入新 VGroup。客户端通过元数据刷新感知这一变化。
5.2 触发条件
sql
-- 企业版功能
SPLIT VGROUP 5;
5.3 MNode 编排的分裂流程
分裂是一个多步骤的串行事务:
SPLIT VGROUP 分裂流程(8 步):
1. 预处理副本数
分裂要求 2 副本状态。
如果当前是 1 副本 → 先扩到 2 副本
如果当前是 3 副本 → 先缩到 2 副本
2. 禁止写入
向所有副本发送"禁写"指令,确保数据一致
3. 确认所有副本就绪
4. 创建新 VGroup
分配新 vgId,hash 范围设为原 VGroup 的下半部分
5. 变更原 VGroup 的 hash 范围
原 VGroup 缩小到上半部分
同时 vgId 可能变更(因为需要重命名数据目录和文件)
6. 在同一 DNode 上创建新 VNode
新 VNode 继承下半部分 hash 范围的数据
7. 恢复副本数
将两个 VGroup 恢复到原来的副本数
8. 更新元数据
5.4 DNode 侧:Hash 范围变更
当 DNode 收到"变更 hash 范围"指令时:
- 记录恢复标记 ------在
vnodes.json中写入toVgId(崩溃恢复面包屑) - 关闭源 VNode------刷盘并关闭所有子模块
- 修改 hash 范围和 vgId ------更新
vnode.json中的配置,重置 Raft 为单副本 - 重命名数据文件------TSDB 文件名中包含 vgId,需要批量重命名
- 重命名数据目录 ------
vnode{旧vgId}→vnode{新vgId} - 以新配置重新打开 VNode
- 清除恢复标记 ------从
vnodes.json中移除toVgId
5.5 崩溃恢复
分裂过程中最危险的是目录和文件重命名步骤。如果 taosd 在重命名过程中崩溃,系统使用 预写标记(write-ahead marker) 模式恢复:
- 操作开始前 :在
vnodes.json中写入toVgId - 操作完成后 :清除
toVgId
重启时根据 toVgId 判断操作阶段:
重启恢复逻辑:
→ 目标目录已有有效配置?
是 → 重命名已完成,使用新 vgId
→ 源目录的 vgId 等于旧值?
是 → 重命名未开始,回滚到旧 vgId
→ 源目录的 vgId 等于新值但目录名未改?
是 → 配置已改但目录未改,继续完成重命名
MNode 的事务重试策略确保整个分裂操作最终完成。
6. VNode 迁移(REDISTRIBUTE)
6.1 触发条件
sql
-- 手动指定 VGroup 5 迁移到 DNode 1, 4, 5
REDISTRIBUTE VGROUP 5 DNODE 1 DNODE 4 DNODE 5;
-- 自动负载均衡(需要在配置中开启 balance 参数)
-- MNode 定时检查各 DNode 负载,自动触发迁移
6.2 迁移流程(单副本场景)
VNode 迁移过程不停服------旧 VNode 在数据追赶期间继续正常服务:
将 VGroup 5 从 DNode 1 迁移到 DNode 4:
1. MNode 验证新旧 DNode 都在线
2. 在 DNode 4 上创建 VNode(Learner 角色)
此时 DNode 4 的 VNode 是空的
3. 修改 DNode 1 上 VNode 的 Raft 配置
添加 DNode 4 为 Learner
4. 等待 Learner 追赶数据
Leader 向 Learner 发送 Raft 日志或快照
追赶期间 DNode 1 的 VNode 继续正常服务
5. 提升 DNode 4 的 Learner 为 Voter
6. 从 DNode 1 删除旧 VNode
7. 更新元数据
6.3 迁移流程(三副本场景)
三副本迁移需要逐个替换副本,保证任何时刻都有多数派存活:
原来在 DNode 1, 2, 3 → 迁移到 DNode 1, 4, 5
第一轮:替换 DNode 2 → DNode 4
在 DNode 4 创建 Learner → 追赶数据 → 提升为 Voter → 删除 DNode 2 的 VNode
第二轮:替换 DNode 3 → DNode 5
在 DNode 5 创建 Learner → 追赶数据 → 提升为 Voter → 删除 DNode 3 的 VNode
全程保持至少 2 个副本可用,业务不受影响
6.4 数据追赶机制
新加入的 Learner VNode 通过两种方式追赶数据:
| 方式 | 适用场景 | 原理 |
|---|---|---|
| Raft 日志复制(增量) | Leader 与 Learner 差距不大 | Leader 将 WAL 中的日志条目逐条发送给 Learner |
| 快照传输(全量) | Learner 是全新节点或差距太大 | Leader 将完整数据按模块顺序发送:配置 → 元数据 → 时序数据 → 消息队列 → SMA → Blob Store |
系统自动判断使用哪种方式。如果 Learner 需要的日志已被 Leader 清理(WAL 回收),则自动切换为快照传输。
7. VNode 压缩(COMPACT)
7.1 触发条件
sql
-- 手动触发整个数据库的压缩
COMPACT DATABASE power;
-- 查看压缩进度
SHOW COMPACTS;
-- 终止压缩
KILL COMPACT <compact_id>;
7.2 处理流程
COMPACT DATABASE power
│
▼
MNode:
├── 创建压缩任务记录
├── 向所有 VGroup 的每个 VNode 发送压缩请求
└── 启动定时器查询进度
│
▼
DNode(各 VNode 独立执行):
├── TSDB 引擎执行 Compaction:
│ ├── 合并 STT 文件中的碎片数据
│ ├── 合并小数据块为大数据块
│ ├── 清理已标记删除的数据
│ └── 重写数据文件,优化存储布局
└── 定期响应 MNode 的进度查询
│
▼
MNode:
├── 所有 VNode 完成 → 标记压缩任务为完成
└── 超时或失败 → 标记为失败
8. VNode 关闭与销毁
8.1 关闭
VNode 关闭按照打开的反向顺序依次关闭子模块:
VNode 关闭的步骤:
1. 等待后台提交任务完成
2. 关闭 Raft 同步
3. 关闭查询引擎
4. 关闭消息队列(TQ)
5. 关闭 WAL
6. 关闭 TSDB
7. 关闭 Meta
8. 释放 Buffer Pool
9. 关闭 Blob Store
10. 释放内存
在正式关闭之前,DNode 还会执行预关闭操作:移除流计算 Leader 身份、预关闭 Raft 同步、预关闭查询引擎------确保不会有新的任务进入。
8.2 销毁(DROP DATABASE → DROP VNODE)
sql
DROP DATABASE power;
DNode 收到删除 VNode 请求后,执行两阶段删除:
两阶段删除流程:
第一阶段:标记删除
├── 在 vnodes.json 中设置 dropped = 1
└── 持久化到磁盘
↑↑↑ 崩溃安全点:如果此后崩溃,重启时看到 dropped=1 会自动清理
第二阶段:执行删除
├── 从"运行中"分组移除 VNode
├── 关闭工作队列(查询/写入/同步等)
├── 关闭 VNode 引擎(反向关闭所有子模块)
├── 删除数据目录
└── 从 vnodes.json 中移除该条目
为什么要两阶段? 这是崩溃安全的关键:
- 如果合并为一步"先删目录再更新 vnodes.json":崩溃后 vnodes.json 还有该条目但数据已删,启动报错
- 如果合并为一步"先更新 vnodes.json 再删目录":崩溃后条目已删但目录残留,磁盘泄漏
两阶段方案确保:先写 dropped=1(崩溃后重启会清理),再实际删除。任何阶段崩溃都能正确恢复。
MNode 侧也有幂等保护------如果目标 VNode 不存在,视为成功。
9. DNode 侧的 VNode 管理机制
9.1 三个状态分组
每个 DNode 维护三个状态分组来管理本地的所有 VNode:
| 分组 | 说明 |
|---|---|
| 创建中 | VNode 正在被创建,还未就绪 |
| 运行中 | VNode 正常运行,可以接受读写请求 |
| 已关闭 | VNode 正在被关闭或已关闭 |
当请求到达时,DNode 只会将请求分发给"运行中"分组中的 VNode。
9.2 线程与队列分配
VNode 管理的线程模型:
全局共享的线程池(所有 VNode 共用):
├── 查询线程池(自动扩缩容)
├── Fetch 线程池
├── 管理线程(串行)------ 处理 DROP/ALTER
└── 管理线程(可并行)------ 处理 CREATE
每个 VNode 独立的工作队列:
├── 写入队列(Raft Propose)
├── 同步队列(Raft 消息)
├── Apply 队列(已提交写入执行)
├── 查询队列 → 发送到全局查询线程池
└── Fetch 队列 → 发送到全局 Fetch 线程池
关键设计:
- CREATE 允许并行------多个 VNode 可同时创建
- DROP/ALTER 串行执行------避免并发删除/修改的竞态条件
- 写入前检查------磁盘满或分裂期间拒绝写入
9.3 引用计数保护
DNode 通过引用计数确保正在处理消息的 VNode 不会被意外关闭或删除。获取 VNode 引用时原子增加计数,处理完消息后原子减少。只有引用计数归零时才会真正释放 VNode 资源。
10. VNode 数据目录结构
/var/lib/taos/vnode/vnode{vgId}/
│
├── vnode.json # VNode 配置和状态
│
├── wal/ # WAL 预写日志
│ ├── 00000000.log # WAL 文件(按序编号)
│ ├── 00000001.log
│ └── meta # WAL 元信息
│
├── meta/ # 元数据(B+Tree)
│ ├── main.tdb # B+Tree 主文件
│ └── main.tdb-journal # 事务日志
│
├── tsdb/ # 时序数据(LSM 结构)
│ ├── v{vgId}f{filesetId}.head # BRIN 索引
│ ├── v{vgId}f{filesetId}.data # 列式数据
│ ├── v{vgId}f{filesetId}.sma # 预计算统计
│ ├── v{vgId}f{filesetId}.stt # 碎片数据
│ └── v{vgId}f{filesetId}.tomb # 删除记录
│
├── tq/ # 消息队列(TMQ + Stream)
│
└── bse/ # Blob Store
注意 TSDB 数据文件的命名中包含 vgId,这就是分裂时需要批量重命名文件的原因。
性能考量
VNode 数量规划
sql
-- 查看当前 VGroup 分布
SHOW VGROUPS;
-- 查看 VNode 状态
SELECT * FROM information_schema.ins_vnodes;
| 场景 | 建议 VGroup 数 | 说明 |
|---|---|---|
| 单节点开发环境 | 1-2 | 减少资源占用 |
| 3 节点生产环境 | 节点数 × 2 | 均匀分布,保证负载均衡 |
| 10+ 节点大集群 | 按表数量估算 | 每个 VGroup 承载 10 万~100 万张表 |
关键性能参数
| 参数 | 影响 | 建议 |
|---|---|---|
supportVnodes |
单 DNode 最大 VNode 数 | 默认 CPU×2,内存不足时减小 |
BUFFER |
每个 VNode 的 MemTable 内存 | 写入量大时增大(单位 MB) |
PAGES + PAGESIZE |
Meta 引擎内存 | 表数量多时增大 |
STT_TRIGGER |
STT 文件合并阈值 | 多表低频场景设大(4-8),少表高频设 1 |
FAQ
Q1: VNode 创建失败了怎么办?
创建是幂等的。如果重试时 VNode 已存在(属于同一个数据库),直接视为成功。如果创建过程中 DNode 崩溃,MNode 会自动重试事务,向同一 DNode 重发创建请求。
Q2: taosd 启动时如果某个 VNode 打开失败会怎样?
该 VNode 会被标记为失败状态,DNode 继续启动其他 VNode。失败的 VNode 不参与读写,但不影响其他 VNode 的正常工作。用户可以通过 SHOW VNODES 查看失败的 VNode。
Q3: vnodes.json 损坏了怎么办?
vnodes.json 采用原子写入(先写临时文件再 rename),极少损坏。如果确实损坏,可以根据实际存在的 vnode/vnode{N}/ 目录手动重建。每个 VNode 目录内的 vnode.json 包含完整配置,vnodes.json 本质上只是目录索引。
Q4: SPLIT VGROUP 期间如果 taosd 崩溃,数据会丢失吗?
不会。分裂采用多重崩溃恢复机制:
- 预写标记 :操作前在
vnodes.json写入toVgId,重启时根据此标记判断恢复方向 - 目录检查:检查源目录和目标目录的配置文件,确定重命名进行到哪一步
- MNode 事务重试:确保整个分裂操作最终完成
Q5: 如何将 VNode 从一个 DNode 迁移到另一个?
使用 REDISTRIBUTE VGROUP 命令。迁移过程不停服:先在新节点创建 Learner 副本 → 通过 Raft 日志/快照追赶数据 → 提升为 Voter → 删除旧副本。数据追赶期间旧 VNode 继续正常服务。
Q6: 为什么副本变更需要关闭再重开 VNode?
TDengine 的 Raft 实现不支持运行时热变更成员配置。通过"关闭-修改配置-重开"的方式简化了实现,代价是副本变更期间该 VNode 有短暂不可用(通常秒级)。对于多副本场景,其他副本仍可服务,所以对用户基本无感知。
Q7: VNode 的 Buffer Pool 为什么是 3 段?
三段设计实现了写入和落盘的流水线:
- 段 1(inUse):接收新写入
- 段 2(onCommit):后台线程正在刷盘
- 段 3(free):已完成刷盘,等待复用
如果只有 2 段,写入和刷盘会交替阻塞。3 段确保在刷盘期间仍有空闲段接收新写入。
Q8: 如何确认 VNode 分裂(SPLIT)是否成功?
sql
-- 查看 VGroup 列表,确认 hash 范围已分割
SHOW VGROUPS;
-- 查看数据库的 VGroup 数量是否增加
SELECT * FROM information_schema.ins_databases WHERE name='power';
分裂成功后,SHOW VGROUPS 会显示新增的 VGroup,其 hash 范围是原 VGroup 的一半。
Q9: 删除 VNode 为什么要两阶段?
这是崩溃安全的关键:
- 如果只有一步且先删数据再更新列表:崩溃后列表还有该条目,但数据已删,启动报错
- 如果只有一步且先更新列表再删数据:崩溃后条目已删但数据残留,磁盘泄漏
两阶段(先标记 dropped=1,再删数据和移除条目)确保任何阶段崩溃都能正确恢复。
Q10: 企业版和开源版在 VNode 生命周期上有什么区别?
| 功能 | 开源版 | 企业版 |
|---|---|---|
| 创建/删除 VNode | ✅ | ✅ |
| ALTER DATABASE | ✅ | ✅ |
| REDISTRIBUTE VGROUP | ✅ | ✅ |
| SPLIT VGROUP | ❌ | ✅ |
| 共享存储 | ❌ | ✅ |
| S3 存储集成 | ❌ | ✅ |
参考
第一篇 系统构架
- 01-《TDengine 整体架构全景 --- 深度解析》
- 02-《集群拓扑深度解析 --- 节点发现、EP 机制与负载均衡》
- 03-《MNode 内部机制深度解析 --- SDB、事务引擎与 DDL 处理全链路》
关于 TDengine
TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。