Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >TiKV 源码解析系列文章(二十)Region Split 源码解析

TiKV 源码解析系列文章(二十)Region Split 源码解析

原创
作者头像
PingCAP
修改于 2020-09-03 02:02:22
修改于 2020-09-03 02:02:22
1.3K0
举报
文章被收录于专栏:PingCAP的专栏PingCAP的专栏

在学习了之前的几篇 **raft-rs**, **raftstore** 相关文章之后(如 Raft Propose 的 Commit 和 Apply 情景分析Raftstore 概览等),**raft-rs** 以及 **raftstore** 的流程大家应该基本了解了。其中 raft-rs 解决的是单个 Raft group(即单个 Region) 的问题,raftstore 解决的是多个 Raft group (即多个 Region)的问题。Split 和 Merge 则是 raftstore 多个 Raft group 所独有的操作。 TiKV 中的 Split 能把一个 Region 分裂成多个 Region,Merge 则能把 Range 相邻的 2 个 Region 合成一个 Region。本文接下来要介绍的是 Split 的源码。

Region epoch

代码语言:txt
AI代码解释
复制
message RegionEpoch {

    // Conf change version, auto increment when add or remove pee

    uint64 conf\_ver = 1;

    // Region version, auto increment when split or merge

    uint64 version = 2;

}

我们先从 region epoch 讲起,上面是它的 protobuf 定义,在之前的源码分享文章中提到过,它的本质就是两个版本号,更新规则如下:

  1. 配置变更的时候, conf\_ver + 1。
  2. Split 的时候,原 region 与新 region 的 version 均等于原 region 的 version + 新 region 个数。
  3. Merge 的时候,两个 region 的 version 均等于这两个 region 的 version 最大值 + 1。

2 和 3 两个规则可以推出一个有趣的结论:如果两个 Region 拥有的范围有重叠,只需比较两者的 version 即可确认两者之间的历史先后顺序,version 大的意味着更新,不存在相等的情况。

证明比较简单,由于范围只在 Split 和 Merge 的时候才会改变,而每一次的 Split 和 Merge 都会更新影响到的范围里 Region 的 version,并且更新到比原范围中的 version 更大,对于一段范围来说,不管它属于哪个 Region,它所在 Region 的 version 一定是严格单调递增的。

PD 使用了这个规则去判断范围重叠的不同 Region 的新旧。

每条 Proposal 都会在提出的时候带上 PeerFsm 的 Region epoch,在应用的时候检查该 Region epoch 的合法性,如果不合法就跳过。

上图所示,新 Proposal 的 Region epoch 是应用了 Applied Index 那条 Proposal 之后得到的,如果在 Applied Index + 1 到 Last Index 之间的 Proposal 有修改 Region Epoch 的操作,新 Proposal 就有可能会在应用的时候被跳过。

列举两个被跳过的情况,其他的可参照代码 store::util::check\_region\_epoch

  1. 非 Admin Request, Proposal 中的 version 与当前的不相等。
  2. Split,Merge 的 Request,Proposal 中的 Region epoch 与当前的不相等。

Split 触发

Split 触发的条件大体分两种:

  1. PD 触发
  2. TiKV 每个 Region 自行定时检查触发

PD 触发主要是指定哪些 key 去 Split,Split Region 使用文档 中的功能就是利用 PD 触发实现的。

每个 Region 每隔 split-region-check-tick-interval(默认 10s)就会触发一次 Split 检查,代码见 PeerFsmDelegate::on\_split\_region\_check\_tick,以下几个情况不触发检查

* 有检查任务正在进行;

* 数据增量小于阈值;

* 当前正在生成 snapshot 中并且触发次数小于定值。如果频繁 Split,会导致生成的 snapshot 可能因为 version 与当前不一致被丢弃,但是也不能一直不 Split,故设置了触发上限。

