
适用版本:TDengine v3.x(v3.3.x / v3.4.x) | 最后更新:2026-05-15
概述
RPC(Remote Procedure Call)通信层是 TDengine 分布式系统的网络基础设施。集群中所有节点间的通信------客户端到 taosd、dnode 到 mnode 的心跳、Raft 日志复制、查询分发------都通过 RPC 层完成。
本文深入解析 RPC 通信层的五大核心机制:
- 协议格式:消息头结构、魔数校验、序列号追踪
- 连接管理:基于 libuv 的 TCP 连接池、空闲回收、多路复用
- 重试机制:指数退避、EP 轮转、快速失败
- 通道隔离:多 RPC 实例分离不同类型流量
- 流控与背压:队列内存限额、连接数上限、磁盘空间检查
核心概念速查表
| 概念 | 说明 |
|---|---|
| RPC 消息 | 所有节点间通信的基本单元,由固定格式的消息头 + 可变长消息体组成 |
| 消息类型(msgType) | 标识消息用途的枚举值,按模块分段(MND/VND/SCH/SYNC 等) |
| EP Set | 端点集合,包含最多 3 个 mnode 的地址,用于自动切换 |
| 连接池 | 客户端按目标地址维护的可复用 TCP 连接队列 |
| 消息队列 | 服务端按功能划分的处理队列(写队列、读队列、同步队列等) |
| traceId | 贯穿请求全生命周期的追踪标识,用于分布式链路追踪 |
| QID | 查询 ID,标识一个查询请求,用于关联请求和响应 |
| 魔数 | 固定值 0x5f375a86,用于快速识别合法的 TDengine 数据包 |
详细解析
1. 协议格式
1.1 消息头结构
每条 RPC 消息都包含一个固定格式的二进制消息头,紧接着是可变长度的消息体:
RPC 消息二进制布局:
┌──────────────────────────────────────────────────────────┐
│ 消息头(固定大小) │
├──────────────────────────────────────────────────────────┤
│ 标志位(1B) │ 标志位(1B) │ 时间戳(8B) │ 兼容版本号(4B) │
├──────────────────────────────────────────────────────────┤
│ 魔数(4B) │ traceId(16B) │ QID(8B) │
├──────────────────────────────────────────────────────────┤
│ 状态码(4B) │ 消息类型(4B) │ 消息总长度(4B) │ 序列号(8B) │
├──────────────────────────────────────────────────────────┤
│ 消息体(可变长度) │
└──────────────────────────────────────────────────────────┘
各字段说明:
| 字段 | 大小 | 说明 |
|---|---|---|
| version | 4 bit | RPC 协议版本号,当前固定为 2 |
| comp | 2 bit | 压缩标志:0=未压缩,1=LZ4 压缩 |
| noResp | 2 bit | 是否需要响应:0=需要,1=不需要 |
| withUserInfo | 2 bit | 是否携带用户认证信息 |
| hasEpSet | 2 bit | 响应中是否包含 EP Set 更新信息 |
| timestamp | 8 字节 | 发送时的时间戳 |
| compatibilityVer | 4 字节 | 客户端/服务端的兼容版本号 |
| magicNum | 4 字节 | 魔数 0x5f375a86,用于包合法性校验 |
| traceId | 16 字节 | 分布式追踪 ID,贯穿请求全链路 |
| qid | 8 字节 | 查询 ID |
| code | 4 字节 | 响应状态码(请求时为 0) |
| msgType | 4 字节 | 消息类型枚举值 |
| msgLen | 4 字节 | 消息总长度(消息头 + 消息体) |
| seqNum | 8 字节 | 每连接递增的序列号 |
1.2 消息大小限制
| 限制 | 值 | 说明 |
|---|---|---|
| 单消息最大长度 | 512 MB | 包含消息头的总长度上限 |
| 默认最大消息体 | 10 MB | 应用层默认限制(可调整) |
| 压缩头额外开销 | 8 字节 | 压缩时额外的长度信息 |
1.3 消息类型分段
TDengine 将消息类型按目标模块分为多个段(segment),便于路由和管理:
| 段号 | 前缀 | 目标模块 | 典型消息 |
|---|---|---|---|
| 0 | TDMT_DND_* |
DNode 管理 | 创建/删除 vnode、mnode |
| 1 | TDMT_MND_* |
MNode | DDL 操作、心跳、元数据查询 |
| 2 | TDMT_VND_* |
VNode | 数据写入、子表创建 |
| 3 | TDMT_SCH_* |
调度器 | 查询执行、结果获取 |
| 4 | TDMT_STREAM_* |
流计算 | 流任务调度 |
| 5 | TDMT_MON_* |
监控 | 监控数据上报 |
| 6 | TDMT_SYNC_* |
Raft 同步 | 选举、日志复制、心跳 |
| 7 | TDMT_VND_STREAM_* |
VNode 流计算 | VNode 上的流处理 |
| 8 | TDMT_VND_TMQ_* |
TMQ 订阅 | 消费请求 |
| 9 | TDMT_MND_ARB_* |
仲裁 | 仲裁心跳与同步 |
每个消息类型都有对应的请求(xxx_REQ)和响应(xxx_RSP)成对定义。
1.4 魔数与版本校验
收到数据包后,RPC 层执行两层校验:
- 魔数校验 :检查消息头中的魔数是否为
0x5f375a86。非法包(如端口扫描、误连接)在此步即被丢弃 - 版本兼容性校验 :比对客户端和服务端的
compatibilityVer,允许最多 3 个版本的差异。超出范围时返回版本不兼容错误
2. 连接管理
2.1 网络基础 --- libuv
TDengine 的 RPC 层基于 libuv 库构建,使用事件驱动的异步 I/O 模型:
- 每个 RPC 线程运行一个独立的
uv_loop(事件循环) - TCP 连接使用
uv_tcp_t句柄 - 线程间通信使用
uv_async_t(异步通知)和uv_pipe_t(管道)
2.2 服务端架构
服务端连接模型:
┌─────────────────────────────────────────────────┐
│ RPC Server (端口 6030) │
│ │
│ Accept 线程 │
│ │ uv_tcp_t (监听 socket) │
│ │ │
│ ├── 新连接到达 → 按轮转分配到 Worker 线程 │
│ │ │
│ ┌─▼──────────┐ ┌───────────┐ ┌───────────┐ │
│ │ Worker 0 │ │ Worker 1 │ │ Worker N │ │
│ │ uv_loop │ │ uv_loop │ │ uv_loop │ │
│ │ │ │ │ │ │ │
│ │ conn pool │ │ conn pool │ │ conn pool │ │
│ │ ├ conn A │ │ ├ conn D │ │ ├ conn G │ │
│ │ ├ conn B │ │ ├ conn E │ │ └ conn H │ │
│ │ └ conn C │ │ └ conn F │ │ │ │
│ └────────────┘ └───────────┘ └───────────┘ │
└─────────────────────────────────────────────────┘
- Accept 线程:负责接受新的 TCP 连接,通过管道将连接分发给 Worker 线程
- Worker 线程:处理连接上的读写事件,解析消息,将请求分发到对应的处理队列
- 线程数由
numOfRpcThreads参数控制
2.3 客户端连接池
客户端(taosc 和 dnode 内部通信)为每个目标地址维护一个连接池:
客户端连接池模型:
┌────────────────────────────────────────────┐
│ RPC Client 线程 0 │
│ │
│ 连接池(按目标地址分组): │
│ "node1:6030" → [idle_conn1, idle_conn2] │
│ "node2:6030" → [idle_conn3] │
│ "node3:6030" → [](无空闲连接) │
│ │
│ 共享连接堆缓存: │
│ 允许多个请求复用同一 TCP 连接 │
└────────────────────────────────────────────┘
连接复用策略:
- 发送请求时,先从连接池中查找目标地址的空闲连接
- 如果有空闲连接,直接复用
- 如果无空闲连接且未达上限,创建新连接
- 如果已达连接上限,等待现有连接释放(最多等待
timeToGetAvailableConn毫秒) - 请求完成后,连接归还到池中供后续请求使用
连接多路复用:
在 Linux 上,单个 TCP 连接允许最多 10 个并发的未完成请求(shareConnLimit),通过序列号(seqNum)匹配请求和响应。macOS/Windows 上限制为 1(不启用多路复用)。
2.4 空闲连接回收
空闲回收机制:
连接空闲时间超过阈值 → 自动关闭
阈值计算:
基础空闲时间 = max(shellActivityTimer × 1000ms, 90秒)
实际关闭时间 ≈ 基础空闲时间 × 3.3
默认情况 ≈ 5 分钟无活动后关闭
2.5 连接建立超时
| 超时类型 | 默认值 | 说明 |
|---|---|---|
| TCP 连接超时 | 5 秒 | TCP 三次握手的超时时间 |
| 读超时 | 3 秒 | 等待数据读取的超时 |
| 等待可用连接 | 500 秒 | 连接池满时等待空闲连接的超时 |
3. 重试机制
3.1 指数退避算法
当 RPC 请求失败时,客户端使用指数退避策略进行重试:
重试算法:
第 1 次重试:等待 10ms
第 2 次重试:等待 20ms (10 × 2¹)
第 3 次重试:等待 40ms (10 × 2²)
第 4 次重试:等待 80ms (10 × 2³)
...
第 N 次重试:等待 min(10 × 2^(N-1), 1000ms)
最大重试间隔:1000ms(不再增长)
总超时限制:20秒(默认)
超过总超时 → 放弃重试,返回错误给上层
关键参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
retryMinInterval |
10 ms | 首次重试等待时间 |
retryStepFactor |
2 | 退避倍数 |
retryMaxInterval |
1000 ms | 单次重试最大等待时间 |
maxRetryWaitTime |
20000 ms | 总重试超时(可配置,范围 3秒~24小时) |
3.2 可重试的错误码
RPC 层只对临时性错误进行重试,永久性错误(如权限不足、SQL 语法错误)直接返回:
| 错误码 | 含义 | 重试原因 |
|---|---|---|
TSDB_CODE_RPC_NETWORK_UNAVAIL |
网络不可达 | 可能是瞬时网络波动 |
TSDB_CODE_RPC_BROKEN_LINK |
连接断开 | 服务端可能重启中 |
TSDB_CODE_MNODE_NOT_FOUND |
MNode 不可达 | Leader 可能在切换 |
TSDB_CODE_SYN_NOT_LEADER |
非 Leader 节点 | 需要切换到新 Leader |
TSDB_CODE_SYN_RESTORING |
Raft 恢复中 | 等待恢复完成 |
TSDB_CODE_APP_IS_STARTING |
服务启动中 | 等待服务就绪 |
TSDB_CODE_APP_IS_STOPPING |
服务关闭中 | 切换到其他节点 |
TSDB_CODE_VND_STOPPED |
VNode 已停止 | 可能在迁移中 |
3.3 不重试的消息类型
以下消息类型不进行 RPC 层重试(由上层自行处理):
- 查询执行消息(
QUERY/MERGE_QUERY) - 结果获取消息(
FETCH/MERGE_FETCH) - 查询心跳消息
- 任务通知和任务清理消息
原因:这些消息是有状态的查询操作,简单重试可能导致重复执行或状态不一致。
3.4 EP Set 轮转
当请求发往 MNode 失败时,RPC 层会自动切换到 EP Set 中的下一个地址重试:
EP Set 轮转示例:
EP Set = [node1:6030, node2:6030, node3:6030]
当前使用 = node1 (inUse=0)
请求失败 → inUse 前移到 node2 (inUse=1) → 重试
再次失败 → inUse 前移到 node3 (inUse=2) → 重试
再次失败 → inUse 回绕到 node1 (inUse=0) → 等待退避后重试
这保证了 MNode Leader 切换时,客户端能自动找到新 Leader。
3.5 快速失败机制
为了避免对已确认故障的节点持续发送请求,RPC 层实现了快速失败:
- 连续 3 次 请求失败(
failFastThreshold = 3) - 在 5 秒 内(
failFastInterval = 5000ms) - → 后续请求直接返回失败,不再尝试连接该节点
- 超过 5 秒后恢复正常重试
4. 通道隔离
4.1 服务端 --- 单端口多队列
TDengine 服务端只监听一个 TCP 端口(默认 6030),但通过消息队列实现不同类型请求的隔离:
服务端消息分发:
TCP 端口 6030 (所有请求)
│
▼
消息头解析 → 提取 msgType + vgId
│
├── TDMT_MND_* (mnode 消息)
│ ├── DDL 操作 → 写队列
│ ├── SHOW 命令 → 读队列
│ └── 心跳状态 → 状态队列
│
├── TDMT_VND_* (vnode 消息)
│ ├── 数据写入 → 写队列(检查磁盘空间)
│ ├── 子表创建 → 写队列
│ └── 查询执行 → 查询队列
│
├── TDMT_SCH_* (调度消息)
│ ├── 查询 → 查询队列(带预处理)
│ └── 获取结果 → 获取队列
│
├── TDMT_SYNC_* (Raft 消息)
│ ├── 选举/日志 → 同步队列
│ └── 心跳 → 同步读队列
│
└── TDMT_STREAM_* (流计算消息)
├── 普通任务 → 流计算队列
├── 控制消息 → 流计算控制队列
└── 长任务 → 长执行队列
4.2 服务端队列类型
| 队列 | 用途 | 特点 |
|---|---|---|
| 写队列 | DDL、数据写入 | 写入前检查磁盘空间 |
| 读队列 | 元数据查询、SHOW 命令 | 只读操作,可高并发 |
| 查询队列 | SQL 查询执行 | 请求预处理后入队 |
| 获取队列 | 查询结果获取 | 轻量操作 |
| 同步队列 | Raft 选举、日志复制 | 高优先级 |
| 同步读队列 | Raft 心跳 | 高频低延迟 |
| 应用队列 | Raft 提交应用 | 有独立内存限额 |
| 流计算队列 | 流任务处理 | 可能长时间执行 |
| 流控制队列 | 流管理操作 | 控制面消息 |
| 检查点队列 | 流计算状态持久化 | 周期性触发 |
| 状态队列 | DNode 心跳上报 | MNode 专用 |
| 仲裁队列 | Arbitrator 通信 | 独立隔离 |
4.3 客户端 --- 多 RPC 实例
客户端(包括 dnode 内部通信)使用多个独立的 RPC 实例,分离不同类型的流量:
| RPC 实例 | 线程数 | 用途 | 为什么隔离 |
|---|---|---|---|
| 通用实例 | numOfRpcThreads / 2 |
dnode 间一般通信 | 默认通道 |
| 状态实例 | 1 | 心跳/状态上报到 MNode | 防止被大量业务消息阻塞,保证心跳不超时 |
| 同步实例 | numOfRpcThreads / 2 |
Raft 日志复制 | 防止 Raft 消息被业务消息饿死 |
为什么要隔离心跳?
如果心跳和数据写入共用同一个 RPC 实例,当写入量极大时,心跳消息可能被积压在队列中无法及时发送,导致 MNode 误判节点离线。独立的状态 RPC 实例保证心跳消息始终有专用通道。
4.4 客户端(taosc)RPC 实例
用户应用程序通过 taosc 驱动连接时,使用一个独立的 RPC 实例:
| 参数 | 值 |
|---|---|
| 线程数 | numOfRpcThreads |
| 最大会话数 | 1024 |
| 连接池上限 | 10~1000(按配置计算) |
| 消息批量发送 | 未启用 |
| 连接多路复用 | 由 shareConnLimit 控制 |
5. 认证与安全
5.1 用户信息传递
RPC 层使用首次消息携带的方式完成认证:
认证流程:
客户端 服务端
│ │
│ 首次请求(withUserInfo=1) │
│ [消息头][用户名+密码哈希] │
│─────────────────────────────→│
│ │ 提取用户信息
│ │ 绑定到连接对象
│ │ 后续请求无需再传
│ │
│ 后续请求(withUserInfo=0) │
│ [消息头][消息体] │
│─────────────────────────────→│
│ │ 从连接对象获取用户
- 每个新建的 TCP 连接,第一条消息会附带用户名和密码摘要
- 服务端验证后将用户信息绑定到该连接
- 后续消息不再传递用户信息,减少网络开销
5.2 IP 白名单
服务端支持按用户配置 IP 白名单:
- 每次收到请求时,检查发送方 IP 是否在该用户的白名单中
- 白名单信息从 MNode 同步,带有版本号用于增量更新
- 不在白名单中的请求返回
TSDB_CODE_IP_NOT_IN_WHITE_LIST
sql
-- 配置 IP 白名单
ALTER USER test_user ADD HOST '192.168.1.0/24';
ALTER USER test_user DROP HOST '10.0.0.0/8';
5.3 版本兼容性校验
每条消息头都携带发送方的兼容版本号(compatibilityVer)。服务端收到请求后:
- 比对客户端版本与服务端版本
- 允许最多 3 个版本号的差异
- 超出范围时返回版本不兼容错误
这确保了滚动升级时,新旧版本节点可以短暂共存通信。
6. 消息压缩
6.1 LZ4 压缩
RPC 层支持对消息体进行 LZ4 压缩,减少网络传输量:
压缩判断逻辑:
消息体大小 > compressMsgSize?
├── 是 → 执行 LZ4 压缩
│ ├── 压缩后更小 → 使用压缩数据,标记 comp=1
│ └── 压缩后反而更大 → 保持原始数据
└── 否 → 不压缩
6.2 压缩配置
compressMsgSize 值 |
行为 |
|---|---|
-1(默认) |
不压缩任何消息 |
0 |
压缩所有消息 |
N > 0 |
仅压缩消息体超过 N 字节的消息 |
配置范围:-1 到 100000000(100MB)。
建议 :在跨机房或带宽受限的网络环境中,设置
compressMsgSize = 65536(64KB)可以显著减少大批量写入时的网络流量,但会增加少量 CPU 开销。
7. 流控与背压
7.1 队列内存限额
服务端对消息处理队列实施内存限制,防止请求堆积导致 OOM:
内存限额计算:
总可用内存 = 系统总内存
RPC 队列内存 = 总可用内存 × 10% × 60%
应用队列内存 = 总可用内存 × 10% × 40%
示例(64GB 内存机器):
RPC 队列上限 ≈ 3.84 GB
应用队列上限 ≈ 2.56 GB
当队列内存使用超过限额时:
- 新请求被拒绝,返回
TSDB_CODE_OUT_OF_RPC_MEMORY_QUEUE - 客户端收到此错误后会进入重试逻辑
- 相当于 TCP 层面的流控------告知客户端"我处理不过来,请稍后再试"
7.2 连接数限制
| 限制维度 | 参数 | 默认值 | 说明 |
|---|---|---|---|
| 服务端最大连接数 | maxShellConns |
50000 | 所有客户端连接总数上限 |
| 客户端单目标连接数 | connLimitNum |
自动计算(10~1000) | 到同一目标地址的最大连接数 |
| 客户端总会话数 | numOfRpcSessions |
30000 | 所有目标的连接总数 |
超过连接数限制时返回 TSDB_CODE_RPC_MAX_SESSIONS。
7.3 磁盘空间检查
写队列有额外的前置检查------将消息入队前验证磁盘空间是否充足:
- 磁盘空间不足时直接拒绝写入请求
- 返回
TSDB_CODE_NO_ENOUGH_DISKSPACE - 避免消息入队后因磁盘满而处理失败
7.4 消息批量发送
dnode 间通信(非客户端)支持消息批量发送------将多条待发送消息合并为一次 TCP 写入:
- 减少系统调用次数
- 降低网络开销(TCP 小包问题)
- 批量大小有上限,防止单次发送过大
8. 请求的完整生命周期
以客户端发送一条写入请求为例:
RPC 请求完整生命周期:
客户端应用
│
│ 1. 调用 API(如 taos_query)
▼
taosc 客户端驱动
│ 2. 构建消息体(序列化 SQL/数据)
│ 3. 根据表名 hash 确定目标 VGroup
│ 4. 从 Catalog 缓存获取 Leader 地址
▼
RPC 客户端层
│ 5. 选择 RPC 线程(轮转分配)
│ 6. 从连接池获取/创建 TCP 连接
│ 7. 填充消息头(msgType, traceId, qid, seq, timestamp, magic)
│ 8. 压缩判断(消息体 > compressMsgSize?)
│ 9. 首次连接?附加用户认证信息
│ 10. uv_write() 发送到网络
▼
网络传输(TCP)
▼
RPC 服务端层
│ 11. Worker 线程的 uv_loop 收到数据
│ 12. 解析消息头,校验魔数和版本
│ 13. 解压缩(如果 comp=1)
│ 14. 提取用户信息(如果 withUserInfo=1)
│ 15. IP 白名单检查
│ 16. 根据 msgType 确定目标队列
│ 17. 检查队列内存限额
│ 18. 入队
▼
业务处理层
│ 19. Worker 从队列取出消息
│ 20. 执行业务逻辑(写入 WAL、MemTable 等)
│ 21. 构建响应消息
▼
RPC 服务端层
│ 22. 填充响应头(code, EP Set 更新等)
│ 23. uv_write() 发送响应
▼
网络传输(TCP)
▼
RPC 客户端层
│ 24. 收到响应,通过 seqNum 匹配请求
│ 25. 检查 code:成功?需要重试?
│ ├── 成功 → 回调上层
│ └── 可重试错误 → 指数退避后重发
│ 26. 归还连接到连接池
▼
taosc 客户端驱动
│ 27. 解析响应,返回结果给应用
▼
客户端应用
代码示例
配置 RPC 参数
bash
# /etc/taos/taos.cfg
# RPC 线程数(默认 CPU 核数/2)
numOfRpcThreads 4
# 最大会话数
numOfRpcSessions 30000
# 消息压缩阈值(字节,-1=不压缩)
compressMsgSize 65536
# 服务端最大连接数
maxShellConns 50000
# 空闲连接基础超时(秒)
shellActivityTimer 3
# 读超时(秒)
readTimeout 900
# 最大重试等待时间(毫秒)
maxRetryWaitTime 20000
# 连接多路复用上限
shareConnLimit 10
监控 RPC 连接状态
sql
-- 查看当前活跃连接
SHOW CONNECTIONS;
-- 输出示例:
-- connId | user | program | pid | endpoint | login_time | last_access
-- 1 | root | taos | 1234 | 192.168.1.10:56789 | 2024-01-15 10:00:00 | 2024-01-15 10:05:30
-- 查看查询(可间接反映 RPC 活跃度)
SHOW QUERIES;
-- 杀掉特定连接
KILL CONNECTION <connId>;
排查连接问题
bash
# 检查端口是否监听
netstat -tlnp | grep 6030
# 检查 FQDN 解析
ping node1.example.com
# 检查防火墙(6030 是唯一需要开放的端口)
telnet node1.example.com 6030
# 检查 RPC 相关日志(包含 traceId 用于链路追踪)
grep "RPC" /var/log/taos/taosdlog.* | tail -20
跨机房部署的压缩优化
bash
# 场景:客户端和服务端不在同一机房,带宽有限
# 建议开启消息压缩
# 客户端 taos.cfg
compressMsgSize 0 # 压缩所有消息(跨机房场景)
# 或者只压缩大消息(> 64KB)
compressMsgSize 65536
高并发场景的 RPC 调优
bash
# 场景:大量客户端并发连接,每秒数万请求
# 在服务端 taos.cfg 中:
# 增加 RPC 线程数
numOfRpcThreads 8
# 增加最大连接数
maxShellConns 100000
# 增加会话数
numOfRpcSessions 50000
# 场景:客户端到服务端延迟高(如跨地域)
# 在客户端 taos.cfg 中:
# 增加重试等待时间
maxRetryWaitTime 60000
# 增加读超时
readTimeout 1800
# 增加连接多路复用(减少连接数)
shareConnLimit 20
性能考量
RPC 延迟构成
一次 RPC 请求的延迟 =
客户端序列化 +
TCP 传输(往返) +
服务端反序列化 +
队列排队等待 +
业务处理 +
响应序列化 +
TCP 传输(返回) +
客户端反序列化
典型值(同机房):
网络往返:0.1~0.5ms
序列化:< 0.1ms
队列等待:0~数ms(取决于负载)
业务处理:视操作而定
总体:简单操作 0.5~2ms,复杂查询取决于执行时间
关键调优参数总结
| 场景 | 建议调整 |
|---|---|
| 高并发连接 | 增加 numOfRpcThreads、maxShellConns |
| 跨机房/高延迟 | 增加 maxRetryWaitTime、readTimeout、开启压缩 |
| 大批量写入 | 增加 shareConnLimit 复用连接 |
| 内存紧张 | 减少 numOfRpcSessions,降低连接池规模 |
| 频繁超时 | 检查网络、增加 shellActivityTimer |
性能瓶颈识别
| 现象 | 可能原因 | 排查方式 |
|---|---|---|
| 请求延迟突增 | 队列积压 | 查看 TSDB_CODE_OUT_OF_RPC_MEMORY_QUEUE 错误 |
| 连接被拒绝 | 达到连接上限 | 查看 TSDB_CODE_RPC_MAX_SESSIONS 错误 |
| 频繁重连 | 空闲超时过短 | 增大 shellActivityTimer |
| 写入超时 | 磁盘满 | 查看 TSDB_CODE_NO_ENOUGH_DISKSPACE 错误 |
| 偶发失败后恢复 | 正常重试行为 | 检查日志中的 retry 信息 |
FAQ
Q1: TDengine 使用什么网络协议通信?
TCP。TDengine 的 RPC 层基于 libuv 构建,所有节点间通信(客户端→服务端、dnode→mnode、Raft 复制)都使用 TCP 长连接。不使用 UDP,也不使用 HTTP(HTTP/WebSocket 由 taosAdapter 提供,是另一层封装)。
Q2: 只需要开放一个端口就够了吗?
是的。TDengine v3.x 只使用一个 TCP 端口(默认 6030)。所有类型的消息(写入、查询、心跳、Raft 复制)都通过这一个端口传输,在内部通过消息类型路由到不同的处理队列。
v2.x 时代需要开放多个端口(6030~6042),v3.x 简化为单端口。
Q3: 客户端到服务端的连接会一直保持吗?
不会无限保持。默认情况下,空闲约 5 分钟后连接会被自动关闭。下次请求时会自动重新建立连接。这个行为对应用程序是透明的。
如果需要保持长连接(如实时监控场景),可以增大 shellActivityTimer 参数。
Q4: 网络断开后会自动重连吗?
是的。RPC 层会在发送请求失败时自动重试(指数退避,默认总超时 20 秒)。在此期间如果网络恢复,请求会自动成功。如果超过总超时仍然失败,会返回错误码给应用程序,应用层可以自行决定是否继续重试。
Q5: 为什么 SHOW CONNECTIONS 显示的连接数比客户端数多?
因为客户端和服务端之间可能建立多条 TCP 连接(连接池机制)。此外,dnode 之间的内部通信(心跳、Raft)也会占用连接。每个独立的 TCP 连接都会显示为一行。
Q6: compressMsgSize 设为 0 会影响性能吗?
会有轻微的 CPU 开销(LZ4 压缩/解压)。但对于可压缩性高的数据(如时序数据的批量写入),网络传输减少带来的收益通常远大于 CPU 开销。
建议:
- 同机房、万兆网络:不压缩(默认
-1) - 跨机房或带宽受限:设为
0或65536 - CPU 瓶颈场景:不压缩
Q7: RPC 超时参数应该如何调整?
| 场景 | maxRetryWaitTime |
readTimeout |
|---|---|---|
| 同机房,低延迟 | 20000(默认) | 900(默认) |
| 跨城市,中等延迟 | 60000 | 1800 |
| 跨国际,高延迟 | 120000 | 3600 |
| 大查询,结果集很大 | 20000 | 3600~86400 |
readTimeout 特别重要------如果查询执行时间超过这个值,连接会被断开。对于复杂的大数据量查询,务必设置足够大的 readTimeout。
Q8: traceId 有什么用?如何利用?
traceId 是一个 128 位的标识符,在请求发起时生成,贯穿整个处理链路(客户端 → RPC → 服务端 → 写入/查询)。所有相关日志都会打印 traceId,用于:
- 问题排查:根据 traceId 搜索 taosd 日志,追踪一个请求的完整执行路径
- 性能分析:对比请求在各阶段的耗时
- 关联分析:将客户端错误与服务端日志对应起来
bash
# 根据 traceId 搜索日志
grep "QID:0x<qid_hex>" /var/log/taos/taosdlog.*
参考
第一篇 系统构架
- 01-《TDengine 整体架构全景 --- 深度解析》
- 02-《集群拓扑深度解析 --- 节点发现、EP 机制与负载均衡》
- 03-《MNode 内部机制深度解析 --- SDB、事务引擎与 DDL 处理全链路》
关于 TDengine
TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。