etcd 笔记(07)— 键值对读写操作过程
1. 讀寫總體概述
etcd 各個模塊交互的總覽,如下圖所示:
總體上的請求流程從上至下依次為客戶端 → API 接口層 → etcd Server → etcd raft 算法庫。
- 讀請求
客戶端通過負載均衡選擇一個 etcd 節點發出讀請求,API 接口層提供了 Range RPC 方法,etcd 服務端攔截到 gRPC 讀請求后,調用相應的處理器處理請求。
- 寫請求
客戶端通過負載均衡選擇一個 etcd 節點發起寫請求,etcd 服務端攔截到 gRPC 寫請求,涉及一些校驗和監控,之后 KVServer 向 raft 模塊發起提案,內容即為寫入數據的命令。經過網絡轉發,當集群中的多數節點達成一致并持久化數據后,狀態變更且 MVCC 模塊執行提案內容。
2. 讀操作整體流程
將整個讀操作劃分成如下幾個步驟:
-
etcdctl會創建一個clientv3庫對象,選取一個合適的etcd節點; -
調用
KVServer模塊的Range RPC方法,發送請求; -
攔截器攔截,主要做一些校驗和監控;
-
調用
KVServer模塊的Range接口獲取數據;
接著就進入了讀請求的核心步驟,會經過線性讀 ReadIndex 模塊、MVCC(包含 treeIndex 和 BlotDB)模塊。
線性讀是相對串行讀來講的概念。集群模式下會有多個
etcd節點,不同節點之間可能存在一致性問題,串行讀直接返回狀態數據,不需要與集群中其他節點交互。這種方式速度快,開銷小,但是會存在數據不一致的情況。
線性讀則需要集群成員之間達成共識,存在開銷,響應速度相對慢。但是能夠保證數據的一致性,etcd 默認讀模式是線性讀。
繼續往下,看看如何讀取 etcd 中的數據。etcd 中查詢請求,查詢單個鍵或者一組鍵,以及查詢數量,到了底層實際都會調用 Range keys 方法。
Range 請求的結構圖如下所示:
從上至下,查詢鍵值對的流程包括:
-
在
treeIndex中根據鍵利用BTree快速查詢該鍵對應的索引項keyIndex,索引項中包含Revision; -
根據查詢到的版本號信息
Revision,在Backend的緩存Buffer中利用二分法查找,如果命中則直接返回; -
若緩存中不符合條件,在
BlotDB中查找(基于BlotDB的索引),查詢之后返回鍵值對信息。
圖中 ReadTx 和 BatchTx 是兩個接口,用于讀寫請求。在創建 Backend 結構體時,默認也會創建 readTx 和 batchTx,readTx 實現了 ReadTx ,負責處理只讀請求;batchTx 實現了 BatchTx 接口,負責處理讀寫請求。
總結客戶端發起讀請求之后的處理流程,如下圖所示:
-
客戶端發起請求之后,
clientv3首先會根據負載均衡算法選擇一個合適的etcd節點,接著調用KVServer模塊對應的RPC接口,發起Range請求的gRPC遠程調用; -
gRPC Server上注冊的攔截器攔截到Range請求,實現Metrics統計、日志記錄等功能; -
然后進入讀的主要過程,
etcd模式實現了線性讀,使得任何客戶端通過線性讀都能及時訪問到鍵值對的更新; -
線性讀獲取到
Leader已提交日志索引構造的最新ReadState對象,實現本節點狀態機的同步; -
接著就是調用
MVCC模塊,根據treeIndex模塊B-tree快速查找key對應的版本號; -
通過獲取的版本號作為
key,查詢存儲在boltdb中的鍵值對;
3. 寫操作整體流程
將整個寫操作劃分成如下幾個步驟:
-
客戶端通過負載均衡算法選擇一個
etcd節點,發起gRPC調用; -
etcd Server收到客戶端請求; -
經過
gRPC攔截、Quota校驗,Quota模塊用于校驗etcd db文件大小是否超過了配額; -
接著
KVServer模塊將請求發送給本模塊中的raft,這里負責與etcd raft模塊進行通信,發起一個提案,命令為put foo bar,即使用put方法將foo更新為bar; -
提案經過轉發之后,半數節點成功持久化;
-
MVCC模塊更新狀態機;
put 接口的執行過程:
調用 put 向 etcd 寫入數據時,首先會使用傳入的鍵構建 keyIndex 結構體,基于 currentRevision 自增生成新的 Revision 如 {1,0} ,并從 treeIndex 中獲取相關版本 Revision 等信息;寫事務提交之后,將本次寫操作的緩存 buffer 合并(merge)到讀緩存上(圖中 ReadTx 中的緩存)。
revision{1,0} 是生成的全局版本號,作為 BoltDB 的 key,經過序列化包括 key 名稱、key 創建時的版本號(create_revision)、value 值和租約等信息為二進制數據之后,將填充到 BoltDB 的 value 中,同時將該鍵和 Revision 等信息存儲到 Btree。
根據 etcd 讀寫流程圖,可以知道讀寫操作依賴 MVCC 模塊的 treeIndex 和 BoltDB,treeIndex 用來保存鍵的歷史版本號信息,而 BoltDB 用來保存 etcd 的鍵值對數據。通過這兩個模塊之間的協作,實現了 etcd 數據的讀取和存儲。
寫請求的處理流程,如下圖所示:
- 客戶端發送寫請求,通過負載均衡算法選取合適的 etcd 節點,發起 gRPC 調用。
- etcd server 的 gRPC Server 收到這個請求,經過 gRPC 攔截器攔截,實現 Metrics 統計和日志記錄等功能。
- Quota 模塊配額檢查 db 的大小,如果超過會報etcdserver: mvcc: database space exceeded的告警,通過 Raft 日志同步給集群中的節點 db 空間不足,同時告警也會持久化到 db 中。etcd 服務端拒絕寫入,對外提供只讀的功能。
- 配額檢查通過,KVServer 模塊經過限速、鑒權、包大小判斷之后,生成唯一的編號,這時才會將寫請求封裝為提案消息,提交給 Raft 模塊。
- 寫請求的提案只能由 Leader 處理,獲取到 Raft 模塊的日志條目之后,Leader 會廣播提案內容。WAL 模塊完成 Raft 日志條目內容封裝,當集群大多數節點完成日志條目的持久化,即將提案的狀態變更為已提交,可以執行提案內容。
- Apply 模塊用于執行提案,首先會判斷該提案是否被執行過,如果已經執行,則直接返回結束;未執行過的情況下,將會進入 MVCC 模塊執行持久化提案內容的操作。
- MVCC 模塊中的 treeIndex 保存了 key 的歷史版本號信息,treeIndex 使用 B-tree 結構維護了 key 對應的版本信息,包含了全局版本號、修改次數等屬性。版本號代表著 etcd 中的邏輯時鐘,啟動時默認的版本號為 1。鍵值對的修改、寫入和刪除都會使得版本號全局單調遞增。寫事務在執行時,首先根據寫入的 key 獲取或者更新索引,如果不存在該 key,則會給予當前最大的 currentRevision 自增得到 revision;否則直接根據 key 獲取 revision。
- 根據從 treeIndex 中獲取到 revision 、修改次數等屬性,以及 put 請求傳遞的 key-value 信息,作為寫入到 boltdb 的 value,而將 revision 作為寫入到 boltdb 的 key。同時為了讀請求能夠獲取最新的數據,etcd 在寫入 boltdb 時也會同步數據到 buffer。因此上文介紹 etcd 讀請求的過程時,會優先從 buffer 中讀取,讀取不到的情況下才會從 boltdb 讀取,以此來保證一致性和性能。為了提高吞吐量,此時提案數據并未提交保存到 db 文件,而是由 backend 異步 goroutine 定時將批量事務提交。
- Server 通過調用網絡層接口返回結果給客戶端。
總的來說,這個過程為客戶端發起寫請求,由 Leader 節點處理,經過攔截器、Quota 配額檢查之后,KVServer 提交一個寫請求的提案給 Raft 一致性模塊,經過 RaftHTTP 網絡轉發,集群中的其他節點半數以上持久化成功日志條目,提案的狀態將會變成已提交。接著 Apply 通過 MVCC 的 treeIndex、boltdb 執行提案內容,成功之后更新狀態機。
原文:
https://kaiwu.lagou.com/course/courseInfo.htm?courseId=613#/detail/pc?id=6411
總結
以上是生活随笔為你收集整理的etcd 笔记(07)— 键值对读写操作过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2022-2028年中国汽车橡胶件行业市
- 下一篇: 2022-2028年中国打印耗材市场现状