触发检查后,会发送任务至 split\_checker\_worker,任务运行时调用 split\_checker.rs 中函数 Runner::check\_split

  1. 调用 coprocessor::new\_split\_checker\_host 获取 SplitCheckerHost,获取时会对每一个注册过的 split\_check\_observers 调用 add\_checker,若满足触发阈值则会把它的 split\_check加入 SplitCheckerHost::checkers 中,如果 checkers 为空则结束检查。(值得一提的是,这里的 coprocessor 并不是指的是计算下推的那个 coprocessor,而是观测 raftstore 事件,给外部提供事件触发的 coprocessor,它的存在可以很好的减少外部观测事件对 raftstore 代码的侵入)
  2. 获取 policy,这里的 policy 只有两种,分别是 SCANAPPROXIMATE,即扫描和取近似,遍历 split\_checker 调用它们的 policy,只要有一个给出的 policy 是取近似,那么最终的结果就是取近似,反之则是扫描。
  3. 获取 Split key。
代码语言:txt
AI代码解释
复制
a.  若 `policy` 为扫描,调用 `scan\_split\_keys`,扫描读出该 Region 范围大 Column Family 的所有数据,对于每一对 KV,调用每个 `split\_checker` 的 `on\_kv` 计算 Split key,扫描完成后遍历 `split\_checker` 的 `split\_keys` 返回第一个不为空的结果。由于需要扫描存储的数据,这个策略会引入额外的 I/O。
代码语言:txt
AI代码解释
复制
b.  若为取近似,调用 `approximate\_split\_keys`,遍历 `split\_checker` 的 `approximate\_split\_keys`,返回第一个不为空的结果。这是通过 RocksDB 的 property 来实现的,几乎没有额外的 I/O 被引入,因而性能上是更优的策略。
  1. 发送 CasualMessage::SplitRegion 给这个 Region。

SplitCheckerHost 只是聚合了split\_check 的结果,具体实现还是在这些 split\_check 中,它们均实现了 SplitChecker trait,由上文的流程叙述也都提到了这些函数。

代码语言:txt
AI代码解释
复制
pub trait SplitChecker<E> {

    /// Hook to call for every kv scanned during split.

    ///

    /// Return true to abort scan early.

    fn on\_kv(&mut self, \_: &mut ObserverContext<'\_>, \_: &KeyEntry) -> bool {

        false

    }



    /// Get the desired split keys.

    fn split\_keys(&mut self) -> Vec<Vec<u8>>;



    /// Get approximate split keys without scan.

    fn approximate\_split\_keys(&mut self, \_: &Region, \_: &E) -> Result<Vec<Vec<u8>>> {

        Ok(vec![])

    }



    /// Get split policy.

    fn policy(&self) -> CheckPolicy;

}

split\_check 有以下几种:

  1. 检查 Region 的总或者近似 Size,代码位于 size.rs
  2. 检查 Region 的总或者近似 Key 数量是否超过阈值,代码位于 key.rs
  3. 根据 Key 范围二分 Split,代码位于 half.rs,除了上文讲的 PD 指定 key 来 Split,这种方式也是由 PD 触发的,目前只有通过 pd-ctltikv-ctl 的命令来手动触发。
  4. 根据  Key 所属 Table 前缀 Split,代码位于 table.rs,配置默认关闭。

由于篇幅所限,具体的实现细节可参阅代码。

Split 实现

Split 的实现相对简单,总的来说,Split 这个操作被当做一条 Proposal 通过 Raft 达成共识,然后各自的 Peer 分别执行 Split。

讲一下具体的流程。

在触发 Split 之后,触发方会发送一条 CasualMessage::SplitRegion 给这个 Region,处理代码见 PeerFsmDelegate::on\_prepare\_split\_region,除了需要检查是否是 leader,还需检查 version 是否有变化,如有变化就拒绝触发 Split。

检查成功后,发送一条 RPC 向 PD 请求分配一些新的 ID,包含所有新 Region 的 ID 以及它所有的 Peer ID,等到 PD 回复后,构造一个类型为 AdminCmdType::BatchSplit 的 Proposal 提给该 Peer。代码在 pd_worker 下的 handle\_ask\_batch\_split

之后的流程就如 Raft Propose 的 Commit 和 Apply 情景分析 所描述的那样,如上文所述,在应用前会判断 Region epoch 的合法性,如果不合法就需要跳过。假设它没有被跳过,接下来看这条 Proposal 应用的地方 ApplyDelegate::exec\_batch\_split

  1. 更新原 Region 的 version,新 Region 的 epoch 继承原 Region 的 epoch。
  2. right\_derive 为 true 的,原 Region 要分裂到右侧,为 false 则反之,依次设置每个 Region 的 start key 与 end key。
  3. 对每个 Split 出来的新 Region 调用 write\_peer\_statewrite\_initial\_apply\_state 创建元数据。

