1. 程式人生 > >docker——Etcd高可用鍵值對資料庫

docker——Etcd高可用鍵值對資料庫

一、簡介

Etcd按照官方介紹:

Etcd is a distributed, consistent key-value store for shared configuration and service discovery

是一個分散式的一致的鍵值對儲存,主要用於共享配置和服務發現。

Etcd是CoreOS團隊於2013年6月發起的開源專案,它的目標是構建一個高可用的分散式鍵值對(key-value)資料庫,基於GO語言實現

在分散式系統中,最基本最重要的問題就是各種資訊的一致性,包括對服務的配置資訊的管理、服務的發現、更新、同步等等。

而要解決這些問題,往往需要基於一套能保證一致性的分散式資料庫系統,比如經典的Apache ZooKeeper專案,通過維護檔案目錄資訊來實現資料的一致性。

Etcd就是專門為叢集環境設計,可以很好的實現資料的一致性,提供叢集節點管理和服務自動發現等



受到Apache ZooKeeper專案和doozer專案的啟發,Etcd在進行設計的時候重點考慮下面四個要素:
  簡單:支援REST風格的HTTP+JSON API;
  安全:支援HTTPS方式的訪問;
  快速:支援併發每秒一千次的寫操作;
  可靠:支援分散式結構,基於Raft演算法實現一致性。

通常情況下,使用Etcd可以在多個節點上啟動多個例項,並將他們新增為一個叢集。
同一個叢集中的Etcd例項將會自動保持彼此資訊的一致性,這意味著分佈在各個節點上的應用也將獲取到一致的資訊。

 

二、安裝和使用

 Etcd基於Go語言實現,可以直接從專案主頁:https://github.com/coreos/etcd下載原始碼自行編譯,
也可以下載編譯好的二進位制檔案,甚至直接使用製作好的Docker映象檔案來使用,下面使用二進位制檔案的方式進行安裝。

1.下載和安裝

下載和解壓:
  wget https://github.com/etcd-io/etcd/releases/download/v3.3.10/etcd-v3.3.10-linux-arm64.tar.gz
  tar -vxf etcd-v3.3.10-linux-arm64.tar.gz
解壓後,可以看到的檔案包括:

其中etcd是服務主檔案,etcdctl是提供給使用者的的客戶端命名,其它都是文件檔案。
將etcd和etcdctl放到系統的可執行目錄/usr/bin/或者/usr/local/bin/下面就可以使用了

cp etcd* /usr/local/bin/

 

2.使用Etcd

直接執行Etcd命令,將啟動一個例項監聽本地的23794001埠。
此時客戶端可以通過本地的2379和4001埠訪問Etcd,其他Etcd本地例項可以通過2380和7001埠連線到新啟動例項。

(1)啟動

(2)檢視版本

(3)檢視叢集健康狀態

通過REST API直接檢視叢集健康狀態:

通過ectdctl檢視:

3.服務啟動引數

Etcd服務啟動的時候支援一些引數,使用者可以通過這些引數來調整服務和叢集的行為。
引數可以通過環境變數形式傳入,命名全部為大寫,並且加ETCD_字首,例如ETCD_NAME='etcd-cluster'。
主要引數包括:通用引數、叢集引數、安全相關引數、代理引數。

 (1)通用引數

(2)叢集引數

(3)安全相關引數

(4)代理引數

這些引數主要是當Etcd服務自身僅作為代理模式時候使用,即轉發來自客戶端的請求到指定的Etcd叢集。
此時Etcd服務本省並不參與叢集中去,不儲存資料和參加選舉。

 (5)日誌引數

(6)其它

 

 

三、使用etcdctl客戶端

etcdctl是Etcd官方提供的命令列客戶端,它支援一些基於HTTP API封裝好的命令,
供使用者直接跟Etcd服務打交道,而無需基於HTTP API的方式。
當然這些命令跟HTTP API實際上是對應的,最終效果上並無不同之處。

