原文連接:https://www.jianshu.com/p/f31ef5e7bdd0php
etcd 是一個分佈式一致性鍵值存儲。其主要功能有服務註冊與發現、消息發佈與訂閱、負載均衡、分佈式通知與協調、分佈式鎖、分佈式隊列、集羣監控與 leader 選舉等。
html
etcd 性能優化git
官方文檔原文:https://github.com/etcd-io/etcd/blob/master/Documentation/tuning.mdgithub
譯文參考:https://skyao.gitbooks.io/learning-etcd3/content/documentation/op-guide/performance.htmlweb
理解 etcd 的性能
決定 etcd 性能的關鍵因素,包括:算法
延遲 (latency):延遲是完成操做的時間。docker
吞吐量 (throughput):吞吐量是在某個時間期間以內完成操做的總數量。當 etcd 接收併發客戶端請求時,一般平均延遲隨着整體吞吐量增長而增長。後端
在一般的雲環境,好比 Google Compute Engine (GCE) 標準的 n-4 或者 AWS 上至關的機器類型,一個三成員 etcd 集羣在輕負載下能夠在低於 1 毫秒內完成一個請求,並在重負載下能夠每秒完成超過 30000 個請求。安全
etcd 使用 Raft 一致性算法來在成員之間複製請求並達成一致。一致性性能,特別是提交延遲,受限於兩個物理約束:網絡 IO 延遲和磁盤 IO 延遲。完成一個 etcd 請求的最小時間是成員之間的網絡往返時延 (Round Trip Time / RTT),加須要提交數據到持久化存儲的 fdatasync 時間。在一個數據中心內的 RTT 可能有數百毫秒。在美國典型的 RTT 是大概 50ms, 而在大陸之間能夠慢到 400ms。旋轉硬盤(注:指傳統機械硬盤) 的典型 fdatasync 延遲是大概 10ms。對於 SSD 硬盤, 延遲一般低於 1ms。爲了提升吞吐量, etcd 將多個請求打包在一塊兒並提交給 Raft。這個批量策略讓 etcd 在重負載試得到高吞吐量。也有其餘子系統影響到 etcd 的總體性能。每一個序列化的 etcd 請求必須經過 etcd 的 boltdb 支持的(boltdb-backed) MVCC 存儲引擎, 它一般須要 10 微秒來完成。etcd 按期遞增快照它最近實施的請求,將他們和以前在磁盤上的快照合併。這個過程可能致使延遲尖峯(latency spike)。雖然在 SSD 上這一般不是問題,在 HDD 上它可能加倍可觀察到的延遲。並且,進行中的壓縮能夠影響 etcd 的性能。幸運的是,壓縮一般無足輕重,由於壓縮是錯開的,所以它不和常規請求競爭資源。RPC 系統,gRPC,爲 etcd 提供定義良好,可擴展的 API,可是它也引入了額外的延遲,尤爲是本地讀取。
Etcd 的默認配置在本地網絡環境(localhost)下一般可以運行的很好,由於延遲很低。然而,當跨數據中心部署 Etcd 或網絡延時很高時,etcd 的心跳間隔或選舉超時時間等參數須要根據實際狀況進行調整。
網絡並非致使延時的惟一來源。不管是 Follower 仍是 Leader,其請求和響應都受磁盤 I/O 延時的影響。每一個 timeout 都表明從請求發起到成功返回響應的總時間。
時間參數
Etcd 底層的分佈式一致性協議依賴兩個時間參數來保證節點之間可以在部分節點掉錢的狀況下依然可以正確處理主節點的選舉。第一個參數就是所謂的心跳間隔,即主節點通知從節點它仍是領導者的頻率。實踐數據代表,該參數應該設置成節點之間 RTT 的時間。Etcd 的心跳間隔默認是 100 毫秒。第二個參數是選舉超時時間,即從節點等待多久沒收到主節點的心跳就嘗試去競選領導者。Etcd 的選舉超時時間默認是 1000 毫秒。
調整這些參數值是有條件的,此消波長。心跳間隔值推薦設置爲臨近節點間 RTT 的最大值,一般是 0.5~1.5 倍 RTT 值。若是心跳間隔設得過短,那麼 Etcd 就會發送不必的心跳信息,從而增長 CPU 和網絡資源的消耗;若是設得太長,就會致使選舉等待時間的超時。若是選舉等待時間設置的過長,就會致使節點異常檢測時間過長。評估 RTT 值的最簡單的方法是使用 ping 的操做。
選舉超時時間應該基於心跳間隔和節點之間的平均 RTT 值。選舉超時必須至少是 RTT 10 倍的時間以便對網絡波動。例如,若是 RTT 的值是 10 毫秒,那麼選舉超時時間必須至少是 100 毫秒。選舉超時時間的上線是 50000 毫秒(50 秒),這個時間只能只用於全球範圍內分佈式部署的 Etcd 集羣。美國大陸的一個 RTT 的合理時間大約是 130 毫秒,美國和日本的 RTT 大約是 350~400 毫秒。若是算上網絡波動和重試的時間,那麼 5 秒是一次全球 RTT 的安全上線。由於選舉超時時間應該是心跳包廣播時間的 10 倍,因此 50 秒的選舉超時時間是全局分佈式部署 Etcd 的合理上線值。
心跳間隔和選舉超時時間的值對同一個 Etcd 集羣的全部節點都生效,若是各個節點都不一樣的話,就會致使集羣發生不可預知的不穩定性。Etcd 啓動時經過傳入啓動參數或環境變量覆蓋默認值,單位是毫秒。示例代碼具體以下:
$ etcd --heartbeat-interval=100 --election-timeout=500
# 環境變量值
$ ETCD_HEARTBEAT_INTERVAL=100 ETCD_ELECTION_TIMEOUT=500 etcd
快照
Etcd 老是向日志文件中追加 key,這樣一來,日誌文件會隨着 key 的改動而線性增加。當 Etcd 集羣使用較少時,保存完整的日誌歷史記錄是沒問題的,但若是 Etcd 集羣規模比較大時,那麼集羣就會攜帶很大的日誌文件。爲了不攜帶龐大的日誌文件,Etcd 須要作週期性的快照。快照提供了一種經過保存系統的當前狀態並移除舊日誌文件的方式來壓縮日誌文件。
快照調優
爲 v2 後端存儲建立快照的代價是很高的,因此只用當參數累積到必定的數量時,Etcd 纔會建立快照文件。默認狀況下,修改數量達到 10000 時纔會創建快照。若是 Etcd 的內存使用和磁盤使用太高,那麼應該嘗試調低快照觸發的閾值,具體請參考以下命令。
啓動參數:
$ etcd --snapshot-count=5000
環境變量:
$ ETCD_SNAPSHOT_COUNT=5000 etcd
磁盤
etcd 的存儲目錄分爲 snapshot 和 wal,他們寫入的方式是不一樣的,snapshot 是內存直接 dump file。而 wal 是順序追加寫,對於這兩種方式系統調優的方式是不一樣的,snapshot 能夠經過增長 io 平滑寫來提升磁盤 io 能力,而 wal 能夠經過下降 pagecache 的方式提早寫入時序。所以對於不一樣的場景,能夠考慮將 snap 與 wal 進行分盤,放在兩塊 SSD 盤上,提升總體的 IO 效率,這種方式能夠提高 etcd 20% 左右的性能。
etcd 集羣對磁盤 I/O 的延時很是敏感,由於 Etcd 必須持久化它的日誌,當其餘 I/O 密集型的進程也在佔用磁盤 I/O 的帶寬時,就會致使 fsync 時延很是高。這將致使 Etcd 丟失心跳包、請求超時或暫時性的 Leader 丟失。這時能夠適當爲 Etcd 服務賦予更高的磁盤 I/O 權限,讓 Etcd 更穩定的運行。在 Linux 系統中,磁盤 I/O 權限能夠經過 ionice 命令進行調整。
nux 默認 IO 調度器使用 CFQ 調度算法,支持用 ionice 命令爲程序指定 IO 調度策略和優先級,IO 調度策略分爲三種:
Idle :其餘進程沒有磁盤 IO 時,才進行磁盤 IO
Best Effort:缺省調度策略,能夠設置 0-7 的優先級,數值越小優先級越高,同優先級的進程採用 round-robin 算法調度;
Real Time :當即訪問磁盤,無視其它進程 IO
None 即 Best Effort,進程未指定策略和優先級時顯示爲 none,會使用依據 cpu nice 設置計算出優先級
Linux 中 etcd 的磁盤優先級可使用 ionice
配置:
$ ionice -c2 -n0 -p `pgrep etcd`
網絡
etcd 中比較複雜的是網絡的調優,所以大量的網絡請求會在 peer 節點之間轉發,並且總體網絡吞吐也很大,可是仍是再次強調不建議你們調整系統參數,你們能夠經過修改 etcd 的 --heartbeat-interval
與 --election-timeout
啓動參數來適當提升高吞吐網絡下 etcd 的集羣魯棒性,一般同步吞吐在 100MB 左右的集羣能夠考慮將 --heartbeat-interval
設置爲 300ms-500ms,--election-timeout
能夠設置在 5000ms 左右。此外官方還有基於 TC 的網絡優先傳輸方案,也是一個比較適用的調優手段。
若是 etcd 的 Leader 服務大量併發客戶端,這就會致使 follower 的請求的處理被延遲由於網絡延遲。follower 的 send buffer 中能看到錯誤的列表,以下所示:
dropped MsgProp to 247ae21ff9436b2d since streamMsg's sending buffer is full
dropped MsgAppResp to 247ae21ff9436b2d since streamMsg's sending buffer is full
這些錯誤能夠經過提升 Leader 的網絡優先級來提升 follower 的請求的響應。能夠經過流量控制機制來提升:
// 針對 237九、2380 端口放行
$ tc qdisc add dev eth0 root handle 1: prio bands 3
$ tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip sport 2380 0xffff flowid 1:1
$ tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip dport 2380 0xffff flowid 1:1
$ tc filter add dev eth0 parent 1: protocol ip prio 2 u32 match ip sport 2379 0xffff flowid 1:1
$ tc filter add dev eth0 parent 1: protocol ip prio 2 u32 match ip dport 2379 0xffff flowid 1:1
// 查看現有的隊列
$ tc -s qdisc ls dev enp0s8
qdisc prio 1: root refcnt 2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
Sent 258578 bytes 923 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
// 刪除隊列
$ tc qdisc del dev enp0s8 root
數據規模
etcd 的硬盤存儲上限(默認是 2GB), 當 etcd 數據量超過默認 quota 值後便再也不接受寫請求,能夠經過設置 --quota-backend-bytes
參數來增長存儲大小,quota-backend-bytes
默認值爲 0,即便用默認 quota 爲 2GB,上限值爲 8 GB,具體說明可參考官方文檔:dev-guide/limit.md。
The default storage size limit is 2GB, configurable with `--quota-backend-bytes` flag. 8GB is a suggested maximum size for normal environments and etcd warns at startup if the configured value exceeds it.
如下摘自 當 K8s 集羣達到萬級規模,阿里巴巴如何解決系統各組件性能問題?
阿里進行了深刻研究了 etcd 內部的實現原理,並發現了影響 etcd 擴展性的一個關鍵問題在底層 bbolt db 的 page 頁面分配算法上:隨着 etcd 中存儲的數據量的增加,bbolt db 中線性查找 「連續長度爲 n 的 page 存儲頁面」 的性能顯著降低。
爲了解決該問題,咱們設計了基於 segregrated hashmap 的空閒頁面管理算法,hashmap 以連續 page 大小爲 key, 連續頁面起始 page id 爲 value。經過查這個 segregrated hashmap 實現 O(1) 的空閒 page 查找,極大地提升了性能。在釋放塊時,新算法嘗試和地址相鄰的 page 合併,並更新 segregrated hashmap。更詳細的算法分析能夠見已發表在 CNCF 博客的博文。
經過這個算法改進,咱們能夠將 etcd 的存儲空間從推薦的 2GB 擴展到 100GB,極大地提升了 etcd 存儲數據的規模,而且讀寫無顯著延遲增加。
pull request :https://github.com/etcd-io/bbolt/pull/141
目前社區已發佈的 v3.4 系列版本並無說明支持數據規模可達 100 G。
etcd 性能測試
測試環境:本機 mac 使用 virtualbox 安裝 vm,全部 etcd 實例都是運行在在 vm 中的 docker 上
參考官方文檔:https://github.com/etcd-io/etcd/blob/master/Documentation/op-guide/performance.md
安裝 etcd 壓測工具 benchmark:
$ go get go.etcd.io/etcd/tools/benchmark
# GOPATH should be set
$ ls $GOPATH/bin
benchmark
本文僅對 etcd v3.3.10 以及 v3.4.1 進行壓測。
部署 etcd 集羣
如下爲腳本示例:
#!/bin/bash
docker ps -a | grep etcd | grep -v k8s
docker rm -f etcd
ETCD_VERSION=3.3.10
TOKEN=my-etcd-token
CLUSTER_STATE=new
NAME_1=etcd-node-0
NAME_2=etcd-node-1
NAME_3=etcd-node-2
HOST_1=192.168.74.36
HOST_2=192.168.74.36
HOST_3=192.168.74.36
CLUSTER=${NAME_1}=http://${HOST_1}:23801,${NAME_2}=http://${HOST_2}:23802,${NAME_3}=http://${HOST_3}:23803
# 對於節點1
THIS_NAME=${NAME_1}
THIS_IP=${HOST_1}
sudo docker run -d --net=host --name ${THIS_NAME} k8s.gcr.io/etcd:${ETCD_VERSION} \
/usr/local/bin/etcd \
--data-dir=data.etcd --name ${THIS_NAME} \
--initial-advertise-peer-urls http://${THIS_IP}:23801 --listen-peer-urls http://${THIS_IP}:23801 \
--advertise-client-urls http://${THIS_IP}:23791 --listen-client-urls http://${THIS_IP}:23791 \
--initial-cluster ${CLUSTER} \
--initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}
# 對於節點2
THIS_NAME=${NAME_2}
THIS_IP=${HOST_2}
sudo docker run -d --net=host --name ${THIS_NAME} k8s.gcr.io/etcd:${ETCD_VERSION} \
/usr/local/bin/etcd \
--data-dir=data.etcd --name ${THIS_NAME} \
--initial-advertise-peer-urls http://${THIS_IP}:23802 --listen-peer-urls http://${THIS_IP}:23802 \
--advertise-client-urls http://${THIS_IP}:23792 --listen-client-urls http://${THIS_IP}:23792 \
--initial-cluster ${CLUSTER} \
--initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}
# 對於節點3
THIS_NAME=${NAME_3}
THIS_IP=${HOST_3}
sudo docker run -d --net=host --name ${THIS_NAME} k8s.gcr.io/etcd:${ETCD_VERSION} \
/usr/local/bin/etcd \
--data-dir=data.etcd --name ${THIS_NAME} \
--initial-advertise-peer-urls http://${THIS_IP}:23803 --listen-peer-urls http://${THIS_IP}:23803 \
--advertise-client-urls http://${THIS_IP}:23793 --listen-client-urls http://${THIS_IP}:23793 \
--initial-cluster ${CLUSTER} \
--initial-cluster-state ${CLUSTER_STATE} --initial-cluster-token ${TOKEN}
壓測
本文主要對不一樣場景下 etcd 的讀寫操做進行測試,儘管環境有限,但在不一樣場景下 etcd 的表現仍是有區別的。對於寫入測試,按照官方文檔的測試方法指定不一樣數量的客戶端和鏈接數以及 key 的大小,對於讀取操做,分別測試了線性化讀取以及串行化讀取,因爲 etcd 是強一致性的,其默認讀取測試就是線性化讀取。
etcd v3.3.10
寫入測試
// 查看 leader
$ etcdctl member list
// leader
$ benchmark --endpoints="http://192.168.74.36:23791" --target-leader --conns=1 --clients=1 put --key-size=8 --sequential-keys --total=10000 --val-size=256
$ benchmark --endpoints="http://192.168.74.36:23791" --target-leader --conns=100 --clients=1000 put --key-size=8 --sequential-keys --total=100000 --val-size=256
// 全部 members
$ benchmark --endpoints="http://192.168.74.36:23791,http://192.168.74.36:23792,http://192.168.74.36:23793" --target-leader --conns=1 --clients=1 put --key-size=8 --sequential-keys --total=10000 --val-size=256
$ benchmark --endpoints="http://192.168.74.36:23791,http://192.168.74.36:23792,http://192.168.74.36:23793" --target-leader --conns=100 --clients=1000 put --key-size=8 --sequential-keys --total=100000 --val-size=256
讀取測試
$ benchmark --endpoints="http://192.168.74.36:23791,http://192.168.74.36:23792,http://192.168.74.36:23793" --conns=1 --clients=1 range foo --consistency=l --total=10000
$ benchmark --endpoints="http://192.168.74.36:23791,http://192.168.74.36:23792,http://192.168.74.36:23793" --conns=1 --clients=1 range foo --consistency=s --total=10000
$ benchmark --endpoints="http://192.168.74.36:23791,http://192.168.74.36:23792,http://192.168.74.36:23793" --conns=100 --clients=1000 range foo --consistency=l --total=100000
$ benchmark --endpoints="http://192.168.74.36:23791,http://192.168.74.36:23792,http://192.168.74.36:23793" --conns=100 --clients=1000 range foo --consistency=s --total=100000
etcd v3.4.1
寫入測試
// 查看 etcd leader
$ etcdctl --write-out=table --endpoints="http://192.168.74.36:23791,http://192.168.74.36:23801" endpoint status
// leader
$ benchmark --endpoints="http://192.168.74.36:23791" --target-leader --conns=1 --clients=1 put --key-size=8 --sequential-keys --total=10000 --val-size=256
$ benchmark --endpoints="http://192.168.74.36:23791" --target-leader --conns=100 --clients=1000 put --key-size=8 --sequential-keys --total=100000 --val-size=256
// 全部 members
$ benchmark --endpoints="http://192.168.74.36:23791,http://192.168.74.36:23792,http://192.168.74.36:23793" --target-leader --conns=1 --clients=1 put --key-size=8 --sequential-keys --total=10000 --val-size=256
$ benchmark --endpoints="http://192.168.74.36:23791,http://192.168.74.36:23792,http://192.168.74.36:23793" --target-leader --conns=100 --clients=1000 put --key-size=8 --sequential-keys --total=100000 --val-size=256
讀取測試
$ benchmark --endpoints="http://192.168.74.36:23791,http://192.168.74.36:23792,http://192.168.74.36:23793" --conns=1 --clients=1 range foo --consistency=l --total=10000
$ benchmark --endpoints="http://192.168.74.36:23791,http://192.168.74.36:23792,http://192.168.74.36:23793" --conns=1 --clients=1 range foo --consistency=s --total=10000
$ benchmark --endpoints="http://192.168.74.36:23791,http://192.168.74.36:23792,http://192.168.74.36:23793" --conns=100 --clients=1000 range foo --consistency=l --total=100000
$ benchmark --endpoints="http://192.168.74.36:23791,http://192.168.74.36:23792,http://192.168.74.36:23793" --conns=100 --clients=1000 range foo --consistency=s --total=100000
因爲僅在本地進行測試,所受網絡帶寬影響不大,因此僅調整 io。
分析
能夠看到,測試結果中寫入操做與以上列出的幾種因素關聯比較大。讀取指標的時候,串行化要比線性化要好,但爲了一致性,線性化 (Linearizable) 讀取請求要經過集羣成員的法定人數來獲取最新的數據。串行化 (Serializable) 讀取請求比線性化讀取要廉價一些,由於他們是經過任意單臺 etcd 服務器來提供服務,而不是成員的法定人數,代價是可能提供過時數據。
本文在力所能及的範圍內對 etcd 的性能進行了必定的評估,所獲得的數據並不能做爲最終的參考數據,應當根據本身的環境進行評估,結合以上性能優化的方法獲得最終的結論。
參考
雲原生是一種信仰 🤘
掃碼關注公衆號
後臺回覆◉圖譜◉領取史上最強 Kubernetes 知識圖譜
點擊 "閱讀原文" 獲取更多資源!
❤️ 給個 「在看」 ,是對我最大的支持❤️
本文分享自微信公衆號 - 雲原生實驗室(cloud_native_yang)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。