本文基于 TiDB 6.0+ 的 Placement Rules in SQL 特性,深入剖析其底层调度原理,并针对"旧集群扩容新节点实现业务隔离"的典型场景提供完整操作指南。
一、为什么需要 Placement Rules
在 TiDB 集群中,数据默认以 Region(默认 96MB)为单位自动均衡分布在所有 TiKV 节点上。这种"全自动"模式虽然简化了运维,但也带来了以下痛点:
- 多业务混部干扰:测试环境多个数据库共用 3 台 TiKV,CPU/IO 互相争抢。
- 无法物理隔离:不同业务的数据在磁盘层面交织在一起,故障会相互影响。
- 冷热数据无法分离:历史归档数据和热数据挤在同一批 SSD 上,成本高昂。
- 跨机房部署不可控:无法指定某份数据必须落在哪个机房、哪批机器。
Placement Rules 的本质是:将 PD 内部的调度能力通过 SQL 暴露给用户,让用户可以基于 TiKV 节点的物理标签(Label)精确控制数据副本的存放位置。
二、核心架构:三个角色的协作
理解 Placement Rules 必须先理解 TiDB 存储层的三个核心角色如何协作:
┌─────────────────────────────────────────────────────────────┐
│ TiDB 集群 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ TiDB │ │ TiDB │ │ TiDB │ 无状态 SQL 层 │
│ │ Server │ │ Server │ │ Server │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └──────────────┼──────────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ │ ▼ │ PD 调度层 │
│ │ ┌─────────────────────────────────┐ │ │
│ │ │ Placement Driver (PD) │ │ │
│ │ │ - 存储集群元数据 │ │ │
│ │ │ - 维护 Region 路由表 │ │ │
│ │ │ - 产生调度指令(副本增减、迁移) │ │ │
│ │ │ - 解析 Placement Policy │ │ │
│ │ └─────────────────────────────────┘ │ │
│ └─────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ │ ▼ │ TiKV 存储层 │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │TiKV │ │TiKV │ │TiKV │ │TiKV │ │ 多副本持久化 │
│ │ │Node1│ │Node2│ │Node3│ │Node4│ │ │
│ │ │zone1│ │zone1│ │zone2│ │zone2│ │ Label: zone,host│
│ │ └─────┘ └─────┘ └─────┘ └─────┘ │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
2.1 TiKV:带"门牌号"的仓库
每个 TiKV 节点在启动时携带 Labels(标签),这些标签就是它的"门牌号":
# tikv 启动参数
--labels zone=zone1,host=host13-1,disk=ssd
| Label | 含义 | 作用 |
|---|---|---|
zone |
可用区/机房 | 跨机房容灾调度 |
host |
物理机 | 单机容灾调度 |
disk |
磁盘类型 | 冷热分离调度 |
rack |
机架 | 机架级容灾 |
重要:TiKV 只负责"上报自己的标签",它不知道这些标签会被如何使用。
2.2 PD:调度大脑与"翻译官"
PD 是 Placement Rules 的核心执行者,它承担两个关键职责:
职责 A:理解拓扑层级(location-labels)
PD 的配置文件 pd.toml 中有一个关键配置:
[replication]
location-labels = "zone,host"
max-replicas = 3
这里的 location-labels 是 PD 的"翻译字典"。它告诉 PD:
"TiKV 节点上报的众多标签中,
zone和host这两个键名代表物理位置层级。调度副本时必须保证同一 Region 的多个副本分散在不同的 zone 和不同的 host 上。"
为什么必须有这个配置?
假设 TiKV 带了这些标签:zone=zone1,host=host13-1,disk=ssd,version=v7.5.0
PD 收到后,如果不配置 location-labels,它无法区分:
disk=ssd只是磁盘属性,与位置无关zone=zone1代表机房位置,与容灾强相关
配置了 location-labels = "zone,host" 后,PD 就能构建出集群的拓扑树:
zone1
├── host13-1 (TiKV-1)
├── host14-1 (TiKV-2)
└── host15-1 (TiKV-3)
zone2
├── host16-1 (TiKV-4)
├── host17-1 (TiKV-5)
└── host18-1 (TiKV-6)
职责 B:执行 Placement Policy
当用户通过 SQL 创建 Placement Policy 时,TiDB 会将策略转换为规则下发给 PD。PD 结合 location-labels 解析这些规则,并产生具体的调度指令:
- Add Learner:在目标节点添加副本(Learner 角色,不参与投票)
- Promote Learner:Learner 数据追齐后,提升为 Follower
- Remove Replica:从旧节点移除多余副本
- Transfer Leader:将 Leader 切换到符合约束的节点
这些调度指令通过心跳机制异步下发给 TiKV 执行。
2.3 TiDB:策略的翻译器与接口层
TiDB 负责将用户友好的 SQL 语句翻译为 PD 能理解的规则格式:
-- 用户输入
CREATE PLACEMENT POLICY p1 CONSTRAINTS="[+zone=zone2]";
-- TiDB 内部转换为 PD 规则(简化示意)
{
"group_id": "TiDB_DDL",
"id": "table_t1",
"start_key": "7480000000000000ff...",
"end_key": "7480000000000000ff...",
"constraints": [{"key": "zone", "op": "in", "values": ["zone2"]}],
"count": 3
}
三、深入工作原理:Region 调度的完整生命周期
3.1 数据分片与 Raft 副本
TiDB 将数据按表/分区划分为多个 Region,每个 Region 默认约 96MB。每个 Region 在 TiKV 层面对应一个 Raft Group,通常包含 3 个副本:
- 1 个 Leader:负责处理读写请求,强一致性写入需 Leader 确认
- 2 个 Follower:通过 Raft 日志复制保持与 Leader 的数据一致
Region-100:
Leader → TiKV-1 (zone1, host13-1)
Follower → TiKV-2 (zone1, host14-1)
Follower → TiKV-3 (zone1, host15-1)
3.2 Placement Policy 绑定后的调度流程
当你执行 ALTER DATABASE db_new PLACEMENT POLICY = zone2_policy 时,底层发生了以下事情:
阶段 1:规则下发(秒级)
- TiDB 将
db_new下所有表的 Region 范围与zone2_policy关联 - 生成新的 Placement Rules 推送到 PD
- PD 的规则引擎立即生效,开始评估现有 Region 分布
阶段 2:调度产生(异步后台)
PD 的 replicationChecker 和 ruleChecker 模块对每个 Region 进行检查:
- 目标状态:Region 的 3 个副本都应在
zone=zone2的节点上 - 当前状态:Region 的 3 个副本都在
zone=zone1的节点上 - 差异检测:需要产生"迁移调度"
PD 产生的调度计划(简化):
Region-100: 需要从 zone1 → zone2
Step 1: 在 TiKV-4 (zone2) 添加 Learner 副本
Step 2: 等待 Learner 数据追齐(Snapshot + Raft log)
Step 3: 将 Learner 提升为 Follower
Step 4: 删除 TiKV-1 (zone1) 上的旧副本
Step 5: 重复直到 zone1 副本数为 0,zone2 副本数为 3
阶段 3:调度执行(TiKV 侧)
TiKV 收到 PD 的调度指令后,执行副本搬迁:
- Snapshot 传输:新节点(TiKV-4)从 Leader(TiKV-1)拉取全量 Snapshot
- Raft 日志追齐:Snapshot 之后的新写入通过 Raft 日志同步
- 角色切换:数据追齐后,PD 将 Learner 提升为 Follower
- 旧副本清理:新副本确认可用后,旧节点上的副本被安全删除
参考:TiKV 副本搬迁原理
阶段 4:持续均衡
迁移完成后,PD 的 balance-region-scheduler 会继续在 zone2 的 3 台节点之间微调 Region 分布,确保磁盘空间和 QPS 均衡。
3.3 已有数据是否自动迁移?
是的,完全自动。
- 绑定 Placement Policy 后,PD 会立即对所有相关 Region 产生迁移调度。
- 迁移速度受 PD 参数控制(如
region-schedule-limit、leader-schedule-limit)。 - 迁移过程中,业务读写不受影响,因为每个 Region 始终保持多数派副本可用。
- 可以通过监控面板(TiDB Dashboard / Grafana)查看
Region health、Region pending等指标观察进度。
3.4 新数据写入去哪里?
- 如果某张表的 Region 已经完成了迁移,新写入会直接路由到 zone2 的新节点。
- 如果某 Region 还在迁移中(新旧副本并存),新写入仍由 Leader 处理,Leader 可能暂时在旧节点,但写入会通过 Raft 同步到新节点的 Learner。
- 迁移完成后,PD 会触发 Leader Transfer,将 Leader 也转移到 zone2 节点上(如果策略要求)。
四、关键配置深度解析
4.1 常规放置选项(适合简单场景)
| 选项 | 说明 | 底层含义 |
|---|---|---|
PRIMARY_REGION |
指定 Raft Leader 放在哪个 region | 对 Leader 添加 +region=xxx 约束 |
REGIONS |
指定 Follower 可放在哪些 region | 对 Follower 添加 +region=xxx 约束 |
SCHEDULE |
Follower 分布策略 | EVEN 均匀分布 / MAJORITY_IN_PRIMARY 主 region 多数派 |
FOLLOWERS |
Follower 数量 | FOLLOWERS=2 = 3 副本(1 Leader + 2 Follower) |
限制:PRIMARY_REGION/REGIONS/SCHEDULE 不能与 CONSTRAINTS 同时混用,因为它们底层逻辑冲突。
4.2 高级放置选项(适合复杂场景)
| 选项 | 说明 | 示例 |
|---|---|---|
CONSTRAINTS |
适用于所有角色(Leader + Follower)的约束 | CONSTRAINTS="[+zone=zone2]" |
LEADER_CONSTRAINTS |
仅约束 Leader 的位置 | LEADER_CONSTRAINTS="[+zone=zone2]" |
FOLLOWER_CONSTRAINTS |
仅约束 Follower 的位置,支持字典格式指定副本数 | FOLLOWER_CONSTRAINTS="{+zone=zone2: 2}" |
LEARNER_CONSTRAINTS |
仅约束 Learner 的位置 | LEARNER_CONSTRAINTS="[+zone=zone1]" |
LEARNERS |
Learner 数量 | LEARNERS=1(额外加 1 个只读副本) |
SURVIVAL_PREFERENCE |
副本容灾优先级 | SURVIVAL_PREFERENCE="[region, zone, host]" |
4.3 约束语法详解
-- 列表格式:所有副本放在 zone2
CONSTRAINTS="[+zone=zone2]"
-- 列表格式:数据放在 SSD 上,同时排除故障节点
CONSTRAINTS="[+disk=ssd, -host=host13-1]"
-- 字典格式:在 zone2 放 2 个 Follower,在 zone1 放 1 个 Follower
FOLLOWER_CONSTRAINTS="{+zone=zone2: 2, +zone=zone1: 1}"
-- 冒号后必须有空格,否则 YAML 解析可能出错!
-- 错误:"{+zone=zone2:1}" → 可能解析为 {"+zone=zone2:1": null}
-- 正确:"{+zone=zone2: 1}" → 解析为 {"+zone=zone2": 1}
| 符号 | 含义 |
|---|---|
+ |
必须满足(必须在该标签的节点上) |
- |
必须排除(不能在该标签的节点上) |
五、用户场景实战:旧集群 + 新节点隔离
5.1 场景描述
- 现有集群:3 台 TiKV(host13-1, host14-1, host15-1),label 均为
zone=zone1 - 新增节点:3 台 TiKV(host16-1, host17-1, host18-1),计划 label 为
zone=zone2 - 目标:将数据库
db_new的所有数据迁移到新节点,后续新数据也写入新节点,实现与旧集群的物理隔离
5.2 前置条件检查
检查 1:确认 PD 已配置 location-labels
# 使用 pd-ctl 或 tiup ctl 查看
$ tiup ctl:v7.5.0 pd config show | grep location-labels
"location-labels": "zone,host"
如果未配置,需修改 pd.toml 并重启 PD(生产环境建议滚动重启):
[replication]
location-labels = "zone,host"
max-replicas = 3
核心理解:PD 本身不存储数据,不需要 label。它只需要
location-labels来理解 TiKV 标签的语义层级。没有这个配置,PD 把zone当普通字符串,不会按机房/机器维度做容灾隔离。
检查 2:确认新 TiKV 已正确配置 Label 并加入集群
新 TiKV 启动参数:
# host16-1
tikv-server --labels zone=zone2,host=host16-1
# host17-1
tikv-server --labels zone=zone2,host=host17-1
# host18-1
tikv-server --labels zone=zone2,host=host18-1
验证节点状态:
-- 查看 TiKV 可用标签
SHOW PLACEMENT LABELS;
-- 期望输出
+------+----------------------------------+
| Key | Values |
+------+----------------------------------+
| host | ["host13-1","host14-1","host15-1","host16-1","host17-1","host18-1"] |
| zone | ["zone1", "zone2"] |
+------+----------------------------------+
5.3 创建 Placement Policy
-- 创建 zone2 专用策略:所有副本(1 Leader + 2 Follower)都放在 zone2
CREATE PLACEMENT POLICY zone2_isolation
CONSTRAINTS = "[+zone=zone2]";
-- 验证策略创建
SHOW CREATE PLACEMENT POLICY zone2_isolation\G
底层发生了什么?
- TiDB 在
mysql.placement_policies系统表中保存了策略定义 - 同时向 PD 注册了一个名为
zone2_isolation的规则模板 - PD 的规则引擎现在知道:当某个 Region 被标记为使用
zone2_isolation时,其副本必须满足zone=zone2
5.4 绑定策略到数据库
-- 已有数据库迁移到新节点
ALTER DATABASE db_new PLACEMENT POLICY = zone2_isolation;
-- 后续创建新数据库直接指定
CREATE DATABASE db_future PLACEMENT POLICY = zone2_isolation;
绑定后的调度流程:
- TiDB 扫描
db_new下的所有表和分区,获取它们的 Region Key Range - 将这些 Key Range 与
zone2_isolation规则关联,推送给 PD - PD 对每个相关 Region 执行检查:
- 当前副本分布在 zone1 → 不满足规则
- 产生迁移调度 → 逐步迁移到 zone2
- 后台异步搬迁,业务无感知
5.5 监控迁移进度
-- 查看所有 Placement 绑定状态
SHOW PLACEMENT;
-- 查看具体 Region 的分布(通过 TiDB 系统表)
SELECT
p.db_name, p.table_name, p.partition_name,
p.policy_name, p.scheduling_state
FROM information_schema.placement_policies p;
Grafana 监控面板关注:
- PD → Region health:观察
pending-peer-region-count和miss-peer-region-count - TiKV → Cluster:观察各节点 Region 数量变化
- TiKV → RocksDB:迁移期间的磁盘 IO 和网络带宽
5.6 验证最终分布
# 使用 pd-ctl 查看某个 Region 的副本分布
$ tiup ctl:v7.5.0 pd region <region_id>
# 期望看到 peers 分布在 zone2 的 3 个 host 上
"peers": [
{"id": 101, "store_id": 4, "role": "Leader"}, # host16-1
{"id": 102, "store_id": 5, "role": "Follower"}, # host17-1
{"id": 103, "store_id": 6, "role": "Follower"} # host18-1
]
六、常见误区与注意事项
6.1 误区一:PD 也需要配置 label
错误理解:"PD 是不是也要配置 zone=zone1 的标签?"
正确理解:PD 不存储数据副本,它只负责"看懂"TiKV 的 label 并产生调度指令。PD 需要的是 location-labels 配置来建立"拓扑语义",而不是自身的标签。
6.2 误区二:设置 Label 后数据立即迁移
注意:
- Label 设置只是前置条件,不会自动触发迁移。
- 必须创建并绑定 Placement Policy 后,PD 才会产生调度。
- 迁移是异步后台过程,大规模数据可能需要数小时甚至更久。
6.3 误区三:FOLLOWERS=2 就一定有 3 副本
注意:如果 CONSTRAINTS 匹配的节点数量不足,实际副本数可能低于预期。例如:
CREATE PLACEMENT POLICY p1 CONSTRAINTS="[+zone=zone2]" FOLLOWERS=2;
如果 zone2 只有 2 台 TiKV,那么最多只能产生 2 个副本(1 Leader + 1 Follower),无法满足 3 副本。因此约束条件必须匹配足够多的节点。
6.4 生产环境建议
| 建议项 | 说明 |
|---|---|
| 策略数量 | 单个集群 Placement Policy 不超过 10 个 |
| 绑定对象数 | 绑定策略的表/分区总数不超过 10000 个 |
| 复杂度 | 优先使用简单约束,避免多层嵌套复杂规则 |
| 容灾 | 3 副本分布在 3 台不同 host,建议进一步增加 rack 标签实现跨机架 |
| 监控 | 迁移期间关注 pd_checker 和 tikv_snapshot 相关指标 |
| 回退 | 绑定策略后如需回退,执行 ALTER DATABASE xxx PLACEMENT POLICY = default; |
七、总结
| 概念 | 一句话解释 |
|---|---|
| TiKV Label | TiKV 节点启动时携带的"门牌号",标识自己的物理位置 |
| PD location-labels | PD 的"翻译字典",告诉 PD 哪些标签键名代表物理位置层级 |
| Placement Policy | 通过 SQL 定义的数据存放规则,约束 Region 副本的位置 |
| 调度流程 | PD 检查规则 → 产生迁移计划 → TiKV 执行 Snapshot 搬迁 → 异步完成 |
| 数据迁移 | 绑定策略后,已有数据自动迁移,新数据直接写入目标节点 |
你的场景结论:
- ✅ 新 3 台 TiKV 直接设置
zone=zone2,host=hostxx-1的 label,完全可行。 - ✅ 创建
CONSTRAINTS="[+zone=zone2]"的 Placement Policy 并绑定到数据库。 - ✅ 已有数据会自动后台迁移,新数据会直接写入 zone2。
- ⚠️ 确保 PD 已配置
location-labels = "zone,host",否则调度不生效。
如需进一步了解 TiKV 副本搬迁的底层实现(Snapshot 传输、Raft 日志同步、流量控制等),可参考官方资料