Etcd專案二進位制檔案包中已經包含了etcdctl工具,也可以專門下載一個。
etcdctl的命令格式:
  etcdctl [global options] command [command options] [arguments...]
  etcdctl [全域性選項] 命令 [命令選項] [命令引數]

etcdctl命令的全域性選項引數:

支援的命令大體上分為資料類操作和非資料類操作兩類。
etcdctl資料類操作命令:

etcdctl非資料類操作命令:

1.資料類操作

資料類操作圍繞對鍵值和目錄CRUD(符合REST風格的一套操作:Create)完整生命週期的管理。
CRUD即Create、Read、Update、Delete,是符合REST風格的一套API操作規範。
Etcd在鍵的組織上採用了層次化的空間結構(類似於檔案系統中目錄的概念),使用者指定的鍵可以為單獨的名字。
例如,testkey放在根目錄/下面,也可以為指定目錄結構,如clusterl/node2/testkey,則將建立相應的目錄結構。

(1)set

為某個鍵設定定值

引數說明:
  --ttl value 鍵值的超時時間(單位為秒),預設為0則為永不超時。
  --swap-with-value value 若該鍵現在的值是value,則進行設定操作
  --swap-with-index value 若該鍵現在的索引值是指定索引,則進行設定操作。

(2)get

獲取指定鍵的值:

當鍵不存在時報錯:

引數說明:
  --sort 對返回結果進行排序

(3)update

當鍵存在時,更新內容:

當不存在時,報錯:

引數說明:
  --ttl value 鍵值的超時時間(單位為秒),預設為0則為永不超時。

(4)mk

如果給定的鍵不存在,則建立一個新的鍵值:

如果鍵存在會直接報錯:

引數說明:
  --in-order 在目錄<key>下建立按順序鍵
  --ttl value 鍵值的超時時間(單位為秒),預設為0則為永不超時。

(5)rm

刪除某個鍵:

當鍵不存在的時候,報錯:

引數說明:
  --dir 如果鍵是個空目錄或者是鍵值對則刪除
  --recursive, -r 刪除目錄和所有子鍵
  --with-value value 檢查現有的值是否匹配
  --with-index value 檢查現有的index是否匹配

(6)watch

監測一個鍵值的變化,一旦鍵值發生更新,就會輸出最新的值並退出。

引數說明:
  --forever, -f 一直監測,直到使用者按“ctrl+c”退出;
  --after-index value 在指定index之前一直監測;
  --recursive, -r 返回所有的鍵值和子鍵值;

(7)exec-watch

檢測一個鍵值的變化,一旦鍵值發生更新,就執行給定命令。
很多時候使用者實時根據鍵值更新本地服務的配置資訊,並重新載入服務。
可以實現分散式應用配置的自動分發。

引數說明:
  --after-index value 在指定index之前一直監測;
  --recursive, -r 返回所有的鍵值和子鍵值;

(8)ls

列出目錄(預設為根目錄)下的鍵或者子目錄,預設不顯示子目錄中的內容。

引數說明:
  --sort 將輸出結果排序
  --recursive 如果目錄下有子目錄,則遞迴輸出其中的內容
  -p 如果輸出為目錄,在最後新增‘/’進行分割槽

(9)mkdir

如果給定的目錄不存在,則建立一個新的鍵目錄:

如果鍵目錄存在,報錯:

引數說明:
  --ttl value 鍵值的超時時間(單位為秒),預設為0則為永不超時。

(10)rmdir

刪除一個空目錄或者鍵值對,如目錄不為空則報錯:

(11)setdir

建立一個鍵目錄,無論存在與否:

(12)updatedir

更新一個已經存在的目錄屬性。

 

2.非資料類操作

(1)backup

備份Etcd的配置狀態資料目錄
選項:
  --data-dir 要進行備份的Etcd的資料存放目錄
  --backup-dir 備份資料到指定路徑

其中,snap為快照目錄,儲存節點狀態檔案,wal儲存了資料庫預寫日誌資訊。

