@
etcd 官網地址 https://etcd.io/ 最新版本3.5.7
etcd 官網檔案地址 https://etcd.io/docs/v3.5/
etcd 原始碼地址 https://github.com/etcd-io/etcd
etcd是一個強一致、可靠的分散式鍵值儲存,使用Go語言開發(docker和k8s也是),其提供可靠的分散式鍵值(key-value)儲存、設定共用和服務發現等功能,即使在叢集腦裂網路分割區情況下也可以優雅地處理leader選舉;官方上有明確說明etcd是一個CNCF專案。可以說,etcd 已經成為了雲原生和分散式系統的儲存基石。
分散式系統中的資料分為控制資料和應用資料。etcd的使用場景預設處理的資料都是控制資料,對於應用資料,只推薦資料量很小,但是更新存取頻繁的情況。應用場景有如下幾類
如果需要一個分散式儲存倉庫來儲存設定資訊,並且希望這個倉庫讀寫速度快、支援高可用、部署簡單、支援http介面,那麼就可以使用雲原生專案etcd。
etcd實現的絕大多數功能Zookeeper都能實現,那為何還要用etcd?相較之下,Zookeeper有如下缺點:
而etcd作為一個後起之秀,對比Zookeeper其優點如下
etcd作為一個年輕的專案,正在高速迭代和開發中,這既優點也是缺點。優點在於它的未來具有無限的可能性,缺點是版本的迭代導致其使用的可靠性無法保證,無法得到大專案長時間使用的檢驗。但由於CoreOS、Kubernetes和Cloudfoundry等知名專案均在生產環境中使用了etcd,所以總的來說etcd值得去嘗試。
etcd按照分層模型可分為 Client 層、API 網路層、Raft 演演算法層、邏輯層和儲存層。各層功能如下:
Client 層:Client 層包括 client v2 和 v3 兩個大版本 API 使用者端庫,提供了簡潔易用的 API,同時支援負載均衡、節點間故障自動轉移,可極大降低業務使用 etcd 複雜度,提升開發效率、服務可用性。
API 網路層:API 網路層主要包括 client 存取 server 和 server 節點之間的通訊協定。一方面,client 存取 etcd server 的 API 分為 v2 和 v3 兩個大版本。v2 API 使用 HTTP/1.x 協定,v3 API 使用 gRPC 協定。同時 v3 通過 etcd grpc-gateway 元件也支援 HTTP/1.x 協定,便於各種語言的服務呼叫。另一方面,server 之間通訊協定,是指節點間通過 Raft 演演算法實現資料複製和 Leader 選舉等功能時使用的 HTTP 協定。etcdv3版本中client 和 server 之間的通訊,使用的是基於 HTTP/2 的 gRPC 協定。相比 etcd v2 的 HTTP/1.x,HTTP/2 是基於二進位制而不是文字、支援多路複用而不再有序且阻塞、支援資料壓縮以減少包大小、支援 server push 等特性。因此,基於 HTTP/2 的 gRPC 協定具有低延遲、高效能的特點,有效解決etcd v2 中 HTTP/1.x 效能問題。
Raft 演演算法層:Raft 演演算法層實現了 Leader 選舉、紀錄檔複製、ReadIndex 等核心演演算法特性,用於保障 etcd 多個節點間的資料一致性、提升服務可用性等,是 etcd 的基石和亮點。
功能邏輯層:etcd 核心特性實現層,如典型的 KVServer 模組、MVCC 模組、Auth 鑑權模組、Lease 租約模組、Compactor 壓縮模組等,其中 MVCC 模組主要由 treeIndex(記憶體樹形索引) 模組和 boltdb(嵌入式的 KV 持久化儲存庫) 模組組成。treeIndex 模組使用B-tree 資料結構來儲存使用者 key 和版本號的對映關係,使用B-tree是因為etcd支援範圍查詢,使用hash表不適合,從效能上看,B-tree相對於二元樹層級較矮,效率更高;boltdb是個基於 B+ tree 實現的 key-value 鍵值庫,支援事務,提供 Get/Put 等簡易 API 給 etcd 操作。
儲存層:儲存層包含預寫紀錄檔 (WAL) 模組、快照 (Snapshot) 模組、boltdb 模組。其中 WAL 可保障 etcd crash 後資料不丟失,boltdb 則儲存了叢集後設資料和使用者寫入的資料。
etcd 是典型的讀多寫少儲存,在我們實際業務場景中,讀一般佔據 2/3 以上的請求。
讀請求:使用者端通過負載選擇一個etcd節點發出讀請求,API介面層提供Range RPC方法,etcd伺服器端攔截gRPC 讀請求後呼叫的處理請求。
寫請求:使用者端通過負載均衡選擇一個etcd節點發起請求etcd伺服器端攔截gRPC寫請求,涉及校驗和監控後KVServer向raft模組發起提案,內容寫入資料命令,經過網路轉發,當叢集中多數節點達成一致持久化資料後,狀態變更MVCC模組執行提案內容。
etcd使用者端工具通過etcdctl執行一個讀命令,解析完請求中的引數建立clientv3 庫物件,然後通過EndPoint列表使用Round-Robin負載均衡演演算法選擇一個etcd server節點,呼叫 KVServer API模組基於 HTTP/2 的 gRPC 協定的把請求傳送給 etcd server,攔截器攔截,主要做一些校驗和監控,然後呼叫KVserver模組的Range介面獲取資料。讀操作的核心步驟:
線性讀是相對序列讀來講的概念,叢集模式下會有多個etcd節點,不同節點間可能存在一致性的問題。序列讀直接返回狀態資料,不需要與叢集中其他節點互動。這種方式速度快,開銷小,但是會存在資料不一致的情況。
線性讀則需要叢整合員之間達成共識,存在開銷,響應速度相對慢,但是能保證資料的一致性,etcd預設的讀模式線性讀。
etcd中查詢請求,查詢單個鍵或者一組鍵及查詢數量,到底層實際會呼叫Range keys方法。
流程如下:
ReadTx和BatchTx是兩個幾口,用於讀寫請求建立Backend結構體,預設也會建立readTx和batchTx。readTx實現了ReadTx,負責處理唯讀請求batchTx,實現了BatchTx介面,負責處理讀寫請求。
對於上層的鍵值儲存,它會利用返回的Revision從正真的儲存資料中的BoltDB中,查詢當前key對應的Revsion資料。BoltDB內部用類似buctket的方式儲存對應MySQL中的表結構,使用者key資料存放bucket的名字是key etcd mvcc後設資料存放bucket的meta。
核心模組的功能:
寫操作涉及核心模組功能如下:
Quoto模組
KVServer模組
WAL模組
Apply模組
MVCC模組
紀錄檔由一個個遞增的有序序號索引標識。Leader維護了所有Follow節點的紀錄檔複製進度,在新增一個紀錄檔後,會將其廣播給所有Follow節點。Follow節點處理完成後,會告知Leader當前已複製的最大紀錄檔索引。Leader收到後,會計算被一半以上節點複製過的最大索引位置,標記為已提交位置,在心跳中告訴Follow節點。只有被提交位置以前的紀錄檔才會應用到儲存狀態機。
在本地安裝、執行和測試etcd的單成員叢集,部署詳細可以檢視下上一篇《雲原生API閘道器全生命週期管理Apache APISIX探究實操》中有關於etcd單節點部署,單節點部署完畢後驗證讀寫和檢視版本資訊如下:
靜態地啟動etcd叢集要求叢集中的每個成員都認識叢集中的其他成員;但通常叢整合員的ip可能事先未知,可以通過發現服務引導etcd叢集。在生產環境中,為了整個叢集的高可用,etcd 正常都會叢集部署,避免單點故障。引導 etcd 叢集的啟動有以下三種機制:
在部署之前已經知道了叢整合員、它們的地址和叢集的大小,name可以通過設定initial-cluster標誌來使用離線引導設定。分別在各個節點上執行下面語句
etcd --name infra1 --initial-advertise-peer-urls http://192.168.3.111:2380 \
--listen-peer-urls http://192.168.3.111:2380 \
--listen-client-urls http://192.168.3.111:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://192.168.3.111:2379 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster infra1=http://192.168.3.111:2380,infra2=http://192.168.3.112:2380,infra3=http://192.168.3.113:2380 \
--initial-cluster-state new
etcd --name infra2 --initial-advertise-peer-urls http://192.168.3.112:2380 \
--listen-peer-urls http://192.168.3.112:2380 \
--listen-client-urls http://192.168.3.112:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://192.168.3.112:2379 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster infra1=http://192.168.3.111:2380,infra2=http://192.168.3.112:2380,infra3=http://192.168.3.113:2380 \
--initial-cluster-state new
etcd --name infra3 --initial-advertise-peer-urls http://192.168.3.113:2380 \
--listen-peer-urls http://192.168.3.113:2380 \
--listen-client-urls http://192.168.3.113:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://192.168.3.113:2379 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster infra1=http://192.168.3.111:2380,infra2=http://192.168.3.112:2380,infra3=http://192.168.3.113:2380 \
--initial-cluster-state new
也可以通過nohup &後臺啟動etcd,獲取叢集的member資訊
etcdctl --endpoints=192.168.5.52:2379 member list
# 建立紀錄檔目錄
mkdir /var/log/etcd
# 建立資料目錄
mkdir /data/etcd
mkdir /home/commons/data/etcd
發現URL標識唯一的etcd叢集。每個etcd範例共用一個新的發現URL來引導新叢集,而不是重用現有的發現URL。如果沒有可用的現有叢集,則使用discovery.etc .io託管的公共發現服務。要使用「new」端點建立一個私有發現URL,使用命令:
# 通過curl生成
curl https://discovery.etcd.io/new?size=3
https://discovery.etcd.io/d45c453e99404bcb4b0b30b0ff924200
# 通過上面返回組裝
ETCD_DISCOVERY=https://discovery.etcd.io/d45c453e99404bcb4b0b30b0ff924200
--discovery https://discovery.etcd.io/d45c453e99404bcb4b0b30b0ff924200
分別在各個節點上執行下面語句
etcd --name myectd1 --data-dir /home/commons/data --initial-advertise-peer-urls http://192.168.5.111:2380 \
--listen-peer-urls http://192.168.5.111:2380 \
--listen-client-urls http://192.168.5.111:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://192.168.5.111:2379 \
--discovery https://discovery.etcd.io/d45c453e99404bcb4b0b30b0ff924200
etcd --name myectd2 --data-dir /home/commons/data --initial-advertise-peer-urls http://192.168.5.112:2380 \
--listen-peer-urls http://192.168.5.112:2380 \
--listen-client-urls http://192.168.5.112:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://192.168.5.112:2379 \
--discovery https://discovery.etcd.io/d45c453e99404bcb4b0b30b0ff924200
etcd --name myectd3 --data-dir /home/commons/data --initial-advertise-peer-urls http://192.168.5.113:2380 \
--listen-peer-urls http://192.168.5.113:2380 \
--listen-client-urls http://192.168.5.113:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://192.168.5.113:2379 \
--discovery https://discovery.etcd.io/d45c453e99404bcb4b0b30b0ff924200
#寫入KV
etcdctl put /key1 value1
etcdctl put /key2 value2
etcdctl put /key3 value3
# 範圍,左閉右開
etcdctl get /key1 /key3
# 以十六進位制格式讀取key foo值的命令:
etcdctl get /key1 --hex
# 僅列印value
etcdctl get /key1 --print-value-only
# 字首匹配和返回條數
etcdctl get --prefix /key --limit 2
# 按照key的字典順序讀取,大於或等於
etcdctl get --from key /key1
# 監聽key,可以獲取key變更資訊
etcdctl watch /key1
# 重新修改
etcdctl put /key1 value111
# 讀取版本
etcdctl get /key1 --rev=5
# 刪除key
etcdctl del /key3
# 租約,例如授予60秒生存時間的租約
etcdctl lease grant 60
lease 5ef786eee44b831d granted with TTL(60s)
# 寫入帶租約
etcdctl put --lease=5ef786eee44b831d /key4 value4
# 復原租約
etcdctl lease revoke 32695410dcc0ca06
# 授權建立角色
etcdctl role add testrole
etcdctl role list
etcdctl role grant-permission testrole read /permission
etcdctl role revoke-permission testrole /permission
etcdctl role del testrole
# 授權建立使用者
etcdctl user add testuser
etcdctl user list
etcdctl user passwd
etcdctl user get testuser
etcdctl user del testuser
etcdctl user grant-role testuser testrole
# 建立測試賬號2
etcdctl role add testrole2
etcdctl role grant-permission testrole2 readwrite /permission
etcdctl user add testuser2
etcdctl user grant-role testuser2 testrole2
#1. 新增root角色
etcdctl role add root
#2. 新增root使用者
etcdctl user add root
#3. 給root使用者授予root角色
etcdctl user grant-role root root
#4.啟用auth
etcdctl auth enable
etcdctl put /permission all2 --user=testuser2
etcdctl get /permission --user=testuser2
etcdctl get /permission --user=testuser
etcdctl put /permission allhello --user=testuser
# 直接帶上密碼
etcdctl --user='testuser2' --password='123456' put /permission all2
# 叢集鑑權
etcdctl --endpoints http://192.168.3.111:2379,http://192.168.3.111:2379,http://192.168.3.111:2379 --user=root --password=123456 auth enable
etcdctl --endpoints http://192.168.3.111:2379,http://192.168.3.111:2379,http://192.168.3.111:2379 --user=root:123456 auth enable