> 本篇內容主要來源於自己學習的視頻,如有侵權,請聯繫刪除,謝謝。 ### 1、etcd讀請求概覽 etcd是典型的`讀多寫少`存儲,在我們實際業務場景中,讀一般占據2/3以上的請求。一個讀 請求從client通過`Round-robin(輪詢)`負載均衡演算法,選擇一個etcd server節點,發 ...
本篇內容主要來源於自己學習的視頻,如有侵權,請聯繫刪除,謝謝。
1、etcd讀請求概覽
etcd是典型的讀多寫少
存儲,在我們實際業務場景中,讀一般占據2/3以上的請求。一個讀 請求從client通過Round-robin(輪詢)
負載均衡演算法,選擇一個etcd server節點,發出 gRPC請 求,經過etcd server的 KVServer模塊、線性讀模塊、MVCC的treelndex和 boltdb模塊緊 密協作,完成了一個讀請求。
思考:通過etcdctl執行如下命令etcd是如何工作的?
etcdctl get hello ‐‐endpoints 192.168.65.210:2379,192.168.65.211:2379,19 2.168.65.212:2379
2、詳細步驟解讀
2.1 Client 層
主要是對應到步驟 1
1、首先,etcdctl 會對命令中的參數進行解析。
get
是請求的方法,它是 KVServer 模塊的 提供的API;hello
是 我們查詢的 key 名;endpoints
是我們後端的 etcd 地址。通常,生產環境下中需要配置多個endpoints
,這樣在 etcd 節點出現故障後,client 就可以自動重連到其它正常的節點,從而保證請求的正常執行。
2、在解析完請求中的參數後,etcdctl 會創建一個 clientv3 庫對象,使用 KVServer 模塊 的 API 來訪問 etcd server。
etcd clientv3 庫採用的負載均衡演算法為 Round-robin
。針對每一個請求,Round-robin 演算法通過輪詢
的方式依次從 endpoint 列表中選擇一個 endpoint 訪問 (長連接),使 etcd server 負載儘量均衡。
2.2 KVServer 與 攔截器
主要是對應到步驟 2
client 發送 Range RPC 請求到了 server 後就進入了 KVServer 模塊。
etcd 通過攔截器
以非侵入式的方式實現了許多特性,例如:豐富的 metrics、日誌、請求行為檢查、所有請求的執行耗時及錯誤碼、來源IP 等。攔截器提供了在執行一個請求前後 的 hook 能力,除了 debug 日誌、metrics 統計、對 etcd Learner 節點請求介面和參數限制等能力,etcd 還基於它實現了以下特性:
-
要求執行一個操作前集群必須有 Leader;
-
請求延時超過指定閾值的,列印包含來源 IP 的慢查詢日誌 (3.5 版本)。
server 收到 client 的 Range RPC 請求後,根據 ServiceName 和 RPC Method 將請求轉 發到對應的 handler 實現,handler 首先會將上面描述的一系列攔截器串聯成一個攔截器再執行,在攔截器邏輯中,通過調用 KVServer 模塊的 Range 介面獲取數據。
2.3 串列讀與線性讀
流程三和四.
etcd 為了保證服務高可用,生產環境一般部署多個節點,多節點之間的數據由於延遲等關係可能會存在不一致的情況。
當 client 發起一個寫請求後分為以下幾個步驟:
1、Leader 收到寫請求,它會將此請求持久化到 WAL 日誌
,並廣播給各個節點;
只有 Leader 節點能處理寫請求。
2、若一半以上節點持久化成功,則該請求對應的日誌條目被標識為已提交
;
3、etcdserver 模塊非同步從 Raft 模塊獲取已提交的日誌條目,應用到狀態機 (boltdb 等)。
此時若client 發起一個讀取 hello 的請求,假設此請求直接從狀態機中讀取,如果連接到的是C節點,若C節點磁碟I/O出現波動,可能導致它應用已提交的日誌條目很慢,則會出現更新 hello 為 world 的寫命令,在client讀 hello 的時候還未被提交到狀態機,因此就可能讀取到舊數據,如上圖查詢hello流程所示。
所以在多節點etcd集群中,各個節點的狀態機數據一致性存在差異。而我們不同業務場景 的讀請求對數據是否最新的容忍度是不一樣的,有的場景它可以容忍數據落後幾秒甚至幾分 鐘,有的場景要求必須讀到反映集群共識的最新數據。根據業務場景對數據一致性差異的接受程度。
**etcd 中有兩種讀模式: **
1、串列 (Serializable) 讀:
直接讀狀態機數據返回、無需通過 Raft 協議與集群進行交互, 它具有低延時、高吞吐量的特點,適合對數據一致性要求不高的場景。
2、線性讀:
etcd
預設讀模式是線性讀
,需要經過 Raft 協議模塊,反應的是集群共識,因 此在延時和吞吐量上相比串列讀略差一點,適用於對數據一致性要求高的場景。
對數據敏感度較低的場景:
- 直接讀狀態機數據返回、無需通過 Raft 協議與集群進行交互的模式,在 etcd 里叫做串列 (Serializable) 讀,它具有低延時、高吞吐量的特點,適合對數據一致性要求不高的場景。
對數據敏感性高的場景:
- 在 etcd 裡面,提供了一種線性讀模式來解決對數據一致性要求高的場景。
什麼是線性讀呢?
你可以理解一旦一個值更新成功,隨後任何通過線性讀的 client 都能及時訪問到。雖然集群中有多個節點,但 client 通過線性讀就如訪問一個節點一樣。etcd 預設讀模式是線性讀,因為它需要經過 Raft 協議模塊,反應的是集群共識,因此在延時和吞吐量上相比串列讀略差一點,適用於對數據一致性要求高的場景。
2.4 ReadIndex
在 etcd 3.1 引入了 ReadIndex 機制,保證在串列讀的時候,也能讀到最新的數據。
接下來看看線性讀的執行流程
具體流程如下:
-
當收到一個線性讀請求時,它
首先
會從Leader獲取集群最新的已提交的日誌索引(committed index)
,如上圖中的流程二所示。 -
Leader收到
ReadIndex請求
時,為防止腦裂等異常場景,會向Follower節點發送心跳確認,一半以上節點
確認Leader身份後才能將已提交的索引(committed index)
返回給節點C(上圖中的流程三)。 -
節點則會等待,直到
狀態機已應用索引 (applied index)大於等於Leader的已提交索引時(committed Index)(上圖中的流程四)
,然後去通知讀請求,數據已趕上 Leader,你可以去狀態機中訪問數據了(上圖中的流程五)。
以上就是線性讀通過ReadIndex機制保證數據一致性原理
,當然還有其它機制也能實現線性讀,如在早期etcd 3.0中讀請求通過走一遍Raft 協議保證一致性,這種Raft log read機制 依賴磁碟IO,性能相比 ReadIndex較差。
總體而言,KVServer模塊
收到線性讀
請求後,通過架構圖中流程三向Raft模塊發起 ReadIndex請求
,Raft模塊將Leader最新的已提交日誌索引
封裝在流程四的ReadState結構體
,通過channel層層返回給線性讀模塊,線性讀模塊等待本節點狀態機追趕上Leader進度,追趕完成後,就通知KVServer模塊,進行架構圖中流程五,與狀態機中的 MVCC模塊進行進行交互了。
2.5 MVCC
流程五中的多版本併發控制(Multiversion concurrency control)模塊
是為瞭解決etcd v2不支持保存key的歷史版本、不支持多key事務等問題
而產生的。它核心由記憶體樹形索引模塊 (treelndex)和嵌入式的KV持久化存儲庫 boltdb 組成
。boltdb是個基於B+ tree
實現的 key-value鍵值庫,支持事務,提供Get/Put等簡易API給etcd操作。
etcd MVCC 具體方案如下:
- 每次修改操作,生成一個
新的版本號 (revision)
,以版本號為 key, value 為用戶 key-value 等信息組成的結構體存儲到 blotdb。 - 讀取時·先從 treeIndex 中獲取 key 的版本號·,再以版本號作為 boltdb 的 key,從 boltdb 中獲取其 value 信息。
2.6 treelndex
treelndex模塊
是基於Google開源的記憶體版btree
庫實現的,treeIndex模塊只會保存用戶的key和相關版本號信息
,用戶 key 的value數據存儲在boltdb裡面,相比ZooKeeper和 etcd v2全記憶體存儲,etcd v3對記憶體要求更低。
簡單介紹了etcd如何保存 key的歷史版本後,架構圖中流程六也就非常容易理解了,它需要從treelndex模塊中獲取 hello這個 key對應的版本號信息
。treeIndex模塊基於 B-tree快速查找此 key,返回此 key對應的索引項keyIndex即可。索引項中包含版本號等信息。
2.7 buffer
在獲取到版本號信息後,就可從boltdb模塊中獲取用戶的key-value數據了
。不過並不是所有請求都—定要從 boltdb 獲取數據。etcd出於數據一致性、性能等考慮,在訪問boltdb前,首先會從一個記憶體讀事務 buffer中,二分查找你要訪問key是否在 buffer裡面,若命中則直接返回。
2.8 boltdb
若buffer未命中,此時就真正需要向boltdb模塊查詢數據了
,進入了流程七。 我們知道MySQL通過 table 實現不同數據邏輯隔離,那麼在boltdb是如何隔離集群元數據 與用戶數據的呢?答案是bucket
。
boltdb 里每個 bucket 類似對應 MySQL 一個表,用戶的 key 數據存放的 bucket 名字的是 key,etcd MVCC 元數據存放的 bucket 是 meta。
我猜測這裡的意思是每個key都當做一個 bucket,然後bucket的名字是 key。這裡是猜測的,待驗證,若有知道的朋友,請不吝賜教,十分感謝。
因boltdb使用B+ tree來組織用戶的key-value數據,獲取 bucket key對象後,通過boltdb 的游標Cursor可快速在B+ tree找到 key hello對應的value數據,返回給client。 到這裡,一個讀請求之路執行完成。
文章來源: