Kafka的生產者優秀架構設計

Kafka 是一個高吞吐量的分佈式的發佈訂閱消息系統,在全世界都很流行,在大數據項目裏面使用尤爲頻繁。筆者看過多個大數據開源產品的源碼,感受 Kafka 的源碼是其中質量比較上乘的一個,這得益於做者高超的編碼水平和高超的架構設計能力。數據庫

Kafka 的核心源碼分爲兩部分:客戶端源碼和服務端源碼,客戶端又分爲生產者和消費者,而我的認爲 Kafka 的源碼裏面生產者的源碼技術含量最高,因此今天給你們剖析 Kafka 的生產者的架構設計,Kafka 是一個飛速發展的消息系統,其架構也在一直演進中,咱們今天分析的 Kafka 的版本是比較成熟穩定的 Kafka1.0.0 版本源碼。
Kafka的生產者優秀架構設計
圖1 Kafka核心模塊
生產者流程概述
先給你們介紹一下生產者的大概的運行的流程。
Kafka的生產者優秀架構設計
圖2 Kafka運行方式
如上圖所示:步驟一:一條消息過來首先會被封裝成爲一個 ProducerRecord 對象。
步驟二:接下來要對這個對象進行序列化,由於 Kafka 的消息須要從客戶端傳到服務端,涉及到網絡傳輸,因此須要實現序列。Kafka 提供了默認的序列化機制,也支持自定義序列化(這種設計也值得咱們積累,提升項目的擴展性)。
步驟三:消息序列化完了之後,對消息要進行分區,分區的時候須要獲取集羣的元數據。分區的這個過程很關鍵,由於這個時候就決定了,咱們的這條消息會被髮送到 Kafka 服務端到哪一個主題的哪一個分區了。
步驟四:分好區的消息不是直接被髮送到服務端,而是放入了生產者的一個緩存裏面。在這個緩存裏面,多條消息會被封裝成爲一個批次(batch),默認一個批次的大小是 16K。
步驟五:Sender 線程啓動之後會從緩存裏面去獲取能夠發送的批次。
步驟六:Sender 線程把一個一個批次發送到服務端。你們要注意這個設計,在 Kafka0.8 版本之前,Kafka 生產者的設計是來一條數據,就往服務端發送一條數據,頻繁的發生網絡請求,結果性能不好。後面的版本再次架構演進的時候把這兒改爲了批處理的方式,性能指數級的提高,這個設計值得咱們積累。
生產者細節深度剖析
接下來咱們生產者這兒技術含量比較高的一個地方,前面概述那兒咱們看到,一個消息被分區之後,消息就會被放到一個緩存裏面,咱們看一下里面具體的細節。默認緩存塊的大小是 32M,這個緩存塊裏面有一個重要的數據結構:batches,這個數據結構是 key-value 的結果,key 就是消息主題的分區,value 是一個隊列,裏面存的是發送到對應分區的批次,Sender 線程就是把這些批次發送到服務端。
Kafka的生產者優秀架構設計
圖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 的源碼,你們加油!!
領取更多有關架構知識及其視頻
Kafka的生產者優秀架構設計緩存

相關文章
相關標籤/搜索