在应用完成之后,ApplyFsm 会发送 PeerMsg::ApplyRes 给 PeerFsm, PeerFsm 处理的代码在 PeerFsmDelegate::on\_ready\_split\_region

  1. 如果是 leader,上报 PD 自己以及新 Region 的 meta 信息(包含范围,Region epoch 等等一系列信息)。
  2. 依次创建新 Region 的 PeerFsm 和 ApplyFsm,做一些注册的工作。
  3. 更新 PeerFsm 的 Region epoch。

需要注意的是,如果在应用完成落盘后宕机,这部分的工作能在重启后恢复。其实所有日志应用的设计都需要满足这个原则。

到这里 Split 的工作就完成了,等到原 Region 大多数的 Peer 都完成了 Split 的工作后,新 Region 就可以成功选出 leader 并且提供服务了。

Split 过程中的一致性

在各机器时钟偏移不超过一定范围的前提下,某个 Region 的 Leader 持有 Raft 租约能保证这段时间不会产生其他 term 更大的 Leader,基于这个保证,使用租约可以提供**线性一致性**的本地读取功能,具体实现可以参考上一篇源码阅读文章

但是在 Split 过程中,原 Region 持有的租约并不能保证这一点。

假设 3 个副本,考虑如下情况:Split Proposal 在 2 个 Follower 上已经应用完成,同时 Leader 上还没有应用(由于 apply 是异步的,Follower 上的应用进度可能超过 Leader)。

Split 之后原 Region 的范围缩小,其余的范围属于新 Region,而新 Region 存活的 Peer 个数已经超过了 Raft 所要求的大多数副本,故可以合理的发起选举并产生 Leader,并且正常服务读写请求。此时原 Region Leader 仍然还未应用 Split Proposal,如果因为持有租约继续服务原范围的读请求,就会破坏**线性一致性**

TiKV 处理方式是在 Split 期间不续约租约。方法是记录最后一条 Split Proposal 的 index last\_committed\_split\_idx, 记录位置见 Peer::handle\_raft\_ready\_append。只需判断 last\_committed\_split\_idx 是否大于 applied\_index 即可得知是否在 Split 期间(Peer::is\_splitting)。

阅读过 Peer::handle\_raft\_ready\_append 中记录 last\_committed\_split\_idx 的小伙伴应该能注意这里并没有让租约立马失效,仅仅设置 index 阻止下次续约。换句话说,在 Split 期间的那次租约时间内是可以让原 Region 的 Leader 提供本地读取功能的。根据前面的分析,这样做貌似是不合理的。

原因十分有趣,对于原 Region 非 Leader 的 Peer 来说,它创建新 Region 的 Peer 是不能立马发起选举的,得等待一个 Raft 的选举超时时间,而对于原 Region 是 Leader 的 Peer 来说,新 Region 的 Peer 可以立马发起选举。Raft 的超时选举时间是要比租约时间长的,这是保证租约正确性的前提。所以在 Split 期间的那次租约时间内,在其他 TiKV 上的新 Region Peer 就算创建出来了,也不会发起选举,因此保证了不会有新数据写入,故在原 Region 上读取不会破坏**线性一致性**

总结

