TiDB PD Coordinator - 快速参考卡片
核心概念、关键代码和设计模式的速查表
🎯 核心概念 30 秒速览
Coordinator = ReportOptions + Operator队列 + HeartbeatStreams
ReportOptions(两个关键时间)
├─ Interval:心跳汇报周期(10-30s,快)
└─ Balance*Interval:决策周期(300-600s,慢)
↓
Operator生成(决策)
├─ TransferLeader(转移leader)
├─ ChangePeer(添加/删除副本)
├─ Merge(合并region)
├─ Split(分裂region)
└─ ChangePeerV2(原子配置变更)
↓
HeartbeatStreams推送(执行)
└─ 500ms周期推送给TiKV(缓冲1024)
📋 四大工作线程
| 线程 | 周期 | 职责 | 关键方法 |
|---|---|---|---|
| PatrolRegions | 10s | 巡检资源,发现问题 | Run() → PatrolRegions() |
| checkSuspectRanges | 可配 | 检测热点范围 | Run() → checkSuspectRanges() |
| drivePushOperator | 500ms | 推送决策到TiKV | Run() → drivePushOperator() |
| driveSlowNodeScheduler | 可配 | 处理故障节点 | Run() → driveSlowNodeScheduler() |
🔄 Operator 状态机
CREATED → WAITING → STARTED → SUCCESS/TIMEOUT/CANCELED
↑ ↓
└──────────┘ (冲突时退回)
关键:STARTED后通过heartbeat推送,TiKV自治执行
💡 核心代码片段速查
1️⃣ Coordinator.Run() - 启动流程
// 第一阶段:等待集群信息充分
for {
if c.ShouldRun(collectWaitTime...) {
break
}
select {
case <-ticker.C:
case <-c.ctx.Done():
return
}
}
// 第二阶段:启动4个工作线程
c.InitSchedulers(true)
c.wg.Add(4)
go c.PatrolRegions()
go c.checkSuspectRanges()
go c.drivePushOperator() // ⭐ 心跳推送线程
go c.driveSlowNodeScheduler()
2️⃣ drivePushOperator() - 决策推送引擎
func (c *Coordinator) drivePushOperator() {
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-c.ctx.Done():
return
case <-ticker.C:
// 每500ms推送一批operator
c.opController.PushOperators(c.RecordOpStepWithTTL)
}
}
}
// PushOperators 内部干什么?
// 1. 遍历所有STARTED operator
// 2. 检查执行进度(心跳反馈)
// 3. 生成RegionHeartbeatResponse
// 4. 放入msgCh(1024缓冲)
3️⃣ HeartbeatStreams.run() - 异步推送
func (s *HeartbeatStreams) run() {
keepAliveTicker := time.NewTicker(1 * time.Minute)
for {
select {
case update := <-s.streamCh: // 优先级1:流更新
s.streams[update.storeID] = update.stream
case msg := <-s.msgCh: // 优先级2:operator消息
if stream, ok := s.streams[storeID]; ok {
stream.Send(msg) // gRPC异步发送
}
case <-keepAliveTicker.C: // 优先级3:keepalive
for storeID, stream := range s.streams {
stream.Send(keepAlive)
}
case <-s.hbStreamCtx.Done():
return
}
}
}
4️⃣ SendMsg() - 推送Operator
func (s *HeartbeatStreams) SendMsg(region *core.RegionInfo, op *Operation) {
resp := &RegionHeartbeatResponse{
RegionId: region.GetID(),
RegionEpoch: region.GetRegionEpoch(),
TargetPeer: region.GetLeader(),
TransferLeader: op.TransferLeader, // ← 指令
ChangePeer: op.ChangePeer, // ← 指令
Merge: op.Merge, // ← 指令
SplitRegion: op.SplitRegion, // ← 指令
}
select {
case s.msgCh <- resp: // 放入缓冲
case <-s.hbStreamCtx.Done(): // ctx取消
// 没有default,意味着会阻塞!(背压机制)
}
}
🎨 关键设计模式
模式1:两层时间分离
心跳间隔(快) 决策间隔(慢)
↓ ↓
10-30 seconds ← 300-600 seconds
(感知延迟) (避免波动)
比例:1 : 30 (决策是心跳的30倍周期)
为什么?
每次决策成本高 O(n²) 心跳必须快 O(1)
↓ ↓
拉长周期 保持高频
减少CPU 快速更新状态
模式2:缓冲异步处理(无锁并发)
// 生产者(多线程)
c.decisions.SendMsg(msg) // 放入msgCh不阻塞
// 消费者(单线程)
case msg := <-msgCh: // 顺序处理
stream.Send(msg) // 异步gRPC
// 优势:
// - 无锁(channel自己同步)
// - 背压自动(缓冲满了生产者会阻塞)
// - 顺序保证(单消费者)
模式3:CP + AP融合
CP层(Raft/etcd) 内存决策 AP层(Heartbeat)
┌──────────────┐ ┌──────────┐ ┌──────────────┐
│ Scheduler配置 │ 读取 │ 生成Op │ 推送 │ 推送给TiKV │
│ RegionState ├─────────→│ (快速) ├──────→│ (异步) │
│ ClusterVer │ └──────────┘ └──────────────┘
└──────────────┘
重启不丢失 几毫秒延迟 500ms推送周期
结果:
- 决策延迟:100ms
- 集群可用性:99.9%(PD故障时TiKV继续执行)
- 一致性:CP + 最终一致性混合
🚀 快速实现 Checklist
如果你要从 0 开始构建 Coordinator
□ 第1步:设计CP层持久化
└─ 用 etcd 保存 Scheduler配置、边界信息
□ 第2步:设计内存决策层
└─ Operator 队列 + 决策算法
□ 第3步:设计AP层推送
└─ HeartbeatStreams + msgCh(1024缓冲)
□ 第4步:启动4个工作线程
├─ 巡检线程(发现问题)
├─ 决策线程(生成指令)
├─ 推送线程(下发指令)✅ 最关键
├─ 故障线程(处理异常)
□ 第5步:处理通信
└─ gRPC heartbeat流 + 缓冲消息
□ 第6步:监控和告警
└─ operator队列深度/延迟/失败率
📊 性能指标参考
| 指标 | 目标 | 来源 |
|---|---|---|
| 决策延迟 | <1000ms | PatrolRegions + drivePushOperator |
| operator推送吞吐 | >1000 ops/s | msgCh缓冲 / 推送周期 |
| heartbeat丢失率 | <0.1% | gRPC重试机制 |
| Coordinator内存 | <1GB | operator队列深度 |
🔍 关键参数调优表
// 参数 默认值 调小(低延迟) 调大(省资源)
// ─────────────────────────────────────────────────────────
BalanceLeaderInterval = 300s → 180s → 600s
BalanceRegionInterval = 600s → 300s → 1200s
pushOperatorTickInterval = 500ms → 200ms → 1000ms
PatrolInterval = 10s → 5s → 30s
⚡ Troubleshooting 速查表
问题:Operator队列堆积
症状:opController.OperatorCount() 不断增加
原因:
1. 前置条件冲突多(如副本数不足状态下加副本)
2. TiKV执行慢(硬件、网络)
3. drivePushOperator频率不够
诊断:
metrics: operator_waiting_count / operator_running_count
if 等待数 > 运行数 × 10:
→ 说明前置条件冲突过多
→ 调整冲突检测逻辑
if 运行数 ≈ 等待数:
→ 说明TiKV执行慢
→ 检查TiKV日志/硬件
问题:决策响应慢(>1s)
症状:从Region心跳到operator下发超过1秒
原因:
1. 决策算法复杂(巡检扫描慢)
2. msgCh缓冲不足
3. gRPC吞吐不足
诊断:
分别测量时间:
1. PatrolRegions(): 应该 <100ms
2. PushOperators(): 应该 <200ms
3. HeartbeatStreams.Send(): 应该 <100ms
加起来应该 <500ms(等一个推送周期)
问题:Heartbeat推送失败
症状:metrics heartbeat_stream xxx err 率高
原因:
1. gRPC连接断开
2. TiKV故障
3. 网络波动
处理:
自动:stream.Send()失败 → 删除stream → 下次心跳重连
手动:检查TiKV日志和网络
📚 相关源文件导航
pkg/schedule/
├─ coordinator.go ⭐⭐⭐
│ └─ Run()、PatrolRegions()、drivePushOperator()
│
├─ hbstream/
│ └─ heartbeat_streams.go ⭐⭐⭐
│ └─ run()、SendMsg()
│
├─ operator/
│ └─ controller.go ⭐⭐
│ └─ PushOperators()、AddOperator()
│
└─ schedulers/
├─ scheduler_controller.go ⭐⭐
│ └─ 周期执行各scheduler
│
├─ balance_leader.go ⭐
│ └─ Leader均衡决策
│
└─ balance_region.go ⭐
└─ Region均衡决策
🎓 学习路线图
Day 1-2:理论基础
- 两层架构(CP + AP)理解
- ReportOptions 参数作用
- Operator 状态机
Day 3-4:代码走读
- Run() 启动流程
- drivePushOperator() 推送引擎
- HeartbeatStreams 消息流
Day 5-6:深度理解
- 四个工作线程分工
- msgCh 缓冲机制
- Operator 生命周期
Day 7+:实战应用
- 构建简单 Coordinator
- 性能调优
- 故障排查
💬 核心问答集
Q: 为什么不直接用Raft推送决策?
A: 延迟太高
Raft同步 = 主-从-多数派 → 10s+
直接推送 = PD→TiKV异步 → <1s
而且TiKV缓存operator,PD故障后继续执行
Q: msgCh缓冲为什么是1024?
A: 权衡内存和吞吐
1024条消息 × 500字节 ≈ 512KB可接受
500ms推送周期 = 1024/0.5s = 2048 ops/s吞吐足够
Q: 为什么要1分钟keepalive?
A: 防止gRPC连接自动关闭
TiKV可能长时间不心跳(如稳定集群)
1分钟keepalive = 确保连接活跃
Q: 决策冲突了怎么办?
A: TiKV自动处理
如果Region已经在执行另一个operator
TiKV忽略新的冲突operator
下次PD推送时会检测到旧operator已完成
🎯 核心洞察(务必记住)
-
分离是关键
- 心跳汇报(快)vs 决策生成(慢)
- CP层存储(重)vs AP层推送(快)
- 状态收集vs决策生成线程独立
-
缓冲是价值
- msgCh 1024缓冲 = 自动背压
- TiKV缓存已下发operator = 高可用
- 500ms推送周期 = 批量优化
-
Raft不对等
- Raft保证配置一致(持久化)
- 但执行指令的一致性靠心跳+重试
- 这不是bug,这是分布式系统的艺术
TiDB PD 通过 Raft 构建分布式协调器系统 - 深度解读
本文基于 TiDB PD v8.5.5 源码深度剖析,展示如何用 Raft 作为 CP 层构建高效的分布式资源调度系统
核心架构:两层融合(CP + AP)
架构全景图
┌─────────────────────────────────────────────────────────────┐
│ Coordinator(PD所有决策的中枢) │
│ │
│ CP层(Raft) 内存协调 │
│ ├─ 持久化存储配置 ├─ 生成Operator │
│ ├─ Scheduler持久化配置 ├─ Operator生命周期管理 │
│ └─ RegionState边界信息 └─ 驱动周期决策 │
│ ↓ │
│ AP层(Heartbeat) │
│ ├─ 缓冲通道(1024) │
│ ├─ 500ms推送间隔 │
│ ├─ 1分钟keepalive保活 │
│ └─ TiKV自治执行机制 │
└────────────────────────────────────────────────────────────┘
↓
┌──────────────────┐
│ TiKV Store │
│ 自治执行Operator │
│ 心跳上报进度 │
└──────────────────┘
1. CP 层:Raft 持久化决策基础
为什么需要 Raft?
PD 原生 embedded etcd(基于 Raft),存储:
- Scheduler 配置 - 哪些调度器启用,参数是什么
- ClusterVersion - 当前集群支持的特性版本
- RegionState - Region 边界、元数据、热点信息
// pkg/schedule/coordinator.go - InitSchedulers 方法
// 从 Raft 存储加载持久化配置
func (c *Coordinator) InitSchedulers(shouldCheckPersist bool) {
if shouldCheckPersist {
// 从etcd(Raft存储)加载所有scheduler配置
// 确保集群重启后调度策略不丢失
}
}
关键特性:
- ✅ 集群重启不丢失调度策略
- ✅ 多副本Raft保证强一致性
- ✅ 支持配置动态更新
2. 内存协调层:四大工作线程体系
2.1 启动流程(Run 方法)
// 第一阶段:等待集群信息充分准备
for {
if c.ShouldRun(collectWaitTime...) { // 等待足够的Region信息
log.Info("coordinator has finished cluster information preparation")
break
}
// 3秒检查一次
}
// 第二阶段:初始化调度器并启动4个工作线程
c.InitSchedulers(true)
c.wg.Add(4)
go c.PatrolRegions() // 线程1:区域巡检
go c.checkSuspectRanges() // 线程2:可疑范围检查
go c.drivePushOperator() // 线程3:心跳推送驱动(500ms周期)
go c.driveSlowNodeScheduler() // 线程4:慢节点调度
工作线程分职责:
| 线程 | 周期 | 职责 |
|---|---|---|
| PatrolRegions | 可配置 | 扫描所有region状态 |
| checkSuspectRanges | 可配置 | 检查未分裂的大key范围 |
| drivePushOperator | 500ms | 推送运算指令到TiKV |
| driveSlowNodeScheduler | 可配置 | 检测慢节点并触发 evict |
3. AP 层:心跳驱动的决策下发机制
3.1 心跳驱动的核心原理
TiKV Region Heartbeat
↓
+─────────────────────────────────────┐
│ HeartbeatStreams.run() │
│ ├─ 接收 stream 绑定更新 │
│ ├─ 接收 msgCh 中的Operator │
│ └─ 1分钟keepalive保活 │
└──────────────┬──────────────────────┘
↓
┌──────────────────┐
│ gRPC流发送 │
│ (async) │
└─────────┬────────┘
↓
TiKV执行Operator
3.2 ReportOptions:两个关键的时间参数
type ReportOptions struct {
// 心跳收集周期 - 快速感知集群状态变化
Interval time.Duration // 通常 10-30 秒
// 决策周期 - 避免频繁波动
BalanceLeaderInterval time.Duration // Leader 均衡周期
BalanceRegionInterval time.Duration // Region 均衡周期
}
为什么分开两个周期?
- 心跳(Interval)短 ✅ 快速感知热点、故障
- 决策(Balance)长* ✅ 避免抖动、汇总更多信息
实际配置示例(TiKV推荐):
心跳间隔: 10s (快速感知)
Leader决策: 300s (5分钟一次均衡)
Region决策: 600s (10分钟一次均衡)
3.3 HeartbeatStreams 的消息通道设计
// 缓冲大小为 1024,异步处理
msgCh chan core.RegionHeartbeatResponse // 1024缓冲
// 三层事件循环
for {
select {
case update := <-s.streamCh: // 高优先级:流更新
s.streams[update.storeID] = update.stream
case msg := <-s.msgCh: // 正常优先级:Operator消息
if stream, ok := s.streams[storeID]; ok {
stream.Send(msg) // 异步gRPC发送
}
case <-keepAliveTicker.C: // 低优先级:keepalive
for storeID, stream := range s.streams {
stream.Send(keepAlive)
}
}
}
风险处理:
- 如果 msgCh 满了? → 新的Operator会被块(PD不会丢消息)
- 如果 gRPC 发送失败? → 删除该stream,触发重连
- 如果 TiKV 长期离线? → 1分钟keepalive失败后清理stream
4. Operator 生命周期:从生成到执行
4.1 完整状态转移
创建 (Create)
↓
等待 (Waiting) ← 检查前置条件
↓ ↑
开始 (Started) ← 可能转移回等待(如冲突)
↓
进行中 (Running) ← 通过heartbeat推送给TiKV
↓ ↑
├─→ TiKV执行(自治)
↓
成功 (Success) / 超时 (Timeout) / 取消 (Canceled)
4.2 drivePushOperator:500ms 周期推送
func (c *Coordinator) drivePushOperator() {
ticker := time.NewTicker(pushOperatorTickInterval) // 500ms
defer ticker.Stop()
for {
select {
case <-c.ctx.Done():
return
case <-ticker.C:
// 关键调用:推送所有未完成的operator
c.opController.PushOperators(c.RecordOpStepWithTTL)
}
}
}
每个周期做什么?
- 遍历所有运行中的 Operator
- 生成 RegionHeartbeatResponse(含diff)
- 放入 msgCh 通道
- HeartbeatStreams.run() 异步发送给 TiKV
5. 核心数据结构:Operation(调度指令)
5.1 Operation 包含的决策类型
type RegionHeartbeatResponse struct {
RegionId uint64
RegionEpoch *RegionEpoch
TargetPeer *Peer // 哪个store是leader
// 六大类决策
ChangePeer *ChangePeer // 添加/删除副本
TransferLeader *TransferLeader // 转移leader
Merge *Merge // 合并两个region
SplitRegion *SplitRegion // 分裂region
ChangePeerV2 *ChangePeerV2 // 原子配置变更
SwitchWitnesses *SwitchWitnesses // 见证者模式
}
为什么用heartbeat推送而不是主动推送?
- ✅ 天然的流量控制(TiKV决定汇报频率)
- ✅ 快速反馈(同一连接中)
- ✅ 减少网络往返(piggyback到heartbeat上)
- ✅ TiKV缓存已下发operator,PD故障不影响执行
6. 两层架构的巧妙平衡
为什么 CP + AP 要分离?
问题场景1:纯CP系统(强一致)
PD决策 → 写入Raft → 等待确认 → 响应TiKV
问题:延迟高、吞吐低、可用性差(PD故障集群停滞)
问题场景2:纯AP系统(高可用)
PD决策 → 直接响应TiKV → 集群执行
问题:PD重启数据丢失、配置波动、不一致
PD 的解决方案(CP + AP融合)
CP层(Raft):
├─ 存储Scheduler配置 ← 保证重启不丢失
├─ 存储ClusterVersion ← 保证版本一致
└─ 存储RegionState ← 保证边界准确
⬇️
内存决策:快速生成Operator(无需等Raft)
AP层(Heartbeat):
├─ 缓冲发送(1024缓冲) ← 高吞吐
├─ TiKV自治执行 ← PD故障不影响
└─ 多次重试(500ms推送) ← 最终一致
效果:
- ✅ 决策延迟:100ms 级别(无需等Raft同步)
- ✅ 集群可用性:PD故障500ms内TiKV继续执行已下发operator
- ✅ 一致性:关键配置通过Raft保证,执行指令通过心跳重试保证
7. 关键设计模式详解
模式1:缓冲异步处理(无锁并发)
msgCh chan core.RegionHeartbeatResponse // 1024缓冲
// 发送端(多个scheduler并发生成operator)
select {
case s.msgCh <- resp:
case <-s.hbStreamCtx.Done():
}
// 接收端(单一协程消费)
for msg := <-s.msgCh {
if stream, ok := s.streams[storeID]; ok {
stream.Send(msg)
}
}
优势:
- 零锁竞争(channel内部自己同步)
- 自动背压(缓冲满了新scheduler会阻塞)
- 顺序保证(单消费者)
模式2:事件驱动的优先级队列
for {
select {
case update := <-s.streamCh: // 最高优先级:流更新
// 立即处理
case msg := <-s.msgCh: // 中优先级:Operator消息
// 按来的顺序处理
case <-keepAliveTicker.C: // 低优先级:超时事件
// 周期处理
}
}
这是Go 原生的优先级队列实现(比传统堆更清晰)。
模式3:两层缓冲(gRPC流 + 通道)
┌──────────────────┐
│ Operator Queue │ (内存Operator队列)
│ (opController) │
└────────┬─────────┘
│ 推送到
⬇️
┌──────────────────┐
│ msgCh (1024) │ (通道缓冲)
└────────┬─────────┘
│ 异步发送
⬇️
┌──────────────────┐
│ gRPC流 Send() │ (系统缓冲,通常64KB)
└────────┬─────────┘
│
⬇️
TiKV
为什么三层?
- 解耦heartbeat发送速度和operator生成速度
- 缓冲突刺流量
- 支持背压机制
8. 实战经验:如何应用到自己的系统
场景1:构建数据库集群协调器
原则:
CP核心 AP传递
├─ 配置存储 ├─ 快速下发
├─ 元数据 ├─ 缓冲重试
└─ 关键决策 └─ TiKV自执行
实现步骤:
- 用 etcd 存储不可变的配置和边界信息
- 在内存中快速生成执行指令(无需Raft同步)
- 通过心跳流缓冲推送,支持重试
- 客户端自治执行(不依赖PD连接保活)
场景2:优化决策延迟
关键参数:
type ReportOptions struct {
Interval // 心跳汇报间隔(10-30s)
BalanceLeaderInterval // Leader决策周期(300s)
BalanceRegionInterval // Region决策周期(600s)
}
调优思路:
- 增加心跳间隔 → 减少CPU成本,但感知延迟增加
- 减少决策周期 → 快速响应,但可能频繁波动
推荐配置:
心跳: 10s (感知热点)
决策: 300-600s (避免抖动)
场景3:处理高并发operator生成
PD的做法:
// 多个scheduler并发push到msgCh(1024缓冲)
// 单个consumer从msgCh pop,通过gRPC发送
// 容错:
// - msgCh满 → scheduler阻塞(背压)
// - gRPC失败 → stream删除,触发重连
// - TiKV离线 → 1分钟后清理stream
9. 源码地图
pkg/schedule/
├─ coordinator.go ← 四大线程启动
├─ operator/
│ └─ controller.go ← Operator生命周期
├─ schedulers/
│ ├─ scheduler_controller.go ← 周期执行
│ ├─ balance_leader.go ← Leader均衡决策
│ └─ balance_region.go ← Region均衡决策
└─ hbstream/
└─ heartbeat_streams.go ← 心跳推送机制
总结:Raft 在分布式协调器中的角色
| 层次 | 技术 | 作用 | 特点 |
|---|---|---|---|
| CP层 | Raft(etcd) | 配置+元数据 | 强一致, 重启不丢 |
| 内存层 | Lock-free | 决策生成 | 毫秒延迟 |
| AP层 | Heartbeat | 推送执行 | 高可用, 最终一致 |
关键洞察:
Raft不是用来保证执行一致性的,而是保证决策配置一致性
执行的一致性通过"心跳+客户端缓存+重试"实现的
这是分布式系统的大智慧:分离存储一致性和执行可用性
推荐深读
- coordinator.go - 架构全景
- heartbeat_streams.go - AP层消息流
- operator/controller.go - Operator管理
- balance_leader.go - 具体决策算法示例