這篇博文是探索三個分布式、一致性鍵值數據存儲軟件性能的系列文章中的第一篇:etcd、Zookeeper和Consul,由etcd團隊所寫,可以讓我們全面地了解如何評估三個分布式一致存儲軟件的性能。翻譯過程中難免有誤差,還請大家諒解
一致性鍵值存儲的用處
許多現代分布式應用程序都建立在分布式一致鍵值存儲之上。Hadoop
生態系統中的應用程序和“Netflix棧”的許多部分都使用Zookeeper
。Consul
公開了服務發現和運行狀況檢查API
,並支持Nomad
等集群工具。Kubernetes
容器編排系統,MySQL
的Vitess
水平擴展,Google Key Transparency
項目以及許多其他系統都是基於etcd
構建的。有了這么多關鍵任務集群,服務發現和基於這些一致鍵值存儲的數據庫應用程序,測量可靠性和性能是至關重要的。
滿足寫性能需要的條件
理想的鍵值存儲每秒攝取許多鍵,快速持久並確認每次寫入,並保存大量數據。如果存儲無法跟上寫入,那么請求將超時,可能會觸發故障轉移和停機。如果寫入速度很慢,那么應用程序看起來很慢。如果數據過多,存儲系統可能會爬行甚至無法運行。
我們使用dbtester來模擬寫入,並發現etcd
在這些基准測試中優於類似的一致分布式鍵值存儲軟件。在較低的層面上,背后的架構決策可以更加統一和有效地利用資源,這些決策轉化為在合理的工作負載和規模下可靠的良好吞吐量、延遲和總容量。這反過來又有助於使用etd
的應用程序,如Kubernetes
,可靠、易於監控和高效。
性能有很多方面,本文將深入介紹鍵的創建,鍵值的填充和存儲,來說明底層的機制。
資源利用
在跳到高級性能之前,首先通過資源利用率和並發性突出鍵值存儲行為的差異是有幫助的,寫操作為驗證這個問題提供了一個很好的例子。寫操作必須和磁盤關聯起來,因為寫操作會持久鍵值到媒體。然后,這些數據必須跨服務器進行復制(replicate across machines
),從而導致集群間大量網絡流量。這些流量構成了處理寫的全部開銷的一部分,這會消耗CPU
。最后,將鍵放入存儲區直接利用內存獲取鍵用戶數據,並間接用於簿記(book-keeping
)。
根據最近的一項用戶調查,大多數etcd
部署都使用虛擬機。為了遵守最常見的平台配置,所有的測試都在谷歌雲平台計算引擎虛擬機(Google Cloud Platform Compute Engine virtual machines
)上運行,並且使用Linux OS
(所有虛擬機都有Ubuntu 16.10
, Linux
內核4.8.0-37-generic
和ext4
文件系統。我們選擇Ubuntu
作為一個中立的Linux
操作系統,而不是像Container Linux
那樣的CoreOS
項目,以說明這些結果應該適用於任何類型的Linux
發行版。但是,在容器Linux
上收集這些基准測試將得到非常相似的結果)。每個集群使用三個VM
,足以容忍單個節點故障。每個VM
都有16
個專用的vcpu
、30GB
內存和300GB SSD
,可以達到150 MB/s
的持續寫操作。這個配置足夠強大,可以模擬來自1000
個客戶機的流量,這對於etcd
的用例和以下資源度量所選擇的目標來說是最小的。所有的測試都進行了多次試驗,在運行之間的偏差相對較小,不影響任何一般結論。etcd
的使用如下圖所示:
鍵值存儲基准測試設置
所有基准測試都使用以下軟件配置:
軟件名稱 | 版本 | 編譯語言版本 |
---|---|---|
etcd | v3.1.0 | Go 1.7.5 |
Zookeeper | r3.4.9 | Java 8 (JRE build 1.8.0_121-b13) |
Consul | v0.7.4 | Go 1.7.5 |
每個資源利用率測試都會創建一百萬個具有1024字節值的唯一256字節鍵(one million unique 256-byte keys with 1024-byte values
)。選擇鍵長度以使用共同的最大路徑長度對存儲軟件施加壓力,選擇值長度是因為它是protobuf
編碼的Kubernetes值的預期平均大小。雖然精確的平均鍵長度和值長度是與工作負載相關的,但長度代表極端之間的權衡。更精確的敏感性研究將為每個存儲軟件提供更多關於最佳案例表現特征的見解,但風險更大。
磁盤帶寬
寫操作必須持久化到磁盤,他們記錄共識提案(consensus proposals
),壓縮舊數據,並保存存儲快照。在大多數情況下,寫入應該以記錄共識提案為主。etcd
的日志(log)將protobuf
編碼的提議流轉換為一系列預分配文件,在頁面邊界處同步,每個條目都有滾動的CRC32
校驗和。 Zookeeper
的事務日志(transaction log)類似,但是使用Adler32
進行jute
編碼和校驗和。Consul
采用不同的方法,而是記錄到boltdb/bolt后端,raft-boltdb。
下圖顯示了擴展客戶端並發性如何影響磁盤寫入。正如預期的那樣,當並發性增加時,ext4
文件系統上/proc/diskstats
上的磁盤帶寬會增加,以應對請求壓力的增加。etcd
的磁盤帶寬穩定增長,它寫的數據比Zookeeper
還多,因為除了日志外,它還必須寫boltDB
。另一方面,Zookeeper
會因為寫入完整的狀態快照而丟失數據速率,這些完整的快照與etcd
的增量和並發提交相反,后者只寫入更新,而不會停止所有正在進行的操作(stopping the world
)。Consul
的數據率最初大於etcd
,這可能是由於在B+
樹中刪除了提交的raft
協議(raft proposals
)而導致的寫放大,然后由於花了幾秒鍾寫快照而出現波動。
創建一百萬個鍵時的平均服務器磁盤寫入吞吐量
網絡
網絡是分布式鍵值存儲的中心。客戶端與鍵值存儲集群的服務器進行通信,集群中的服務器相互通信。每個鍵值存儲都有自己的客戶端協議,etcd
客戶端使用建立在HTTP/2之上的Protocol Buffer v3 gRPC協議,Zookeeper客戶端使用自定義的流式TCP
協議Jute,Consul
使用JSON
。同樣,每個協議都有自己的TCP
服務器協議。etcd peer stream protobuf編碼的raft RPC提議,Zookeeper將TCP流用於有序的雙向jute編碼ZAB通道,Consul發布用MsgPack編碼的raft RPC。
下表顯示了所有服務器和客戶端的總網絡利用率。在大多數情況下,etcd
具有最低的網絡使用率,除了Consul
客戶端接收的數據略少。這可以通過etcd
的Put
響應來解釋,其中包含帶有修訂數據的標題,而Consul
只是以明文true
響應。Zookeeper
和Consul
的服務器間流量可能是由於傳輸大型快照和節省空間的協議編碼較少。
使用1,000個客戶端創建一百萬個鍵時傳輸的總數據量
CPU
即使存儲和網絡速度很快,集群也必須小心處理開銷。浪費CPU
的機會比比皆是:許多消息必須被編碼和解碼,糟糕的並發控制可以對抗鎖定,系統調用可以以驚人的頻率進行,並且內存堆可以捶打。 由於etcd
、Zookeeper
和Consul
都希望leader
服務器節點處理寫入,因此較差的CPU
利用率可以輕松降低性能。
下圖顯示了在擴展客戶端時使用top -b -d 1
測量的服務器CPU
利用率。etcd CPU
利用率按預期平均和最大負載進行擴展,隨着更多連接的增加,CPU
負載依次增加。最引人注目的是Zookeeper
的平均跌幅為700
,但客戶端數量增加了1000
,日志報告太忙以捕捉,跳過其SyncRequestProcessor
,然后創建新的日志文件,從1,073%
利用率到230%
。 這種下降也發生在1,000
個客戶端,但從平均值來看不太明顯,利用率從894%
上升到321%
。同樣,處理快照時Consul CPU
利用率下降10
秒,從389% CPU
降至16%
。
用於在客戶端擴展時創建一百萬個鍵的服務器CPU使用
內存
當鍵值存儲設計為僅管理元數據大小的數據時,大多數數據可以緩存在內存中。維護內存數據庫可以提高速度,但代價是過多的內存占用可能會導致頻繁的垃圾回收和磁盤交換,從而降低整體性能。當Zookeeper和Consul在內存中加載所有鍵值數據時,etcd
只保留一個小的駐留內存索引,直接通過boltdb中的內存映射文件支持其大部分數據,僅將數據保存在boltDB
中會因請求分頁而導致磁盤訪問,但總體而言,etcd
更好地考慮操作系統設施。
下圖顯示了在集群總內存占用量中向集群添加更多鍵的效果。最值得注意的是,一旦存儲系統中有大量的鍵,etcd
使用的內存量不到Zookeeper
或Consul
的一半。Zookeeper
位居第二,占據了四倍的內存,這符合仔細調整JVM
堆設置的建議(recommendation)。最后,盡管Consul
使用了etcd
所用的boltDB,但它的內存存儲(in-memory store)否定了etcd
中的占用空間優勢,消耗了三者中最大的內存。
創建一百萬個鍵時的服務器內存占用
存儲爆炸
隨着物理資源的確定,重點可以回歸到聚合基准測試。首先,為了找到最大鍵提取率,系統並發性可擴展到一千個客戶端。這些最佳攝取率為測量負載下的延遲提供了基礎,從而衡量總的等待時間。同樣,每個系統客戶端以最佳攝取速率計數,當密鑰從一百萬個鍵擴展到三百萬個鍵時,可以通過測量吞吐量的下降來強調總容量。
吞吐量變化
隨着越來越多的客戶端同時寫入集群,理想情況下,在提升之前,提取率應該穩定上升。但是,下圖顯示在寫出一百萬個鍵時縮放客戶端數量時不是這種情況。 相反,Zookeeper
(最大速率為43,458 req/sec
)波動很大,這並不奇怪,因為它必須明確配置為允許大量連接。Consul
的吞吐量(最大速率16,486 req/sec
)可以很好地擴展,但在並發壓力下會降低到低速率。etcd
的吞吐量(最大速率34,747 req/sec
)總體穩定,隨着並發性而緩慢上升。最后,盡管Consul
和Zookeeper
使用了更多的CPU
,但最大吞吐量仍然落后於etcd
。
隨客戶端規模創建一百萬個鍵的平均吞吐量
延遲分布
鑒於存儲系統的最佳吞吐量,延遲應該是局部最小且穩定,排隊效應將延遲其他並發操作。同樣,理想情況下,隨着鍵總數的增加,延遲會保持低且穩定,如果請求變得不可預測,則可能存在級聯超時,抖動監視警報或故障。然而,通過下面顯示的延遲測量來判斷,只有etcd
具有最低的平均等待時間和規模上的緊密、穩定的界限。
etcd,Zookeeper和Consul鍵創建延遲位數和范圍
Zookeeper
努力為並發客戶提供最佳吞吐量,一旦它觸發快照,客戶端請求就會開始失敗。服務器記錄列表錯誤,例如Too busy to snap, skipping, fsync-ing the write ahead log
和fsync-ing the write ahead log in SyncThread: 1 took 1,038 ms which will adversely effect operation latency
,最終導致leader
節點丟失,Exception when following the leader
。客戶端請求偶爾會失敗,包括zk
等錯誤,例如zk: could not connect to a server
和zk: connection closed
錯誤。Consul
報告沒有錯誤,盡管可能應該,它經歷了廣泛的差異降級性能,可能是由於其大量寫入放大。
鍵總數
憑借最佳平均吞吐量達到100萬個鍵的最大並發性,可以在容量擴展時測試吞吐量。下圖顯示了時間序列延遲,以及延遲峰值的對數標度,因為鍵被添加到存儲中,最多可達300
萬個鍵。在大約50
萬個鍵之后,Zookeeper
和Consul
延遲峰值都會增長。由於高效的並發快照,etcd
沒有出現尖峰,但是在一百萬個鍵之前略有延遲。
特別值得注意的是,就在兩百萬個鍵之前,Zookeeper
完全失敗了。其追隨者落后,未能及時收到快照,這表明領導者選舉需要長達20
秒才能鎖定集群。
創建300萬個鍵時的延遲
下一步是什么
在創建一百萬個或更多鍵時,etcd
可以比Zookeeper
或Consul
穩定地提供更好的吞吐量和延遲。此外,它實現了這一目標,只有一半的內存,顯示出更高的效率。但是,還有一些改進的余地,Zookeeper
設法通過etcd
提供更好的最小延遲,代價是不可預測的平均延遲。
所有基准測試都是使用etcd
的開源dbtester生成的。任何希望重現結果的人都可以獲得上述測試的測試用例參數。對於更簡單,僅限etcd
的基准測試,請嘗試使用etcd3
基准測試工具。
編譯自:Exploring Performance of etcd, Zookeeper and Consul Consistent Key-value Datastores