Kafka 如何最佳化記憶體緩衝機制造成的頻繁 GC 問題?

石杉的架構筆記發表於2022-12-08

目錄

1、Kafka的客戶端緩衝機制

2、記憶體緩衝造成的頻繁GC問題

3、Kafka設計者實現的緩衝池機制

4、總結一下

“ 這篇文章,給大家聊一個硬核的技術知識,我們透過Kafka核心原始碼中的一些設計思想,來看你設計Kafka架構的技術大牛,是怎麼最佳化JVM的GC問題的?


1、Kafka的客戶端緩衝機制


首先,先得給大家明確一個事情,那就是在客戶端傳送訊息給kafka伺服器的時候,一定是有一個記憶體緩衝機制的。

也就是說,訊息會先寫入一個記憶體緩衝中,然後直到多條訊息組成了一個Batch,才會一次網路通訊把Batch傳送過去。

整個過程如下圖所示:

Kafka 如何最佳化記憶體緩衝機制造成的頻繁 GC 問題?


2、記憶體緩衝造成的頻繁GC問題


那麼這種記憶體緩衝機制的本意,其實就是把多條訊息組成一個Batch,一次網路請求就是一個Batch或者多個Batch。

這樣每次網路請求都可以傳送很多資料過去,避免了一條訊息一次網路請求。從而提升了吞吐量,即單位時間內傳送的資料量。

但是問題來了,大家可以思考一下,一個Batch中的資料,會取出來然後封裝在底層的網路包裡,透過網路傳送出去到達Kafka伺服器。

那麼然後呢?

這個Batch裡的資料都傳送過去了,現在Batch裡的資料應該怎麼處理?

你要知道,這些Batch裡的資料此時可還在客戶端的JVM的記憶體裡啊!那麼此時從程式碼實現層面,一定會嘗試避免任何變數去引用這些Batch對應的資料,然後嘗試觸發JVM自動回收掉這些記憶體垃圾。

這樣不斷的讓JVM回收垃圾,就可以不斷的清理掉已經傳送成功的Batch了,然後就可以不斷的騰出來新的記憶體空間讓後面新的資料來使用。

這種想法很好,但是實際線上執行的時候一定會有問題,最大的問題,就是JVM GC問題。

大家都知道一點,JVM GC在回收記憶體垃圾的時候,他會有一個“Stop the World”的過程,也就是垃圾回收執行緒執行的時候,會導致其他工作執行緒短暫的停頓,這樣可以便於他自己安安靜靜的回收記憶體垃圾。

這個也很容易想明白,畢竟你要是在回收記憶體垃圾的時候,你的工作執行緒還在不斷的往記憶體裡寫資料,製造更多的記憶體垃圾,那你讓人家JVM怎麼回收垃圾?

這就好比在大馬路上,如果地上有很多垃圾,現在要把垃圾都掃乾淨,最好的辦法是什麼?大家都讓開,把馬路空出來,然後清潔工就是把垃圾清理乾淨。

但是如果清潔工在清掃垃圾的時候,結果一幫人在旁邊不停的嗑瓜子扔瓜子殼,吃西瓜扔西瓜皮,不停的製造垃圾,你覺得清潔工內心啥感受?當然是很憤慨了,照這麼搞,地上的垃圾永遠的都搞不乾淨了!

透過了上面的語言描述,我們再來一張圖,大家看看就更加清楚了

Kafka 如何最佳化記憶體緩衝機制造成的頻繁 GC 問題?

現在JVM GC是越來越先進,從CMS垃圾回收器到G1垃圾回收器,核心的目標之一就是不斷的縮減垃圾回收的時候,導致其他工作執行緒停頓的時間。

所以現在越是新款的垃圾回收器導致工作執行緒停頓的時間越短,但是再怎麼短,他也還是存在啊!

所以說,如何儘可能在自己的設計上避免JVM頻繁的GC就是一個非常考驗水平的事兒了。


3、Kafka設計者實現的緩衝池機制

在Kafka客戶端內部,對這個問題實現了一個非常優秀的機制,就是緩衝池的機制

簡單來說,就是每個Batch底層都對應一塊記憶體空間,這個記憶體空間就是專門用來存放寫入進去的訊息的。

然後呢,當一個Batch被髮送到了kafka伺服器,這個Batch的資料不再需要了,就意味著這個Batch的記憶體空間不再使用了。

此時這個Batch底層的記憶體空間不要交給JVM去垃圾回收,而是把這塊記憶體空間給放入一個緩衝池裡。

這個緩衝池裡放了很多塊記憶體空間,下次如果你又有一個新的Batch了,那麼不就可以直接從這個緩衝池裡獲取一塊記憶體空間就ok了?

然後如果一個Batch傳送出去了之後,再把記憶體空間給人家還回來不就好了?以此類推,迴圈往復。

同樣,聽完了上面的文字描述,再來一張圖,看完這張圖相信大夥兒就明白了:

Kafka 如何最佳化記憶體緩衝機制造成的頻繁 GC 問題?

一旦使用了這個緩衝池機制之後,就不涉及到頻繁的大量記憶體的GC問題了。

為什麼呢?因為他可以上來就佔用固定的記憶體,比如32MB。然後把32MB劃分為N多個記憶體塊,比如說一個記憶體塊是16KB,這樣的話這個緩衝池裡就會有很多的記憶體塊。

然後你需要建立一個新的Batch,就從緩衝池裡取一個16KB的記憶體塊就可以了,然後這個Batch就不斷的寫入訊息,但是最多就是寫16KB,因為Batch底層的記憶體塊就16KB。

接著如果Batch被髮送到Kafka伺服器了,此時Batch底層的記憶體塊就直接還回緩衝池就可以了。

下次別人再要構建一個Batch的時候,再次使用緩衝池裡的記憶體塊就好了。這樣就可以利用有限的記憶體,對他不停的反覆重複的利用。因為如果你的Batch使用完了以後是把記憶體塊還回到緩衝池中去,那麼就不涉及到垃圾回收了。

如果沒有頻繁的垃圾回收,自然就避免了頻繁導致的工作執行緒的停頓了,JVM GC問題是不是就得到了大幅度的最佳化?

沒錯,正是這個設計思想讓Kafka客戶端的效能和吞吐量都非常的高,這裡蘊含了大量的優秀的機制。

那麼此時有人說了,如果我現在把一個緩衝池裡的記憶體資源都佔滿了,現在緩衝池裡暫時沒有記憶體塊了,怎麼辦呢?

很簡單,阻塞你的寫入操作,不讓你繼續寫入訊息了。把你給阻塞住,不停的等待,直到有記憶體塊釋放出來,然後再繼續讓你寫入訊息。


4、總結一下


這篇文章我們從Kafka記憶體緩衝機制的設計思路開始,一直分析到了JVM GC問題的產生原因以及惡劣的影響。

接著談到了Kafka優秀的緩衝池機制的設計思想以及他是如何解決這個問題的,分析了很多Kafka作者在設計的時候展現出的優秀的技術設計思想和能力。

希望大家多吸取這裡的精華,在以後面試或者工作的時候,可以把這些優秀的思想納為己用。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901780/viewspace-2659417/,如需轉載,請註明出處,否則將追究法律責任。

相關文章