預寫日誌要求資料庫在發生實際提交之前必須先將操作寫入日誌,可以保障系統在崩潰後更具日誌回覆狀態。

(2)cluster-health

  檢視Etcd叢集的健康狀態:

引數說明:
  --forever, -f 每10秒檢查一次,直到手動終止

(3)member

通過list、add、remove等子命令列出、新增、刪除Etcd例項到Etcd叢集中。
etcdctl member command [command options] [arguments...]
例如本地啟動一個Etcd服務例項後,可以用如下命令檢視預設的成員例項:

(4)import

匯入舊版本的快照檔案到系統

(5)user

對使用者進行管理,包括一系列子命令
  add 新增一個使用者
  get 查詢使用者細節
  list 列出所有使用者
  remove 刪除使用者
  grant 新增使用者到角色
  revoke 刪除使用者角色
  passwd 修改使用者密碼
預設情況下,需要先建立(啟用)root使用者作為etcd叢集的最高許可權管理員。

建立一個testuser使用者:

分配某些已有角色給使用者:

(6)role

對使用者角色進行管理
  add 新增一個角色
  get 查詢角色資訊
  list 列出所有使用者角色
  remove 刪除使用者角色
  grant 新增路徑到角色控制
  revoke 刪除某路徑的角色使用者資訊

預設帶有root、guest兩種角色,前者為全域性最高許可權。

(7)auth

是否啟用訪問驗證,enable為啟用,disable為禁用。

 

四、叢集管理

Etcd的叢集也採用了典型的“主從”模型,通過Raft協議來保證在一段時間內有一個節點為主節點,其它節點為從節點。
一旦主節點發生故障,其它節點可以自動再重新選舉出新的節點。
和其它分散式系統類似,急群眾節點個數推薦為基數個,最少為3個,此時(quorum為2),
越多節點自然能提供更多的冗餘性,但同時會帶來寫資料能力的下降。

1.構建叢集

構建叢集無非是讓節點知道自己加入哪個叢集,其它對等節點的訪問資訊是什麼。
Etcd支援兩種模式來構建叢集:靜態配置和動態探測。

(1)靜態配置叢集資訊

靜態配置就是提取寫好叢集中的有關資訊。
三個節點的ip資訊分別為:

172.16.16.15
172.16.0.4
172.16.0.15

通過如下命令來分別啟動各個節點上的etcd服務。

節點1:
 etcd --name infra0 
	--initial-advertise-peer-urls http://172.16.16.15:2380 
	--listen-peer-urls http://172.16.16.15:2380 
	--listen-client-urls http://172.16.16.15:2379,http://127.0.0.1:2379 
	--advertise-client-urls http://172.16.16.15:2379 
	--initial-cluster-token etcd-cluster-1 
	--initial-cluster 
		infra0=http://172.16.16.15:2380,infra1=http://172.16.0.4:2380,infra2=http://172.16.0.15:2380 
	--initial-cluster-state new
節點2:
etcd --name infra1 
	--initial-advertise-peer-urls http://172.16.0.4:2380 
	--listen-peer-urls http://172.16.0.4:2380 
	--listen-client-urls http://172.16.0.4:2379,http://127.0.0.1:2379 
	--advertise-client-urls http://172.16.0.4:2379 
	--initial-cluster-token etcd-cluster-1 
	--initial-cluster 
		infra0=http://172.16.16.15:2380,infra1=http://172.16.0.4:2380,infra2=http://172.16.0.15:2380 
	--initial-cluster-state new
節點3:
etcd --name infra2 
	--initial-advertise-peer-urls http://172.16.0.15:2380 
	--listen-peer-urls http://172.16.0.15:2380 
	--listen-client-urls http://172.16.0.15:2379,http://127.0.0.1:2379 
	--advertise-client-urls http://172.16.0.15:2379 
	--initial-cluster-token etcd-cluster-1 
	--initial-cluster 
		infra0=http://172.16.16.15:2380,infra1=http://172.16.0.4:2380,infra2=http://172.16.0.15:2380 
	--initial-cluster-state new