Region Split 的基础流程比较简单,简单来说就是依赖原 Region 的 Raft 提供的可靠复制功能实现的,而与此相对的 Region Merge 由于两个 Region 属于不同的 Raft group,与 Region Split,Raft Snapshot 的相互作用,再加上网络隔离带来的影响,无疑有更大的复杂度。在之后的源码阅读文章中我们会继续讲解 Region Merge,敬请期待!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
TiKV源码解析系列文章(二十)Region Split源码解析
在学习了之前的几篇 raft-rs,raftstore 相关文章之后(如 Raft Propose 的 Commit 和 Apply 情景分析,Raftstore 概览等),raft-rs 以及 raftstore 的流程大家应该基本了解了。其中 raft-rs 解决的是单个 Raft group(即单个 Region) 的问题,raftstore 解决的是多个 Raft group (即多个 Region)的问题。Split 和 Merge 则是 raftstore 多个 Raft group 所独有的操作。TiKV 中的 Split 能把一个 Region 分裂成多个 Region,Merge 则能把 Range 相邻的 2 个 Region 合成一个 Region。本文接下来要介绍的是 Split 的源码。
CNCF
2020/09/04
6510
TiKV源码解析系列文章(二十)Region Split源码解析
TiKV 源码解析系列文章(二十一)Region Merge 源码解析
Region Merge 是 Range 相邻的两个的 Region 合并的过程,我们把一个 Region 称为 Source Region,另一个称为 Target Region,在 Merge 过程结束后,Target Region 管理的 Range 会扩大到 Source Region 的部分,Source Region 则被删除。
PingCAP
2020/12/17
1.1K0
TiKV 源码解析系列文章(二十一)Region Merge 源码解析
TiKV 源码解析系列文章(十八)Raft Propose 的 Commit 和 Apply 情景分析
在学习了 前面的文章 之后,相信大家已经对 TiKV 使用的 Raft 核心库 raft-rs 有了基本的了解。raft-rs 实现了 Raft Leader election 和 Log replication 等核心功能,而消息的发送、接收、应用到状态机等操作则需要使用者自行实现,本文将要介绍的就是 TiKV 中这些部分的处理过程。
PingCAP
2020/03/24
9190
TiKV 源码解析系列文章(十八)Raft Propose 的 Commit 和 Apply 情景分析
TiKV 源码阅读三部曲(二)读流程
TiKV 是一个支持事务的分布式 Key-Value 数据库,目前已经是 CNCF 基金会 的顶级项目。
PingCAP
2022/10/27
2610
TiKV 源码解析系列文章(一)序
TiKV 是一个支持事务的分布式 Key-Value 数据库,有很多社区开发者基于 TiKV 来开发自己的应用,譬如 titan、tidis。尤其是在 TiKV 成为 CNCF 的 Sandbox 项目之后,吸引了越来越多开发者的目光,很多同学都想参与到 TiKV 的研发中来。这时候,就会遇到两个比较大的拦路虎:
PingCAP
2019/01/28
1.5K0
TiKV Raft Store 内存管理的原理与实现丨TiKV 源码解读(二十三)
内存管理是数据库系统不可忽视的核心问题之一,它直接影响系统的性能、稳定性和成本效率。
PingCAP
2024/11/27
1610
TiKV Raft Store 内存管理的原理与实现丨TiKV 源码解读(二十三)
TiKV 源码阅读三部曲(三)写流程
TiKV 是一个支持事务的分布式 Key-Value 数据库,目前已经是 CNCF 基金会 的顶级项目。
PingCAP
2022/11/16
7590
TiDB 最佳实践系列(四)海量 Region 集群调优
在 TiDB 的架构中,所有的数据按照 range 划分成一个个 Region 分布在多个 TiKV 实例上。随着数据的写入,一个集群中会产生上百万,甚至千万个 Region。而量变引起质变,单 TiKV 实例上过多的 Region 无疑会带来比较大的负担,进而影响整个集群的性能表现。
PingCAP
2019/10/24
8870
TiKV 源码解析系列文章(十九)read index 和 local read 情景分析
在上篇文章中,我们讲解了 Raft Propose 的 Commit 和 Apply 情景分析,相信大家对 TiKV 的 Raft 写流程有了大概了解。这篇文章将尝试向大家较为完整的介绍下 TiKV 中的 Raft 读流程的实现,特别是 read index 和 lease read(或称 local read)。关于 read index 和 lease read 的介绍和理论基础,请大家参阅 TiKV 功能介绍 - Lease Read 或者 Raft 论文第 6.4 节,不在这里赘述。
PingCAP
2020/08/27
7800
tikv和tidb_tidb优缺点
TiKV 最底层使用的是 RocksDB 做为持久化存储,所以 TiKV 的很多性能相关的参数都是与 RocksDB 相关的。TiKV 使用了两个 RocksDB 实例,默认 RocksDB 实例存储 KV 数据,Raft RocksDB 实例(简称 RaftDB)存储 Raft 数据。
全栈程序员站长
2022/09/29
8960
PD 调度策略最佳实践
众所周知,PD 是整个 TiDB 集群的核心,负责全局元信息的存储以及 TiKV 集群负载均衡调度,本文将详细介绍 PD 调度系统的原理,并通过几个典型场景的分析和处理方式,分享调度策略的最佳实践和调优方法,帮助大家在使用过程中快速定位问题。本文内容基于 3.0 版本,更早的版本(2.x)缺少部分功能的支持,但是基本原理类似,也可以以本文作为参考。
PingCAP
2019/10/12
1.3K0
以TiDB热点问题来谈Region的调度流程
TiDB的核心架构分为TiDB、TiKV、PD三个部分,其中TiKV是一个分布式数据存储引擎用来存储真实的数据,在TiKV中又对存储区域进行了一系列的逻辑划分也就是Region,它是被PD调度的最小单元。熟悉TiDB的读者对这个结构应该了然于胸。
HOHO
2021/08/18
8410
以TiDB热点问题来谈Region的调度流程
TIDB 中的REGION 是如何进行管理和协调的
熟悉TIDB 的同学都知道,TIDB 中的数据库存储节点是TIKV ,而这里TIKV 仅仅是数据的一个“物理”的存储地,并不是一个数据单位,TIKV的数据单元用Region来表达,那么一个TIKV 可以存储多个region,TIKV 和Region之间的关系是什么,之间的性能关系又是什么。
AustinDatabases
2021/10/15
1.1K0
TiKV 源码阅读三部曲(一)重要模块
作者简介:谭新宇,清华大学软件学院研三在读,Apache IoTDB committer,Talent Plan Community mentor。
PingCAP
2022/10/18
9120
TiKV 源码解析系列文章(二)raft-rs proposal 示例情景分析
本文为 TiKV 源码解析系列的第二篇,按照计划首先将为大家介绍 TiKV 依赖的周边库 raft-rs 。raft-rs 是 Raft 算法的 Rust 语言实现。Raft 是分布式领域中应用非常广泛的一种共识算法,相比于此类算法的鼻祖 Paxos,具有更简单、更容易理解和实现的特点。
PingCAP
2019/02/15
7370
TiKV 源码解析系列文章(九)Service 层处理流程解析
之前的 TiKV 源码解析系列文章介绍了 TiKV 依赖的周边库,从本篇文章开始,我们将开始介绍 TiKV 自身的代码。本文重点介绍 TiKV 最外面的一层——Service 层。
PingCAP
2019/07/08
7680
TiKV 源码解析系列文章(十)Snapshot 的发送和接收
TiKV 使用 Raft 算法来提供高可用且具有强一致性的存储服务。在 Raft 中,Snapshot 指的是整个 State Machine 数据的一份快照,大体上有以下这几种情况需要用到 Snapshot:
PingCAP
2019/07/10
8790
TiKV 集群版本的安全迁移
在 TiDB 的产品迭代中,不免会碰到一些兼容性问题出现。通常协议上的兼容性 protobuf 已经能帮我们处理的很好,在进行功能开发,性能优化时,通常会保证版本是向后兼容的,但并不保证向前兼容性,因此,当集群中同时有新旧版本节点存在时,旧版本不能兼容新版本的特性,就有可能造成该节点崩溃,影响集群可用性,甚至丢失数据。目前在有不兼容的版本升级时,会要求进行离线升级,但这会影响到服务,我们需要一个适合的机制来进行不停服务的升级。因此我们需要在进行滚动升级时,让这些不能保证整个集群的向后兼容性的功能不被启用。只有在保证集群中所有节点都已经升级完成后,我们才安全的启用这些功能。
PingCAP
2018/09/22
8600
TIDB TIKV 数据是怎么写入与通过Region 分割的?
国产的分布式数据库不少,TDSQL, OB, TIDB ,等等都是比较知名的产品,使用的分布式协议也不同,有使用POSTGRES-XL ,也有从外观模仿ORACLE 的,还有借鉴各家所长自己研发的。为什么最近一直在看TIDB,主要有以下几点
AustinDatabases
2021/09/24
1.1K0
TIDB  TIKV  数据是怎么写入与通过Region 分割的?
TiKV 源码解析(六)raft-rs 日志复制过程分析
在 《TiKV 源码解析(二)raft-rs proposal 示例情景分析》 中,我们主要介绍了 raft-rs 的基本 API 使用,其中,与应用程序进行交互的主要 API 是:
PingCAP
2019/04/25
7990
推荐阅读
相关推荐
TiKV源码解析系列文章(二十)Region Split源码解析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档