关于数据库:TiKV-源码解析系列文章十九read-index-和-local-read-情景分析

16次阅读

共计 2988 个字符,预计需要花费 8 分钟才能阅读完成。

在上篇文章中,咱们解说了 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<E>,并将回复传入。

这篇文章接下来的局部将围绕图中黄色局部开展。

读申请有哪些?

既然这么问,必定意味着 TiKV 中有多个不同类型的读申请。这就须要理解下 RaftCmdRequest 的形成了。TiKV 对外的申请都是 Protocol buffer message,RaftCmdRequest 定义在 kvproto/raft_cmd.proto,它蕴含了所有 TiKV 反对的读写申请。

下面代码中加粗的就是 TiKV 目前反对的几种读申请。

  • GetRequest:读取一个 key value 对。
  • SnapRequest:获取以后时刻 RocksDB 的 snapshot。
  • 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 做进一步解决。为了突出重点,无关代码已被删去。

  1. 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 合乎申请中的要求。

  2. peer.propose: 当全副查看通过后,正式进入 Raft 的 Propose 环节。

    Peer::propose

因为 RaftCmdRequest 可能蕴含了多种申请,加上申请间的解决形式各有不同,所以咱们须要判断下该如何解决。

  • inspect:判断申请类别和解决形式。让咱们聚焦到读申请,解决形式总共有两种:
  • RequestPolicy::ReadLocal,也就是 local read,阐明该 Peer 是 leader 且在 lease 内,能够间接读取数据。
  • RequestPolicy::ReadIndex,也就是 read index,阐明该 Peer 是 leader 但不在 lease 内,或者该申请明确要求应用 read index 解决。
  • self.read_local:以 loca read 形式解决申请,间接读取 RocksDB。
  • self.read_index:以 read index 形式解决申请,询问一遍大多数节点,确保本人是非法 leader,而后达到或超过线性一致性的点(read index)后读取 RocksDB。

Peer::inspect

inspect 办法也不简单,咱们住逐行看一下:

  • req.get_header().get_read_quorum():该申请明确要求须要用 read index 形式解决,所以返回 ReadIndex。
  • self.has_applied_to_current_term():如果该 leader 尚未 apply 到它本人的 term,则应用 ReadIndex 解决,起因见 TiKV 性能介绍 – Lease Read。
  • self.raft_group.raft.in_lease():如果该 leader 不在 raft 的 lease 内,阐明可能呈现了一些问题,比方网络不稳固,心跳没胜利等。应用 ReadIndex 解决。
  • 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 解决读申请时的一些状态。

  • acceptable(): 查看这个申请是否容许用 local read 形式解决。
  • execute_raft_command(): 尝试以 local read 形式解决该申请。

LocalReader::execute_raft_command


上述代码就是 Localreader 中解决申请的要害逻辑。留神为了突出重点,咱们对该函数做了适当精简,残缺代码请参考 链接。

  • pre_propose_raft_command(): 这个函数和 PeerFsm 中的同名函数做的事件是相似的,对 lease 的查看也在这里产生,如果所有查看通过,就会返回 Ok(Some(delegate)),用来执行读申请。
  • redirect():如果 Localreader 不确定如何解决,那它就用该办法将申请从新转发到 raftstore 中,所有以 raftstore 为准。

Localreader 中对 lease 的解决和 raftstore 略有不同,要害代码在 这里 和 这里,至于为什么能够这么写,在这就不说了,作为课后作业留给读者思考 :-p

最初

read index 和 local read 的源码浏览就到这完结了,心愿读者看完后能理解并把握 TiKV 解决读申请的逻辑。

正文完
 0