啟動成功之後,可以在任意一個節點檢視當前叢集中的成員資訊:

[[email protected] ~]# etcdctl member list
1f146a283033baa3: name=infra2 peerURLs=http://172.16.0.15:2380 clientURLs=http://172.16.0.15:2379 isLeader=false
6de1f3013b32aaf9: name=infra1 peerURLs=http://172.16.0.4:2380 clientURLs=http://172.16.0.4:2379 isLeader=false
d89aedc239d376e5: name=infra0 peerURLs=http://172.16.16.15:2380 clientURLs=http://172.16.16.15:2379 isLeader=true

還可以檢視各個節點的健康狀態:

[[email protected] ~]# etcdctl cluster-health
member 1f146a283033baa3 is healthy: got healthy result from http://172.16.0.15:2379
member 6de1f3013b32aaf9 is healthy: got healthy result from http://172.16.0.4:2379
member d89aedc239d376e5 is healthy: got healthy result from http://172.16.16.15:2379
cluster is healthy

(2)動態發現

靜態配置的方法雖然簡單,但是如果節點的資訊需要變動的時候,就需要手動修改。
可以通過動態發現的方法,讓叢集自動更新節點資訊。
要實現動態發現,首先需要一套支援動態發現的服務。
CoreOS提供了一個公開的Etcd發現服務,地址是:https://discovery.etcd.io
這個網址會為建立的叢集提供一個獨一無二的uuid,需要提供的唯一引數是節點的個數

[[email protected] ~]# curl https://discovery.etcd.io/new?size=3
https://discovery.etcd.io/37435a480ba59aa81a682fee7f415856

分別在各個節點上指定服務發現地址資訊,替換掉原先動態指定的節點列表。

節點1:
 etcd --name infra0 
	--initial-advertise-peer-urls http://172.16.16.15:2380 
	--listen-peer-urls http://172.16.16.15:2380 
	--listen-client-urls http://172.16.16.15:2379,http://127.0.0.1:2379 
	--advertise-client-urls http://172.16.16.15:2379 
	--initial-cluster-token etcd-cluster-1 
	--discovery  https://discovery.etcd.io/37435a480ba59aa81a682fee7f415856
	--initial-cluster-state new
節點2:
etcd --name infra1 
	--initial-advertise-peer-urls http://172.16.0.4:2380 
	--listen-peer-urls http://172.16.0.4:2380 
	--listen-client-urls http://172.16.0.4:2379,http://127.0.0.1:2379 
	--advertise-client-urls http://172.16.0.4:2379 
	--initial-cluster-token etcd-cluster-1 
	--discovery  https://discovery.etcd.io/37435a480ba59aa81a682fee7f4158560 
	--initial-cluster-state new
節點3:
etcd --name infra2 
	--initial-advertise-peer-urls http://172.16.0.15:2380 
	--listen-peer-urls http://172.16.0.15:2380 
	--listen-client-urls http://172.16.0.15:2379,http://127.0.0.1:2379 
	--advertise-client-urls http://172.16.0.15:2379 
	--initial-cluster-token etcd-cluster-1 
	--discovery  https://discovery.etcd.io/37435a480ba59aa81a682fee7f415856 
	--initial-cluster-state new

(3)DNS發現

dns發現主要通過dns服務來記錄叢集中各節點的域名資訊,各節點到dns服務中獲取相互的地址資訊,從而建立叢集。
即為每個節點指定同一個子域的域名,然後通過域名發現來自動註冊。例如:

infra0.example.com
infra1.example.com
infra2.example.com

則啟動引數中的叢集節點列表資訊可以替換為--discovery-srv example.com

 

2.叢集引數配置

影響叢集效能的因素可能有很多,包括時間同步、網路抖動、儲存壓力、讀寫壓力等。
需要通過優化配置儘量減少這些因素的影響。

(1)時間同步

