Apache Kafka設計思考

Arno_z發表於2024-10-14

kafka設計

  • 微信公眾號:阿俊的學習記錄空間
  • 小紅書:ArnoZhang
  • wordpress:arnozhang1994
  • 部落格園:arnozhang
  • CSDN:ArnoZhang1994

一、目標

能夠作為一個統一的平臺,處理大型公司可能擁有的所有實時資料流。(更像是資料庫日誌)

  • 高吞吐量:Kafka必須具有高吞吐量,以支援高容量的事件流,例如實時日誌聚合。
  • 處理資料積壓:它需要能夠優雅地處理大量資料積壓,以支援離線系統的週期性資料載入。
  • 低延遲傳輸:這也意味著系統必須能夠處理低延遲傳輸,以滿足更傳統的訊息傳遞用例。
  • 分割槽與實時處理:我們希望支援這些資料流的分割槽、分散式、實時處理,以建立新的派生資料流。這激發了我們的分割槽和消費者模型。
  • 容錯性:最後,在將資料流傳輸到其他資料系統以提供服務的場景中,我們知道系統必須能夠在機器故障時保證容錯性。

二、持久化

Kafka嚴重依賴檔案系統來儲存和快取訊息。設計良好的磁碟結構通常可以和網路一樣快。

磁碟效能的關鍵在於,過去十年裡,硬碟的吞吐量與磁碟尋道延遲的差距正在擴大。結果是,配置六個7200rpm SATA RAID-5陣列的JBOD(只是一堆磁碟)可以提供約600MB/秒的線性寫入效能,但隨機寫入的效能卻只有約100k/秒——差距超過6000倍。這些線性讀寫操作是所有使用模式中最可預測的,作業系統對此進行了大量最佳化。現代作業系統提供了預讀和回寫技術,這些技術可以預取大塊資料並將較小的邏輯寫操作組合成較大的物理寫操作。關於這個問題的進一步討論可以參考ACM Queue的文章;他們實際上發現順序磁碟訪問有時比隨機記憶體訪問更快!

為了彌補這種效能差異,現代作業系統在磁碟快取上變得越來越積極。現代作業系統會愉快地將所有空閒記憶體用於磁碟快取,而當記憶體被回收時,效能損失很小。所有磁碟的讀寫操作都會經過這個統一的快取。如果不使用直接I/O,很難關閉此功能,因此即使一個程序維護了一個程序內快取,這些資料可能仍然會在作業系統頁面快取中被複制,實際上將所有內容儲存了兩次。

此外,kafka是基於JVM的,任何使用過Java記憶體的人都知道兩件事:

  • 物件的記憶體開銷非常高,通常會使儲存的資料量加倍(甚至更糟)。
  • 隨著堆內資料的增加,Java垃圾回收變得越來越複雜和緩慢。

磁碟頁面檔案是指作業系統用來構建虛擬記憶體的硬碟空間。
頁快取是Linux核心一種重要的磁碟快取記憶體,它透過軟體機制實現。

由於這些因素,使用檔案系統並依賴頁面快取要優於維護記憶體快取或其他結構——透過自動訪問所有空閒記憶體,我們至少可以將可用快取容量加倍,並且由於儲存的是壓縮的位元組結構而不是單個物件,可能會再次加倍。這樣可以在32GB機器上得到高達28-30GB的快取空間,並且不會有GC(垃圾回收)帶來的效能損失。此外,即使服務重新啟動,這個快取仍然保持熱態,而程序內快取則需要在記憶體中重建(對於一個10GB的快取,這可能需要10分鐘),否則將不得不從完全冷態快取開始(這可能意味著初始效能非常差)。這也大大簡化了程式碼,因為所有維護快取和檔案系統一致性的邏輯現在都由作業系統處理,作業系統通常比一次性程序內嘗試更有效、更正確。如果你的磁碟使用偏向於線性讀取,那麼預讀操作實際上是在每次磁碟讀取時用有用資料預填充這個快取。

這表明了一種非常簡單的設計:與其儘可能多地在記憶體中維護資料,並在空間不足時恐慌地將其全部重新整理到檔案系統中,我們反其道而行之。所有資料都立即寫入檔案系統上的持久日誌,而不一定要重新整理到磁碟上。實際上,這意味著資料被傳輸到了核心的頁面快取中。

