在上篇文章 中,我们讲解了 Raft Propose 的 Commit 和 Apply 情景分析,相信大家对 TiKV 的 Raft 写流程有了大概了解。这篇文章将尝试向大家较为完整的介绍下 TiKV 中的 Raft 读流程的实现,特别是 read index 和 lease read(或称 local read)。关于 read index 和 lease read 的介绍和理论基础,请大家参阅 TiKV 功能介绍 - Lease Read 或者 Raft 论文第 6.4 节,不在这里赘述。
如何发起 Raft 读请求?
TiKV 的实现是分层的, 不同模块负责不同事情,下图直观地介绍了 TiKV 的模块的层级关系。
TiKV 中所有 Raft 相关的逻辑都在 Raftstore 模块 ,如何发起 Raft 读请求就是说如何通过 Raftstore 发起读请求。Raftstore 对外(TXN/MVCC)提供接口叫做 RaftStoreRouter ,它提供了多方s法,但能供外面发起读写请求的只有一个,叫做 send_command 。
所有的读写请求统一使用这个方法发起。当操作完成后,不管成功与否,都调用 cb: Callbck ,并将回复传入。
这篇文章接下来的部分将围绕图中黄色部分展开。
读请求有哪些?
既然这么问,肯定意味着 TiKV 中有多个不同类型的读请求。这就需要了解下 RaftCmdRequest 的构成了。TiKV 对外的请求都是 Protocol buffer message, RaftCmdRequest 定义在 kvproto/raft_cmd.proto ,它包含了所有 TiKV 支持的读写请求。
上面代码中加粗的就是 TiKV 目前支持的几种读请求。
ReadIndexRequest:获取当前时刻能保证线性一致的 Raft log index。
注意:不要把 ReadIndexRequst 和 Read Index 搞混。ReadIndexRequest 是一种读的请求,ReadIndex 是一种处理读请求的方式。
Raft 如何处理读请求?
我们以日常使用中最常见的 SnapRequest 为例,说一下 Read Index 和 Local read 的流程。
在 TXN/MVCC 层通过 send_command 发起一个读请求后,Raftstore 中对应的 PeerFsm (就是一个 Raft 状态机)会在 PeerFsm::handld_msgs 中收到该请求。
PeerFsm::propose_raft_command
PeerFsm 在会将该请求传入 PeerFsm::propose_raft_command 做进一步处理。为了突出重点,无关代码已被删去。
pre_propose_raft_command:检查能否处理该请求,包括:
a. 检查 store id,确认是否发送到发送到了对的 TiKV;
b. 检查 peer id,确认是否发送到了对的 Peer;
c. 检查 leadership,确认当前 Peer 是否为 leader;
d. 检查 Raft 任期,确认当前 leader 的任期是否符合请求中的要求;
e. 检查 peer 初始化状态,确认当前 Peer 已经初始化,有完整数据;
f. 检查 region epoch,确认当前 Region 的 epoch 符合请求中的要求。
peer.propose: 当全部检查通过后,正式进入 Raft 的 Propose 环节。
由于 RaftCmdRequest 可能包含了多种请求,加上请求间的处理方式各有不同,所以我们需要判断下该如何处理。
inspect:判断请求类别和处理方式。让我们聚焦到读请求,处理方式总共有两种:
self.read_index:以 read index 方式处理请求,询问一遍大多数节点,确保自己是合法 leader,然后到达或超过线性一致性的点(read index)后读取 RocksDB。
**Peer::inspect
inspect 方法也不复杂,我们住逐行看一下:
self.leader_lease.inspect(None):使用 CPU 时钟判断 leader 是否在 lease 内,如果在,则使用 ReadLocal 处理。
这判断总的来说就是,如果不确定能安全地读 RocksDB 就用 read index,否则大胆地使用 local read 处理。
多线程 local read
细心的读者可能已经发现,是否能 local read 关键在 leader 是否在 lease 内,而判断 lease 其实是不用经过 Raft 状态机的,所以我们能不能扩展下 lease,让它能在多线程间共享,特别是在 TXN/MVCC 层,这样读请求就能绕过 Raft 直接执行了。答案是可以的,而且 TiKV 已经实现了。话不多说,直接看代码。
这个实现的有些取巧,我们直接把它做到 raftstore 的入口处,也就是 RaftStoreRouter 中。这里的 LocalReader 其实就是一个 cache,缓存了现有 leader 处理读请求时的一些状态。
execute_raft_command(): 尝试以 local read 方式处理该请求。
**LocalReader::execute_raft_command
上述代码就是 Localreader 中处理请求的关键逻辑。注意为了突出重点,我们对该函数做了适当精简,完整代码请参考 链接 。
redirect():如果 Localreader 不确定如何处理,那它就用该方法将请求重新转发到 raftstore 中,一切以 raftstore 为准。
Localreader 中对 lease 的处理和 raftstore 略有不同,关键代码在 这里 和 这里 ,至于为什么可以这么写,在这就不说了,作为课后作业留给读者思考 :-p
最后
read index 和 local read 的源码阅读就到这结束了,希望读者看完后能了解并掌握 TiKV 处理读请求的逻辑。
如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!
赞0
添加新评论0 条评论