對於分散式叢集來說,各個節點上的同步時鐘十分重要,
Etcd叢集需要各個節點時鐘差異不超過1s。否則可能會導致Raft協議的異常。
可以修改/etc/ntp.conf這個配置檔案,來指定ntp伺服器的地址。

(2)心跳訊息時間間隔和選舉時間間隔

對於Etcd叢集來說,有兩個因素十分重要:心跳訊息時間間隔和選舉時間間隔。
前者意味著主節點每隔多久來通過心跳訊息通知從節點自身的存活狀態;
後者意味著從節點多久沒收到心跳通知後可以嘗試發起選舉自身為主節點。
顯然後者要比前者大,一般建議設為前者的5倍以上。
時間越短,發生故障後回覆的越快,但心跳資訊佔用的計算和網路資源也就越多。

預設情況下,心跳訊息間隔為100ms。選舉時間間隔為1s。
可以在啟動服務的時候通過--heartbeat-interval和--election-timeout來指定。
當然也可以通過環境變數來指定。ETCD_HEARTBEAT_INTERVAL=100 ETCD_ELECTION_TIMEOUT=1000。

(3)snapshop頻率

Etcd會定期將資料的修改儲存為snapshop,預設情況下每10000次修改才會存一個snapshot。
在儲存的時候會有大量資料進行寫入,影響Etcd的效能。
啟動時通過--snapshot-count '100000'指定,也可以使用環境變數ETCD_SNAPSHOP_COUNT=2000 etcd來指定。

(4)修改節點

無論是新增、刪除還是遷移節點,都要一個一個的進行,並且確保先修改配置資訊
(包括節點廣播的監聽地址、叢集中節點列表),然後再進行操作。
例如要刪多個節點,當有主節點被刪除時,需要先刪掉一個,等叢集中狀態穩定後(新的節點重新生成),再刪除另外節點。

要遷移或者替換節點的時候,先將節點從叢集中刪除掉,等叢集狀態重新穩定後,再新增上新的節點。
當然,使用舊節點的資料目錄會加快新節點的同步過程,但要保證這些資料是完整的,且是比較新的。

(5)節點恢復

Etcd叢集中的節點會通過資料目錄來存放修改資訊和叢集配置。
一般來說,當某個節點出現故障的時候,本地資料已經過期甚至格式破壞。
如果只是簡單的重啟程序,容易造成資料的不一致。
這個時候保險的做法是先通過命令來刪除該節點,然後清空資料目錄,再重新作為空節點加入。
Etcd提供了-strict-reconfig-check選項,確保當叢集狀態不穩定的時候拒絕對配置狀態的修改。

(6)重啟叢集

極端情況下,叢集中大部分節點都出現問題,需要重啟整個叢集。
這個時候,最保險的做法就是找到一個數據記錄完整且比較新的節點,
先以它為唯一節點建立新的叢集,然後將其他節點一個一個的新增進來。

 

五、Etcd架構與實現分析

1.為什麼需要Etcd?

所有的分散式系統,都面臨的一個問題是多個節點之間的資料共享,這和團隊協作的道理是一樣的,成員可以分頭幹活,

但總是需要共享一些必要的資訊,比如誰是leader,都有那些成員,依賴任務之間的順序協調等。

所以分散式系統要麼自己實現一個可靠的共享儲存來同步資訊(比如Elasticsearch),要麼依賴一個可靠的共享儲存,而Etcd就是這樣一個服務。

2.Etcd提供什麼能力?

(1)提供儲存以及獲取資料的介面,它通過協議保證Etcd叢集中的多個節點資料的強一致性,用於儲存元資訊和共享配置。
(2)提供監聽機制,客戶端可以監聽某個key或者某些key的變更(v2和v3的機制不同),用於監聽和推送變更。
(3)提供key的過期以及續約機制,客戶端通過定時重新整理來實現續約(v2和v3實現的機制也不一樣),用於叢集監控以及服務註冊發現。
(4)提供原子的CAS(Compare-and-swap)和CAD(Compare-and-Delete)支援(v2通過介面引數實現,v3通過批量事物實現),用於分散式鎖以及leader選舉。

