TDengine VNode 生命周期 — 从创建到销毁的完整旅程

适用版本: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 的创建请求后,按以下步骤执行:

  1. 反序列化请求------提取 vgId、数据库名、hash 范围、所有配置参数、副本列表等
  2. 验证身份------检查请求中的 dnodeId/fqdn/port 与本节点是否匹配
  3. 幂等性检查------如果该 VNode 已存在且健康,直接返回成功
  4. 分配磁盘------选择 TFS(分级文件系统)中负载最低的磁盘
  5. 创建 VNode 数据目录 ------初始化 vnode.json 配置文件
  6. 打开 VNode 引擎------按顺序初始化所有子模块(详见第 2 节)
  7. 注册到管理器------将 VNode 加入"运行中"状态组,分配工作队列
  8. 启动 Raft 同步------开始参与 Raft 组
  9. 持久化 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:

  1. 读取 vnodes.json------获取本节点上所有 VNode 的列表
  2. 遍历每个条目
    • 如果 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:收到所有响应后发送确认

哪些参数可以动态修改BUFFERPAGESCACHEMODELCACHESIZEWAL_LEVELWAL_FSYNC_PERIODKEEPSTT_TRIGGERMINROWSCOMP 等。

哪些参数不可修改PRECISIONDURATION 等结构性参数需要重建数据库。

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 成员:

  1. 关闭当前 VNode(保留数据)
  2. 在磁盘上修改 vnode.json 中的 Raft 配置(新增/移除 Voter 和 Learner 列表)
  3. 以新的 Raft 配置重新打开 VNode
  4. 更新 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 范围"指令时:

  1. 记录恢复标记 ------在 vnodes.json 中写入 toVgId(崩溃恢复面包屑)
  2. 关闭源 VNode------刷盘并关闭所有子模块
  3. 修改 hash 范围和 vgId ------更新 vnode.json 中的配置,重置 Raft 为单副本
  4. 重命名数据文件------TSDB 文件名中包含 vgId,需要批量重命名
  5. 重命名数据目录 ------vnode{旧vgId}vnode{新vgId}
  6. 以新配置重新打开 VNode
  7. 清除恢复标记 ------从 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 崩溃,数据会丢失吗?

不会。分裂采用多重崩溃恢复机制:

  1. 预写标记 :操作前在 vnodes.json 写入 toVgId,重启时根据此标记判断恢复方向
  2. 目录检查:检查源目录和目标目录的配置文件,确定重命名进行到哪一步
  3. 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 存储集成

参考

第一篇 系统构架

关于 TDengine

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

相关推荐
2301_783848651 小时前
JavaScript中利用Symbol实现单例模式的属性锁定
jvm·数据库·python
Elastic 中国社区官方博客1 小时前
在 Kubernetes 上的 Elastic Cloud:简化的可用区感知、重启和 mTLS
大数据·数据库·搜索引擎·云原生·容器·kubernetes·全文检索
蜀道山老天师1 小时前
Prometheus监控Hadoop集群(实操完整版,含避坑指南)
大数据·linux·运维·hadoop·云原生·prometheus
努力努力再努力wz1 小时前
【Redis入门系列】Redis基础命令详解:从客户端连接到数据读写、key 管理与过期机制
c语言·开发语言·数据结构·数据库·c++·redis·缓存
m0_609160491 小时前
如何使用Python查询MongoDB并转为Pandas DataFrame_数据分析集成实战
jvm·数据库·python
环流_1 小时前
分清redis主要数据操作中的key
数据库·redis·哈希算法
西京刀客1 小时前
redis 大key使用 UNLINK 命令删除、Redis Set / ZSet 存储上限、ZRemRangeByRank命令
数据库·redis·缓存
woxihuan1234561 小时前
c++怎么利用std--variant处理多种二进制子协议包的自动分支解析【进阶】
jvm·数据库·python
切糕师学AI1 小时前
gRPC 负载均衡详解:从原理到最佳实践
架构·负载均衡·grpc