三、效率

我們投入了大量精力提高效率。我們最主要的用例之一是處理網頁活動資料,這是一種非常高容量的資料:每個頁面瀏覽可能會生成幾十次寫操作。此外,我們假設每個釋出的訊息至少會被一個消費者讀取(通常是多個),因此我們努力使消費變得儘可能便宜。

我們還從構建和執行許多類似系統的經驗中發現,效率是實現多租戶操作的關鍵。如果下游基礎設施服務由於應用程式使用中的一個小突增而輕易成為瓶頸,這樣的小變化往往會引發問題。透過確保基礎設施不會成為效能瓶頸,我們可以幫助確保在負載下首先崩潰的是應用程式,而不是基礎設施。這一點在執行一個支援幾十或上百個應用程式的集中式叢集時尤為重要,因為使用模式的變化幾乎是每天都會發生的。

我們在前一節討論了磁碟效率。一旦消除了不良的磁碟訪問模式,這類系統中的兩個常見低效原因是:過多的小I/O操作和過多的位元組複製。

小I/O問題發生在客戶端與伺服器之間以及伺服器自身的持久操作中。

為避免此問題,我們的協議圍繞著“訊息集”抽象構建,自然地將訊息分組在一起。這允許網路請求將訊息分組在一起,分攤網路往返的開銷,而不是一次只傳送一條訊息。伺服器則一次將大量訊息附加到日誌中,消費者也一次獲取大塊的線性資料。

這一簡單的最佳化帶來了數量級的加速。批處理使網路包更大,順序磁碟操作更大,記憶體塊更連續,所有這些都允許Kafka將隨機的訊息寫入流轉變為線性寫入,從而流向消費者。

四、生產者

負載均衡

生產者將資料直接傳送到分割槽的主副本,避免了中間路由層的複雜性。所有Kafka節點都可以響應後設資料請求,告知主題的各個分割槽的主副本位置,允許生產者適當地定向請求。
生產者可以選擇透過分割槽鍵對資料進行語義分割槽,確保同一鍵值的資料傳送到同一個分割槽,方便消費者進行本地化處理。

非同步傳送

為了提高效率,Kafka生產者積累記憶體中的資料並批次傳送。批次大小和等待時間可配置,例如64k或10毫秒,提供了在延遲和吞吐量之間的權衡。生產者透過批處理減少伺服器上較小I/O操作的開銷。

五、消費者

Kafka消費者透過向分割槽的主代理發出"fetch"請求來工作,並指定從何處開始消費日誌。消費者對消費位置有完全控制,支援倒回重新消費。

推送與拉取

Kafka採用拉取模式,允許消費者控制資料消費速度,避免了推送模式下因消費速率不足導致的過載問題。拉取模式支援資料批處理,確保不影響延遲的情況下最佳化傳輸效率。

Kafka透過長輪詢避免緊密輪詢輪詢的忙等待問題,消費者可以等待資料的到來或設定一個位元組限制,確保批次資料傳輸。
拉取模式更適合多生產者、大規模資料管道的應用場景。

消費者位置

Kafka的分割槽是有序的,每個分割槽的消費者位置僅是一個整數,代表下一個要消費的訊息偏移量。這使得已消費資料的狀態非常小,並可以定期進行檢查點操作。消費者可以重新消費資料,支援對Bug修復後的回溯處理。

離線資料載入

Kafka支援批次資料載入,如載入到Hadoop或資料倉儲中。Hadoop的每個節點/主題/分割槽組合一個任務,支援完全並行載入。

六、靜態成員身份

靜態成員身份用於提高基於組rebalance協議構建的流應用程式的可用性。動態成員身份在任務重新分配時可能導致有狀態應用程式的任務恢復時間過長,影響可用性。Kafka透過允許組成員提供持久的實體ID來避免不必要的重平衡,保證組成員身份不變,從而提升系統的可用性。

使用靜態成員身份步驟:

  1. 將代理叢集和客戶端應用程式升級到2.3或更高版本。
  2. 為每個消費者例項設定唯一的GROUP_INSTANCE_ID_CONFIG值。
  3. 對Kafka Streams應用程式,使用application.server引數,確保部署的例項具有唯一值。

相關文章