3.Etcd是如何實現一致性的?

(1)raft演算法通過對不同的場景(選主,日誌複製)設計不同的機制,雖然降低 了通用性(相對paxos),但同時也降低了複雜度,便於理解和實現。
(2)raft內建的選主協議是給自己用的,用於選出主節點,理解raft的選主機制的關鍵在於理解raft的時鐘週期以及超時機制。
(3)理解Etcd的資料同步的關鍵在於理解raft的日誌同步機制。

Etcd實現raft的時候,充分利用go語言CSP併發模型和chan的魔法,想更進一步瞭解的話可以去閱讀原始碼,下面簡單分析一下它的wal日誌。

wal日誌是二進位制的,解析出來後是以上資料結構LogEntry。
  其中第一個欄位type,只有兩種,一種是0表示Normal,1表示ConfChange(ConfChange表示Etcd本身的配置變更同步,比如有新的節點加入等)。
  第二個欄位是term,每個term代表一個主節點的任期,每次主節點變更term就會變化。
  第三個欄位是index,這個序號是嚴格有序遞增的,代表變更序號。
  第四個欄位是二進位制的data,將raft request物件的pb結構整個儲存下。
Etcd原始碼下有個tools/etcd-dump-logs,可以將wal日誌dump成文字檢視,可以協助分析raft協議。

raft協議本身不關心應用資料,也就是data中的部分,一致性都通過同步wal日誌來實現,

每個節點將從主節點收到的data apply到本地的儲存,raft只關心日誌的同步狀態,

如果本地儲存實現的有bug,比如沒有正確的將data apply到本地,可能會導致資料不一致。

4.Etcd v2與v3

Etcd v2與v3本質上是共享同一套raft協議程式碼的兩個獨立應用,介面不一樣,儲存不一樣,資料相互隔離。
也就是說如果從Etcd v2升級到Etcd v3,原來v2的資料還是隻能用v3的介面訪問,v3的介面建立的資料只能訪問通過v3的介面訪問。

Etcd v2儲存,Watch以及過期機制

Etcd v2是個純記憶體的實現,並未實時將資料寫入到磁碟,持久化機制很簡單,就是將store整合序列成json寫入檔案。
資料在記憶體中是一個簡單的樹結構,比如以下結構資料儲存到Etcd中的結構如圖所示:

store中有一個全域性currentindex,每次變更,index會加1,然後每個event都會關聯到currentindex。
當客戶端呼叫watch介面(引數中怎加wait引數)時,如果請求引數中有waitindex,
並且waitindex小於currentindex,則從EventHistory表中查詢index小於等於waitindex,並且和watch key匹配的event。
如果有資料,則直接返回。如果歷史表中沒有或者請求沒有帶waitindex,則放入WatchHub中,每個key會關聯以和watcher列表。
當有變更操作時,變更生成的event會放入EventHistory表中,同時通知和該key相關的watcher。
這裡面有幾個影響使用的細節問題:
(1)EventHistory是有長度限制的,最長1000,也就是說,如果你的客戶端停了許久,

  然後重新watch的時候,可能和該waitindex相關的event已經被淘汰了,這種情況下會丟失變更。
(2)如果通知watch的時候,出現了阻塞(每個watch的channel有100個緩衝空間),

  Etcd會直接把watch刪除,也就是會導致wait請求的連線中斷,客戶端需要重新連線。
(3)Etcd store的每個node中儲存了過期時間,通過定時機制進行清理。

Etcd v2的一些限制:
(1)過期時間只能設定到每個key上,如果多個key要保證生命週期一致則比較困難。
(2)watch只能watch某一個key以及其子節點(通過引數 recursive),不能進行多個watch。
(3)很難通過watch機制來實現完整的資料同步(有丟失變更的風險),所以當前的大多數使用方式是通過watch得知變更,

  然後通過get重新獲取資料,並不完全依賴於watch的變更event。