声明:学习过程中的个人解读,不正之处,欢迎各位指正。
很多人第一次接触 TiDB,都会把 Region 想象成图书馆里一个个固定的书架格子。
但这个比喻其实不够准确。
Region 不是一个物理格子,而是 Key 空间中的一段连续区间。
更有意思的是,这段区间还会动态变化:
数据太多了,它会分裂;副本负载不均,它可以搬家;两个相邻的小区间,还可以重新合并。
所以,与其把 Region 想象成一个固定书架格子,不如把它想象成:
一段会“分裂、搬家、合并”的图书编号区间。
01 一个常见的误解
在图书馆里,我们习惯这样找书:
先看图书编号,比如:
A000 ~ B000
然后走到对应的书架,在相应位置找到书。
于是很多人第一次接触 TiDB 时,会自然地产生一个类比:
TiDB 的 Region,是不是就像 TiKV 磁盘上的一个个数据格子?
不是。
Region 不是一个独立的物理存储格子。
它没有一个与自己一一对应的独立磁盘文件,也没有一个专门负责它的进程,更没有一个永远固定的物理位置。
它真正描述的是:
一段连续的 Key 范围 [StartKey, EndKey),以及负责这段范围的多副本 Raft 状态。
因此,更准确地说:
Region 是一个逻辑数据分片,而不是一个独立的物理存储设备。
这里还有一个很容易混淆的地方:
Region 也不是 SQL 层面的 Table。
一张表的数据通常会随着增长被划分到多个 Region 中。
Region 工作在 TiKV 的 KV 层,它关心的是:
“这一段 Key 属于谁?”
而不是:
“这是哪张 SQL 表?”
02 用图书馆比喻,重新理解 Region
我们把这个比喻重新设计一下:
- Key:每本书的图书编号
- Region:一段连续的图书编号区间
- Peer:某个分馆中保存的这段区间的一个副本
- Leader:当前负责处理请求的那个 Peer
- Raft Group:保存同一份数据的多个管理员组成的小组
- TiKV Store:一个分馆
- RocksDB:分馆的底层书库
- PD:总调度中心
- RegionCache:目录系统里缓存的“图书编号 → 区间 → 负责人 → 分馆”信息
比如现在有一本图书编号为:
A123
假设它属于:
[A000, B000)
那么整个过程可以理解成:
- Peer 1 → TiKV-A
- Peer 2 → TiKV-B
- Peer 3 → TiKV-C
- 图书编号
A123 - 属于区间
[A000, B000) - 这个区间对应
Region 100 Region 100有三个 Peer:- 其中一个 Peer 是 Leader
客户端并不需要知道:
“A123 在 TiKV-B 的 RocksDB 哪个 SST 文件里?”
它只需要知道:
A123 属于哪个 Region,以及应该把请求发给哪个 Leader。
至于书最终放在分馆书库里的哪个架子上,是底层存储引擎负责的事情。
所以,Region 更像是一种:
“图书编号区间责任制”。
它关心的是:
“这段 Key 归我管。”
而不是:
“这些数据必须放在某一个固定物理格子里。”
03 Region、Peer、Raft Group,到底是什么关系?
理解 Region,最容易混淆的就是这三个概念:
Region、Peer、Raft Group。
可以把它们理解成三个不同层次。
第一层:Region
Region 描述的是:
哪一段 Key 空间属于这个数据分片。
例如:
- Region 100
- StartKey = A000
- EndKey = B000
它表示:
[A000, B000)
这一段 Key。
第二层:Peer
一个 Region 通常不会只保存一份。
为了高可用,同一个 Region 会有多个副本。
这些副本就是:
Peer。
例如:
- Peer 1 → TiKV-A
- Peer 2 → TiKV-B
- Peer 3 → TiKV-C
- Region 100 有三个 Peer:
所以不要简单地记成:
Region = 逻辑Peer = 物理
更准确的说法是:
Region 是逻辑数据分片;Peer 是这个 Region 在某个 TiKV Store 上的一个副本。
一个 TiKV Store 上可以同时承载大量不同 Region 的 Peer。
第三层:Raft Group
同一个 Region 的多个 Peer,会通过 Raft 保持一致。
因此:
- Peer 1
- Peer 2
- Peer 3
- Region 100 对应一个 Raft Group
- 这个 Raft Group 包含:
其中一个 Peer 会承担:Leader角色。
Leader 负责处理该 Region 的主要写请求,并协调 Raft 复制。
所以可以用一句话记住:
Region 决定“管哪段 Key”,Peer 决定“这段数据在哪些 TiKV 上有副本”,Raft 决定“这些副本如何保持一致”。
04 Region 为什么不是一个物理实体?
因为 Region 的职责,本质上是描述和管理:一段 Key 空间。
它并不需要独占某个物理文件。
在 TiKV 中,实际数据最终由底层存储引擎保存,例如 RocksDB。
因此你可以把整个关系理解成:
- Peer 1 在 TiKV-A
- Peer 2 在 TiKV-B
- Peer 3 在 TiKV-C
- 最上层是 Region,负责 Key 范围
[A000, B000) - 这个 Region 有三个 Peer:
- 每个 TiKV 底层都有自己的 RocksDB
- 数据最终由 RocksDB 组织存储
这里最重要的一点是:
Region 有自己的逻辑边界,但它并不等于一个独立的物理文件。
底层数据具体如何组织,是 TiKV 存储引擎的事情。
因此,看到:
- Region 100
- StartKey = A000
- EndKey = B000
并不能简单理解成:
“磁盘上一定存在一个叫 Region-100 的文件,里面只存 A000~B000。”
这种理解是不准确的。
05 这么设计,到底高级在哪?
如果 Region 真的是一个固定的物理格子,那么很多操作都会变得非常笨重:
扩容时,需要搬动整个格子;负载不均时,需要搬动整个格子;副本迁移时,需要复制整个格子;数据过大时,需要重新组织物理文件;故障恢复时,也需要围绕物理块进行迁移。
而 TiKV 把:“数据属于哪一段 Key”和“这段数据当前有哪些副本、这些副本在哪里”分开处理。
这就产生了很大的灵活性。
优点一:Region 可以动态 Split
假设:
- Region 100 的范围是
[A000, Z000)
随着数据不断增长,这个 Region 变得太大。
它可以 Split 成:
- Region 100:
[A000, M000) - Region 101:
[M000, Z000)
原来一个逻辑数据分片,现在变成两个。
这里需要特别注意:
Region Split 不能简单理解成“只修改一条元数据”。
Split 的核心是:
改变 Region 的 Key 范围;创建新的 Region 状态;更新对应的 Raft/Peer 状态;更新路由信息。
至于底层数据具体如何组织,则由 TiKV 的存储引擎负责。
因此,更准确的理解是:
Split 改变的是逻辑分片边界和对应的 Raft 状态,而不是像搬运一个“物理格子”那样搬走整块磁盘空间。
具体 ID 分配由 TiKV 内部决定,这里只是示意。
优点二:相邻 Region 可以 Merge
如果两个 Region 都很小:
- Region 100:
[A000, M000) - Region 101:
[M000, Z000)
满足相应的 Merge 条件后,它们可以合并成更大的 Region。
合并后:
- Region 100:
[A000, Z000)
Merge 的目的,是降低过多小 Region 带来的管理和调度开销。
但同样不能简单理解成:
“PD 把两条元数据记录删掉,然后改成一条。”
真正的 Merge 是 TiKV/PD 协同完成的 Region 合并过程,会涉及 Region 状态和 Raft 状态的变化。
通常要求两个 Region 的副本集合相同或先通过调度对齐,然后才能合并。
所以,Region 的逻辑边界可以动态变化,但这并不意味着底层物理存储完全不参与这个过程。
优点三:副本可以“搬家”
再看一个非常重要的场景。
假设:
- Peer 1 → TiKV-A
- Peer 2 → TiKV-B
- Peer 3 → TiKV-C
- Region 100 有三个 Peer:
现在 TiKV-A 太热,或者集群需要重新平衡。
PD 可以调度这个 Region 的副本,把 Peer 从 A 移到 D。
可以粗略理解为:
原来:
- TiKV-A:Peer 1
- TiKV-B:Peer 2
- TiKV-C:Peer 3
后来:
- TiKV-B:Peer 2
- TiKV-C:Peer 3
- TiKV-D:Peer 4
Region 的 Key 范围仍然是:
[A000, B000)
变化的是:
这个 Region 的副本分布。
在实际过程中,通常会先在目标 Store 上创建 Learner,通过 Raft/Snapshot 等机制追赶数据,满足条件后转为 Follower,再删除源 Store 上的旧 Peer。
所以这里有一个非常漂亮的架构分离:
Region 不需要跟着物理节点一起“搬家”。
搬的是Peer,而不是Region 这个逻辑 Key Range。
06 高可用:Region 背后其实是一组 Raft 副本
为什么一个 Region 要有多个 Peer?
因为单副本不具备高可用能力。
例如:
- Peer 1 → TiKV-A
- Peer 2 → TiKV-B
- Peer 3 → TiKV-C
- Region 100 有三个 Peer:
假设 TiKV-A 宕机:
- TiKV-A ❌
- TiKV-B ✓
- TiKV-C ✓
只要剩余副本满足 Raft 的多数派要求,Region 仍然可以继续提供服务。
如果原来的 Leader 挂掉,那么其他 Peer 可以通过 Raft 选举产生新的 Leader。
如果某个副本长期缺失,PD 会根据集群状态调度新的 Peer,逐渐恢复期望的副本数量和分布。
因此Region 是逻辑分片,而 Raft Group 为这个分片提供了复制和一致性能力。
这两者结合起来,才构成 TiKV 的高可用数据单元。
07 RegionEpoch:Region 为什么知道自己的“旧路由”已经过期?
Region 还有一个非常关键的概念:RegionEpoch。
为什么需要它?
因为 Region 并不是永远不变的。
例如最开始:
- Region 100:
[A000, Z000)
后来发生 Split:
- Region 100:
[A000, M000) - Region 101:
[M000, Z000)
那么客户端之前缓存的:
[A000, Z000)
显然已经过时。
如果客户端还拿着旧信息访问 TiKV,就必须有办法让 TiKV 告诉它:
“你手里的 Region 信息已经不是最新的了。”
RegionEpoch 就承担了这个作用。
它可以理解为:Region 拓扑的版本信息。
其中:ConfVer 主要反映副本配置变化;Version 主要反映 Region 范围变化,例如 Split、Merge。
因此RegionEpoch 不是普通意义上的“软件版本号”,而更像 Region 拓扑变化的版本标记。
当客户端携带的 Region 信息已经过期,
TiKV 可能返回:EpochNotMatch
客户端随后刷新 RegionCache,再重新定位请求。
如果 Leader 发生变化,还可能遇到:NotLeader
客户端同样可以根据返回信息重新寻找正确的 Leader。
这就是为什么Region 可以动态 Split、Merge、迁移,而上层客户端仍然能够继续工作。
08 从 SQL 到 TiKV:一次请求到底怎么找到 Region?
把前面的概念串起来,就能理解 TiDB 的请求路径。
假设 SQL 最终需要访问某个 Key:
A123
大致可以理解成:
- SQL
- → TiDB Server
- → 生成 Key
- → RegionCache
- → A123 属于哪个 Region?
- → Region 100
[A000, B000) - → Leader 在哪里?
- → TiKV-A
- → Raft Group
- → RocksDB
TiDB Server 并不需要知道:“A123 在 RocksDB 的哪个 SST 文件里?”
它主要需要知道:A123 属于哪个 Region,以及这个 Region 的 Leader 在哪里。
如果本地 RegionCache 没有对应信息,就可以向 PD 查询。
所以可以把整个过程浓缩成:
- Key
- → Region
- → Leader Peer
- → TiKV
- → RocksDB
这就是 TiDB 分布式架构非常重要的一层解耦。
读请求同样会与 Raft Group 交互,例如通过 ReadIndex 确认已 apply 的索引,以保证读到最新已提交数据。
09 Region Split 和 Hot Region,不是一回事
这里还要特别区分两个概念。
Region Split 解决的是:一个 Region 太大了。
例如:
- 一个超大 Region
- → Split 成多个 Region
它主要改变:数据分片的粒度。
Hot Region 调度解决的是:某些 Region 的访问压力太大,或者负载分布不均。
例如:
- TiKV-A:████████████████████
- TiKV-B:█████
- TiKV-C:████
PD 可以通过 Leader/Peer 调度等方式,让负载更加均衡。
因此Split 是“把一个大区间切小”;调度是“把副本和 Leader 分布得更合理”。
两者经常一起出现,但解决的问题并不一样。
10 Region 什么时候 Split?
TiKV 会持续统计 Region 的相关信息,例如:
Region 数据大小;Key 数量;与 Region 负载相关的统计信息。
当 Region 达到相应 Split 条件时,就可能触发分裂。
具体阈值和机制会受到 TiDB/TiKV 版本及相关配置影响,因此不要把某一个固定数字当成所有版本的默认值。
最核心的认知只有一句:
Region Split 的本质,是把一个过大的 Key Range 切成多个更合适的数据分片。
11 Region 什么时候 Merge?
反过来,如果集群里出现大量非常小的 Region,就会增加:
Region 元数据;Raft Group 管理成本;调度开销;RegionCache 等相关管理成本。
因此 PD 会根据相应的 Merge 条件寻找可以合并的 Region。
通常会关注:
Region 是否相邻;Region 大小是否合适;是否满足相关调度条件;是否满足 Split/Merge 的时间间隔等约束。
最终:
[A000, B000)- 加上
[B000, C000)
可能重新变成:
[A000, C000)
所以 Region 并不是:“一旦创建,就永远固定不变。”
恰恰相反Region 的边界本身就是动态的。
12 真正理解 Region,要记住这四句话
如果把整篇文章压缩成四句话,我建议记住:
Region 决定“哪一段 Key 归谁管”。
Peer 决定“这段数据在哪些 TiKV 上有副本”。
Raft 决定“这些副本如何保持一致”。
PD 决定“这些副本应该如何分布和调度”。
再加上一层:
RegionCache 让客户端不用每次都去问 PD,而是可以缓存 Key → Region → Leader 的路由信息。
这样,整个架构就串起来了。
13 回到最开始的图书馆
现在再回头看图书馆的比喻。
Region 不是:
❌ 一个固定的书架格子。
它更像:
✅ 一段图书编号区间。
比如:
[A000, B000)
这段区间由一个 Raft Group 负责。
这个 Raft Group 有多个 Peer:
- Peer 1 → 分馆 A
- Peer 2 → 分馆 B
- Peer 3 → 分馆 C
其中一个 Peer 是 Leader。
如果数据太多:
[A000, B000)- → Split
- →
[A000, A500)+[A500, B000)
如果副本分布不合理:
- Peer
- → 搬到另一个 TiKV
如果两个 Region 太小:
[A000, A500)+[A500, B000)- → Merge
- →
[A000, B000)
所以它真正有趣的地方就在这里:
它不是一个固定格子,而是一套可以动态调整“责任边界”和“副本位置”的机制。
14 总结:Region 的“虚”,恰恰是它的力量
Region 是 TiDB/TiKV 中非常核心的逻辑数据分片。
它描述的是:
一段连续的 Key Range + 这段 Range 对应的 Raft 副本状态
它不是一个独立磁盘文件,也不是一个固定的物理格子。
这种设计带来了非常强的弹性:
可分裂:大 Region 可以 Split 成多个小 Region;可合并:相邻的小 Region 可以 Merge;可迁移:Peer 可以在 TiKV Store 之间重新分布;高可用:多个 Peer 通过 Raft 保持一致;可路由:TiDB 根据 RegionCache 定位 Key 对应的 Region 和 Leader;可演进:RegionEpoch 帮助客户端识别已经过期的 Region 信息。
所以,下次有人问你:
“TiDB 的 Region 到底是什么?”
你可以这样回答:
Region 不是 TiKV 磁盘上的一个物理格子,而是 Key 空间中的一个逻辑数据分片:它代表一段连续的 Key 区间,并由多个 Peer 通过 Raft 共同维护。这个区间可以 Split、Merge,它的 Peer 也可以被 PD 调度到不同 TiKV 上。
换句话说,Region 就是一段会“分裂、搬家、合并”的图书编号区间。
而正是这种逻辑分片与物理存储、复制、调度之间的解耦,让 TiDB 能够在分布式环境中实现弹性扩展、高可用和自动负载均衡。
一句话版:
Region 管的是 Key Range,Peer 管的是副本,Raft 管一致性,PD 管调度。Region 不“住”在某个固定物理格子里,但它确实对应着 TiKV 中真实存在的数据和副本状态。