Kafka的生產者優秀架構設計

奈學教育發表於2020-06-03

Kafka 是一個高吞吐量的分散式的釋出訂閱訊息系統,在全世界都很流行,在大資料專案裡面使用尤其頻繁。筆者看過多個大資料開源產品的原始碼,感覺 Kafka 的原始碼是其中質量比較上乘的一個,這得益於作者高超的編碼水平和高超的架構設計能力。

Kafka 的核心原始碼分為兩部分:客戶端原始碼和服務端原始碼,客戶端又分為生產者和消費者,而個人認為 Kafka 的原始碼裡面生產者的原始碼技術含量最高,所以今天給大家剖析 Kafka 的生產者的架構設計,Kafka 是一個飛速發展的訊息系統,其架構也在一直演進中,我們今天分析的 Kafka 的版本是比較成熟穩定的 Kafka1.0.0 版本原始碼。

圖1 Kafka核心模組

生產者流程概述 

先給大家介紹一下生產者的大概的執行的流程。

圖2 Kafka執行方式

如上圖所示:步驟一:一條訊息過來首先會被封裝成為一個 ProducerRecord 物件。

步驟二:接下來要對這個物件進行序列化,因為 Kafka 的訊息需要從客戶端傳到服務端,涉及到網路傳輸,所以需要實現序列。Kafka 提供了預設的序列化機制,也支援自定義序列化(這種設計也值得我們積累,提高專案的擴充套件性)。

步驟三:訊息序列化完了以後,對訊息要進行分割槽,分割槽的時候需要獲取叢集的後設資料。分割槽的這個過程很關鍵,因為這個時候就決定了,我們的這條訊息會被髮送到 Kafka 服務端到哪個主題的哪個分割槽了。

步驟四:分好區的訊息不是直接被髮送到服務端,而是放入了生產者的一個快取裡面。在這個快取裡面,多條訊息會被封裝成為一個批次(batch),預設一個批次的大小是 16K。

步驟五:Sender 執行緒啟動以後會從快取裡面去獲取可以傳送的批次。

步驟六:Sender 執行緒把一個一個批次傳送到服務端。大家要注意這個設計,在 Kafka0.8 版本以前,Kafka 生產者的設計是來一條資料,就往服務端傳送一條資料,頻繁的發生網路請求,結果效能很差。後面的版本再次架構演進的時候把這兒改成了批處理的方式,效能指數級的提升,這個設計值得我們積累。 

生產者細節深度剖析 

接下來我們生產者這兒技術含量比較高的一個地方,前面概述那兒我們看到,一個訊息被分割槽以後,訊息就會被放到一個快取裡面,我們看一下里面具體的細節。預設快取塊的大小是 32M,這個快取塊裡面有一個重要的資料結構:batches,這個資料結構是 key-value 的結果,key 就是訊息主題的分割槽,value 是一個佇列,裡面存的是傳送到對應分割槽的批次,Sender 執行緒就是把這些批次傳送到服務端。

圖3 生產者架構

01 /  生產者高階設計之自定義資料結構

生產者把批次資訊用 batches 這個物件進行儲存。如果是大家,大家會考慮用什麼資料結構去儲存批次資訊?

Kafka 這兒採取的方式是自定義了一個資料結構:CopyOnWriteMap。熟悉 Java 的同學都知道,JUC 下面是有一個 CopyOnWriteArrayList 的資料結構的,但是沒有 CopyOnWriteMap,我這兒給大家解釋一下 Kafka 為什麼要設計這樣的一個資料結構。

1. 他們儲存的資訊的是 key-value 的結構,key 是分割槽,value 是要存到這個分割槽的對應批次(批次可能有多個,所以用的是佇列),故因為是 key-value 的資料結構,所以鎖定用 Map 資料結構。

2. 這個 Kafka 生產者面臨的是一個高併發的場景,大量的訊息會湧入這個這個資料結構,所以這個資料結構需要保證執行緒安全,這樣我們就不能使用 HashMap 這樣的資料結構了。

3. 這個資料結構需要支援的是讀多寫少的場景。讀多是因為每條訊息過來都會根據 key 讀取 value 的資訊,假如有 1000 萬條訊息,那麼就會讀取 batches 物件 1000 萬次。寫少是因為,比如我們生產者傳送資料需要往一個主題裡面去傳送資料,假設這個主題有 50 個分割槽,那麼這個 batches 裡面就需要寫 50 個 key-value 資料就可以了(大家要搞清楚我們雖然要寫 1000 萬條資料,但是這 1000 萬條是寫入 queue 佇列的 batch 裡的,並不是直接寫入 batches,所以就我們剛剛說的這個場景,batches 裡只需要最多寫 50 條資料就可以了)。

根據第二和第三個場景我們總結出來,Kafka 這兒需要一個能保證執行緒安全的,支援讀多寫少的 Map 資料結構。但是 Java 裡面並沒有提供出來的這樣的一個資料,唯一跟這個需求比較接近的是 CopyOnWriteArrayList,但是偏偏它又不是 Map 結構,所以 Kafka 這兒模仿 CopyOnWriteArrayList 設計了 CopyOnWriteMap。採用了讀寫分離的思想解決了執行緒安全且支援讀多寫少等問題。

高效的資料結構保證了生產者的效能。(CopyOnWriteArrayList 不熟悉的同學,可以嘗試百度學習)。這兒筆者建議大家可以去看看 Kafka 生產者往 batches 裡插入資料的原始碼,生產者為了保證插入資料的高效能,採用了多執行緒,又為了執行緒安全,使用了分段加鎖等多種手段,原始碼非常精彩。

02 /  生產者高階設計之記憶體池設計

剛剛我們看到 batches 裡面儲存的是批次,批次預設的大小是 16K,整個快取的大小是 32M,生產者每封裝一個批次都需要去申請記憶體,正常情況下如果一個批次傳送出去了以後,那麼這 16K 的記憶體就等著 GC 來回收了。但是如果是這樣的話,就可能會頻繁的引發 FullGC,故而影響生產者的效能,所以在快取裡面設計了一個記憶體池(類似於我們平時用的資料庫的連線池),一個 16K 的記憶體用完了以後,把資料清空,放入到記憶體池裡,下個批次用的時候直接從裡面獲取就可以。這樣大大的減少了 GC 的頻率,保證了生產者的穩定和高效(Java 的 GC 問題是一個頭疼的問題,所以這種設計也非常值得我們去積累)。

結尾 

Kafka 的設計之中精彩的地方有很多,今天我們擷取了一部分跟大家分享。之前我看到過 Kafka 的原始碼以後,就想以後如果我要去當老師,去培養架構師的話,那麼我一定得跟學生分享 Kafka 的原始碼,透過學習 Kafka 原始碼提升系統架構能力,再次建議大家有空可以研究研究 Kafka 的原始碼,大家加油!!

領取更多有關架構知識及其影片

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

相關文章