kafka叢集基於吞吐量指標進行效能調優實踐-kafka 商業環境實戰
本套技術專欄是作者(秦凱新)平時工作的總結和昇華,通過從真實商業環境抽取案例進行總結和分享,並給出商業應用的調優建議和叢集環境容量規劃等內容,請持續關注本套部落格。期待加入IOT時代最具戰鬥力的團隊。QQ郵箱地址:[email protected],如有任何學術交流,可隨時聯絡。
概要
- 效能
- 吞吐量:broker或者clients應用程式每秒能處理多少位元組(或訊息)
- 延時:通常指Producer傳送到broker端持久化儲存訊息之間的時間間隔。
- 可用性:系統和元件正常執行的概率或時間比率,業界一般N個9來量化可用性,比如年度4個9表示53分鐘(365* 24 * 60 * 0.01%=53分鐘)
- 永續性:已提交的訊息需要被持久化到Broker端底層的檔案系統的物理日誌而不能丟失。
1 kafka 基礎設施優化
-
磁碟容量:首先考慮的是所需儲存的訊息所佔用的總磁碟容量和每個broker所能提供的磁碟空間。如果Kafka叢集需要保留 10TB資料,單個broker能儲存 2TB,那麼我們需要的最小Kafka叢集大小5 個broker。此外,如果啟用副本引數,則對應的儲存空間需至少增加一倍(取決於副本引數)。這意味著對應的Kafka叢集至少需要 10 個broker。
-
檔案系統在檔案被訪問、建立、修改等的時候會記錄檔案的一些時間戳,比如:檔案建立時間(ctime)、最近一次修改時間(mtime)和最近一次訪問時間(atime)。預設情況下,atime的更新會有一次讀操作,這會產生大量的磁碟讀寫,然而atime對Kafka完全沒用。
mount -o noatime
-
絕大多數執行在Linux上的軟體都是基於EXT4構建和測試的,因此相容性上EXT4要優於其他檔案系統。
-
作為高效能的64位日誌檔案系統(journaling file system),XFS表現出高效能,高伸縮性,特別適應於生產伺服器,特別是大檔案(30+GB)操作。很多儲存類的應用都適合選擇XFS作為底層檔案系統。
-
計算機的記憶體分為虛擬記憶體和實體記憶體。實體記憶體是真實的記憶體,虛擬記憶體是用磁碟來代替記憶體。
並通過swap機制實現磁碟到實體記憶體的載入和替換,這裡面用到的磁碟我們稱為swap磁碟。在寫檔案的時候,Linux首先將資料寫入沒有被使用的記憶體中,這些記憶體被叫做記憶體頁(page cache)。然後讀的時候,Linux會優先從page cache中查詢,如果找不到就會從硬碟中查詢。
當實體記憶體使用達到一定的比例後,Linux就會使用進行swap,使用磁碟作為虛擬記憶體。
通過cat /proc/sys/vm/swappiness可以看到swap引數。這個引數表示虛擬記憶體中swap磁碟佔了多少百分比。0表示最大限度的使用記憶體,100表示儘量使用swap磁碟。系統預設的引數是60,當實體記憶體使用率達到40%,就會頻繁進行swap,影響系統性能,推薦將vm.swappiness 設定為較低的值1。
最終我設定為10,因為我們的機器的記憶體還是比較小的,只有40G,設定的太小,可能會影響到虛擬記憶體的使用吧。臨時修改:sudo sysctl vm.swappiness=N 永久修改(/etc/sysctl.conf):vm.swappiness=N
2 kafka JVM設定
-
PermGen space : 全稱是Permanent Generation space,是指記憶體的永久儲存區域,為什麼會發生記憶體溢位?這一部分用於存放Class和Meta的資訊, Class在被 Load的時候被放入PermGen space區域,它和存放Instance的Heap區域不同,所以如果你的APP會LOAD很多CLASS的話,就很可能出現PermGen space錯誤。
-
G1演算法將堆劃分為若干個區域(Region),它仍然屬於分代收集器。不過,這些區域的一部分包含新生代,新生代的垃圾收集依然採用暫停所有應用執行緒的方式,將存活物件拷貝到老年代或者Survivor空間。老年代也分成很多區域,G1收集器通過將物件從一個區域複製到另外一個區域,完成了清理工作。這就意味著,在正常的處理過程中,G1完成了堆的壓縮(至少是部分堆的壓縮),這樣也就不會有cms記憶體碎片問題的存在了。
-
在G1中,還有一種特殊的區域,叫Humongous區域。 如果一個物件佔用的空間超過了分割槽容量50%以上,G1收集器就認為這是一個巨型物件。這些巨型物件,預設直接會被分配在年老代,但是如果它是一個短期存在的巨型物件,就會對垃圾收集器造成負面影響。為了解決這個問題,G1劃分了一個Humongous區,它用來專門存放巨型物件。如果一個H區裝不下一個巨型物件,那麼G1會尋找連續的H分割槽來儲存。為了能找到連續的H區,有時候不得不啟動Full GC。
-
G1採用記憶體分割槽(Region)的思路,將記憶體劃分為一個個相等大小的記憶體分割槽,回收時則以分割槽為單位進行回收,存活的物件複製到另一個空閒分割槽中。由於都是以相等大小的分割槽為單位進行操作,因此G1天然就是一種壓縮方案(區域性壓縮);
-
G1雖然也是分代收集器,但整個記憶體分割槽不存在物理上的年輕代與老年代的區別,也不需要完全獨立的survivor(to space)堆做複製準備。G1只有邏輯上的分代概念,或者說每個分割槽都可能隨G1的執行在不同代之間前後切換;
-
G1的收集都是STW的,但年輕代和老年代的收集界限比較模糊,採用了混合(mixed)收集的方式。即每次收集既可能只收集年輕代分割槽(年輕代收集),也可能在收集年輕代的同時,包含部分老年代分割槽(混合收集),這樣即使堆記憶體很大時,也可以限制收集範圍,從而降低停頓。
-
堆記憶體中一個Region的大小可以通過-XX:G1HeapRegionSize引數指定,大小區間只能是1M、2M、4M、8M、16M和32M,總之是2的冪次方,如果G1HeapRegionSize為預設值,則在堆初始化時計算Region的實踐大小,預設把堆記憶體按照2048份均分,最後得到一個合理的大小。
-
JVM 8 metaSpace 誕生了: 不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機器中,而是使用本地記憶體。因此,預設情況下,元空間的大小僅受本地記憶體限制,但可以通過以下引數來指定元空間的大小:
-XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行型別解除安裝,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize時,適當提高該值。 -
-XX:MinMetaspaceFreeRatio,在GC之後,最小的Metaspace剩餘空間容量的百分比,減少為分配空間所導致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之後,最大的Metaspace剩餘空間容量的百分比,減少為釋放空間所導致的垃圾收集 -
XX:MaxGCPauseMillis=n : 設定最大GC停頓時間(GC pause time)指標(target). 這是一個軟性指標(soft goal), JVM 會盡量去達成這個目標。
-
InitiatingHeapOccupancyPercent: 整個堆疊使用達到百分之多少的時候,啟動GC週期. 基於整個堆,不僅僅是其中的某個代的佔用情況,G1根據這個值來判斷是否要觸發GC週期, 0表示一直都在GC,預設值是45(即45%慢了,或者說佔用了)
-
-XX:G1NewSizePercent 新生代最小值,預設值5%
-
-XX:G1MaxNewSizePercent 新生代最大值,預設值60%
-
MetaspaceSize: 這個JVM引數是指Metaspace擴容時觸發FullGC的初始化閾值,也是最小的閾值。
# export JAVA_HOME=/usr/java/jdk1.8.0_51 # export KAFKA_HEAP_OPTS=" -Xmx6g -Xms6g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:+G1HeapRegionSize=16M -XX:MinMetaspaceFreeRatio=50 "
-
本套技術專欄是作者(秦凱新)平時工作的總結和昇華,通過從真實商業環境抽取案例進行總結和分享,並給出商業應用的調優建議和叢集環境容量規劃等內容,請持續關注本套部落格。期待加入IOT時代最具戰鬥力的團隊。QQ郵箱地址:[email protected],如有任何學術交流,可隨時聯絡。
3 檔案調優
-
若出現"too many files open"錯誤時,就需要為Broker所在的機器調優最大檔案部署符上限。檔案控制代碼個數計算方法如下:
broker上可能最大分割槽數*(每個分割槽平均資料量/平均的日誌段大小+3)其中3 表示索引檔案個數, 假設20個分割槽,分割分割槽總資料量為100GB,每一個日誌段大小是1GB,那麼這臺機器最大檔案部署符大小應該是: 20*(100/1+3)=2060 因此該引數一定要設定足夠大。比如100000
-
若出現"java.lang.OutOfMemoryError:Map failed的嚴重錯誤,主要原因是大量建立topic將極大消耗作業系統記憶體,使用者可以適當調整vm.max.map.count引數,具體方法如下:
/sbin/sysctl -w vm.max_map_count = N ,該引數預設值是65535,可以考慮線上環境設定更大的值。
4 吞吐量
Broker端;
- 適當增加num.replica.fetchers,但不要超過CPU核數,該值控制了broker端follower副本從leader副本處獲取訊息的最大執行緒數。預設值是1,表明follower副本只使用一個執行緒實時拉取leader處的最新訊息。對於設定了acks=all的producer而言,主要延時可能耽誤在follower與leader同步過程,所以增加該值可以縮短同步的時間,從而間接的提升Producer端的TPS。
- 調優GC避免經常性的Full GC。老版本過渡依賴Zookeeper來表徵Consumer還活著,若GC時間過長,會導致Zookeeper會話過期,kafka會立即對group進行rebalance。新版本上已經棄用了對Zookeeper的依賴。
Producer端:
- 適當增加batch.size,比如100-512KB。
- 適當增加linger.ms,比如10-100毫秒
Producer端是批量傳送訊息的,更大的batch size可以令更多的訊息封裝進同一個請求,故傳送給broker端的總請求數就會減少,此舉可以減輕Producer的負載,也降低了broker端的CPU請求處理開銷。而更大的linger.ms使producer等待更長的時間才傳送訊息,這樣就能夠快取更多的訊息填滿batch,從而從整體上提升TPS。但是延時肯定增加了。 - 設定壓縮型別compression.type=lz4,目前支援的壓縮方式有:GZIP,Snappy,LZ4,在CPU資源豐富的情況下compression.type=lz4的效果是最好的。
- acks=0或1
- retries=0
- 若多執行緒共享Produer或分割槽數很多時,增加buffer.memory。因為每一個分割槽都會佔用一個batch.size。
consumer端
- 採用多Consumer例項,共同消費多分割槽資料,這些例項共享相同的group.id。
- 增加fetch.min.bytes,比如10000,表徵每次leader副本所在的broker所返回的最小資料量來間接影響TPS,通過增加該值,Kafka會為每一個FETCH請求的response填入更多的資料。
5 悖論存在(分割槽數越多,TPS越高)
- Kafka基本的並行單元就是分割槽,producer在設計時實現了能夠同時向多個分割槽傳送訊息,從而這些訊息最終寫入到多個broker上,最終可以被多個consumer同時消費。通常來說,分割槽數越多,TPS越高
- 分割槽數越多,佔用的緩衝區越多,因為緩衝區是以分割槽為粒度的,所以Server/clients端將佔用更多的記憶體。
- 每一個分割槽在底層檔案系統中都有專屬目錄,除了3個索引檔案外還儲存日誌段檔案,會佔用大量的檔案控制代碼。
- 每一個分割槽都有若干個副本儲存在不同的broker上,當broker掛掉後,因為需要Controller進行處理leader變更請求,該處理執行緒是單執行緒,瘋了吧,亞歷山大。
- 本套技術專欄是作者(秦凱新)平時工作的總結和昇華,通過從真實商業環境抽取案例進行總結和分享,並給出商業應用的調優建議和叢集環境容量規劃等內容,請持續關注本套部落格。期待加入IOT時代最具戰鬥力的團隊。QQ郵箱地址:[email protected],如有任何學術交流,可隨時聯絡。
7 總結
num.replica.fetchers倒是一個新穎的選手,可以好好試試,acks重點關注,其他都中規中矩。一片好文得來不易,尊重原創,謝絕轉載,謝謝!
本套技術專欄是作者(秦凱新)平時工作的總結和昇華,通過從真實商業環境抽取案例進行總結和分享,並給出商業應用的調優建議和叢集環境容量規劃等內容,請持續關注本套部落格。期待加入IOT時代最具戰鬥力的團隊。QQ郵箱地址:[email protected],如有任何學術交流,可隨時聯絡。
秦凱新 於深圳 201812032355