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 節,不在這裏贅述。緩存

如何發起 Raft 讀請求?安全

TiKV 的實現是分層的,不一樣模塊負責不一樣事情,下圖直觀地介紹了 TiKV 的模塊的層級關係。 網絡

TiKV 中全部 Raft 相關的邏輯都在 Raftstore 模塊,如何發起 Raft 讀請求就是說如何經過 Raftstore 發起讀請求。Raftstore 對外(TXN/MVCC)提供接口叫作 RaftStoreRouter,它提供了多方s法,但能供外面發起讀寫請求的只有一個,叫作 send_command多線程

image

全部的讀寫請求統一使用這個方法發起。當操做完成後,無論成功與否,都調用 cb: Callbck<E>,並將回覆傳入。app

這篇文章接下來的部分將圍繞圖中黃色部分展開。函數

讀請求有哪些?spa

既然這麼問,確定意味着 TiKV 中有多個不一樣類型的讀請求。這就須要瞭解下 RaftCmdRequest 的構成了。TiKV 對外的請求都是 Protocol buffer message,RaftCmdRequest 定義在 kvproto/raft_cmd.proto,它包含了全部 TiKV 支持的讀寫請求。線程

image

上面代碼中加粗的就是 TiKV 目前支持的幾種讀請求。code

  • 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

image

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

image

因爲 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

image

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 已經實現了。話很少說,直接看代碼。

image

這個實現的有些取巧,咱們直接把它作到 raftstore 的入口處,也就是 RaftStoreRouter 中。這裏的 LocalReader 其實就是一個 cache,緩存了現有 leader 處理讀請求時的一些狀態。

  • acceptable(): 檢查這個請求是否容許用 local read 方式處理。
  • execute_raft_command(): 嘗試以 local read 方式處理該請求。

LocalReader::execute_raft_command

image
image

上述代碼就是 Localreader 中處理請求的關鍵邏輯。注意爲了突出重點,咱們對該函數作了適當精簡,完整代碼請參考 連接。

  • pre_propose_raft_command(): 這個函數和 PeerFsm 中的同名函數作的事情是相似的,對 lease 的檢查也在這裏發生,若是全部檢查經過,就會返回 Ok(Some(delegate)),用來執行讀請求。
  • redirect():若是 Localreader 不肯定如何處理,那它就用該方法將請求從新轉發到 raftstore 中,一切以 raftstore 爲準。

Localreader 中對 lease 的處理和 raftstore 略有不一樣,關鍵代碼在 這裏 和 這裏,至於爲何能夠這麼寫,在這就不說了,做爲課後做業留給讀者思考 :-p

最後

read index 和 local read 的源碼閱讀就到這結束了,但願讀者看完後能瞭解並掌握 TiKV 處理讀請求的邏輯。

相關文章
相關標籤/搜索