kafka是如何做到高效的

上篇文章是我們在設計系統的如何讓它做到高可用,這篇文章學習一下在設計時候如何把消息隊列設計的高效,當然這個還是以kafka爲原型去學習,首先從整體上看,有哪些措施導致了kafka的快呢?

總體架構:

  • 利用Partition實現並行處理
  • ISR實現可用性與數據一致性的動態平衡

具體設計:

  • 順序寫磁盤
  • 充分利用Page Cache
  • 零拷貝
  • 批處理
  • 高效的序列化方式
  • 數據壓縮降低網絡負載
  • Kafka客戶端和服務端通信採取的是NIO的reactor模式,它是一種事件驅動模式

總體架構

1. 利用Partition實現並行處理:

  • 由於不同Partition可位於不同機器,因此可以充分利用集羣優勢,實現機器間的並行處理。

  • 由於Partition在物理上對應一個文件夾,即使多個Partition位於同一個節點,也可通過配置讓同一節點上的不同Partition置於不同的disk drive上,從而實現磁盤間的並行處理,充分發揮多磁盤的優勢。雖然物理上最小單位是Segment,但Kafka並不提供同一Partition內不同Segment間的並行處理。因爲對於寫而言,每次只會寫Partition內的一個Segment,而對於讀而言,也只會順序讀取同一Partition內的不同Segment。Partition個數決定了可能的最大並行度。

2. ISR實現可用性與數據一致性的動態平衡:

  • 由於Leader可移除不能及時與之同步的Follower,故與同步複製相比可避免最慢的Follower拖慢整體速度,也即ISR提高了系統可用性。

  • ISR中的所有Follower都包含了所有Commit過的消息,而只有Commit過的消息纔會被Consumer消費,故從Consumer的角度而言,ISR中的所有Replica都始終處於同步狀態,從而與異步複製方案相比提高了數據一致性。

  • ISR可動態調整,極限情況下,可以只包含Leader,極大提高了可容忍的宕機的Follower的數量。與MajorityQuorum方案相比,容忍相同個數的節點失敗,所要求的總節點數少了近一半。

具體設計

1. 順序寫磁盤:

  • Kafka的整個設計中,Partition相當於一個非常長的數組,而Broker接收到的所有消息順序寫入這個大數組中。同時Consumer通過Offset順序消費這些數據,並且不刪除已經消費的數據,從而避免了隨機寫磁盤的過程。

  • 由於磁盤有限,不可能保存所有數據,實際上作爲消息系統Kafka也沒必要保存所有數據,需要刪除舊的數據。而這個刪除過程,並非通過使用“讀-寫”模式去修改文件,而是將Partition分爲多個Segment,每個Segment對應一個物理文件,通過刪除整個文件的方式去刪除Partition內的數據。這種方式清除舊數據的方式,也避免了對文件的隨機寫操作。

2. 充分利用Page Cache

  • I/O Scheduler會將連續的小塊寫組裝成大塊的物理寫從而提高性能
  • I/O Scheduler會嘗試將一些寫操作重新按順序排好,從而減少磁盤頭的移動時間
  • 充分利用所有空閒內存(非JVM內存)。如果使用應用層Cache(即JVM堆內存),會增加GC負擔
  • 讀操作可直接在Page Cache內進行。如果消費和生產速度相當,甚至不需要通過物理磁盤(直接通過Page Cache)交換數據
  • 如果進程重啓,JVM內的Cache會失效,但Page Cache仍然可用

3. 零拷貝

Kafka中存在大量的網絡數據持久化到磁盤(Producer到Broker)和磁盤文件通過網絡發送(Broker到Consumer)的過程。這一過程的性能直接影響Kafka的整體吞吐量。

buffer = File.read
Socket.send(buffer)

這一過程實際上發生了四次數據拷貝。首先通過系統調用將文件數據讀入到內核態Buffer(DMA拷貝),然後應用程序將內存態Buffer數據讀入到用戶態Buffer(CPU拷貝),接着用戶程序通過Socket發送數據時將用戶態Buffer數據拷貝到內核態Buffer(CPU拷貝),最後通過DMA拷貝將數據拷貝到NIC Buffer。

Linux 2.4+內核通過sendfile系統調用,提供了零拷貝。數據通過DMA拷貝到內核態Buffer後,直接通過DMA拷貝到NIC Buffer,無需CPU拷貝。這也是零拷貝這一說法的來源。除了減少數據拷貝外,因爲整個讀文件-網絡發送由一個sendfile調用完成,整個過程只有兩次上下文切換,因此大大提高了性能。

4. 批處理

Kafka 0.8.2開始支持新的Producer API,將同步Producer和異步Producer結合。雖然從send接口來看,一次只能發送一個ProducerRecord,而不能像之前版本的send方法一樣接受消息列表,但是send方法並非立即將消息發送出去,而是通過batch.size和linger.ms控制實際發送頻率,從而實現批量發送。

由於每次網絡傳輸,除了傳輸消息本身以外,還要傳輸非常多的網絡協議本身的一些內容(稱爲Overhead),所以將多條消息合併到一起傳輸,可有效減少網絡傳輸的Overhead,進而提高了傳輸效率。

5. 高效的序列化方式

Kafka消息的Key和Payload(或者說Value)的類型可自定義,只需同時提供相應的序列化器和反序列化器即可。因此用戶可以通過使用快速且緊湊的序列化-反序列化方式(如Avro,Protocal Buffer)來減少實際網絡傳輸和磁盤存儲的數據規模,從而提高吞吐率。這裏要注意,如果使用的序列化方法太慢,即使壓縮比非常高,最終的效率也不一定高。

6. 數據壓縮降低網絡負載

Kafka從0.7開始,即支持將數據壓縮後再傳輸給Broker。除了可以將每條消息單獨壓縮然後傳輸外,Kafka還支持在批量發送時,將整個Batch的消息一起壓縮後傳輸。數據壓縮的一個基本原理是,重複數據越多壓縮效果越好。因此將整個Batch的數據一起壓縮能更大幅度減小數據量,從而更大程度提高網絡傳輸效率。

Broker接收消息後,並不直接解壓縮,而是直接將消息以壓縮後的形式持久化到磁盤。Consumer Fetch到數據後再解壓縮。因此Kafka的壓縮不僅減少了Producer到Broker的網絡傳輸負載,同時也降低了Broker磁盤操作的負載,也降低了Consumer與Broker間的網絡傳輸量,從而極大得提高了傳輸效率,提高了吞吐量。

7. NIO的reactor模式

Kafka客戶端和服務端通信採取的是NIO的reactor模式,它是一種事件驅動模式。那麼一個常見的單線程Reactor模式下,NIO線程的職責都有哪些呢?我們整理了如下幾點:

  1. 作爲NIO服務端,接收客戶端的TCP連接
  2. 作爲NIO客戶端,向服務端發起TCP連接
  3. 讀取通信對端的請求或者應答消息
  4. 向通信對端發送消息請求或者應答消息

參考地址:

http://www.jasongj.com/kafka/high_throughput/

https://www.okcode.net/article/38883

https://cloud.tencent.com/developer/article/1114834

https://www.cnblogs.com/swordfall/p/10193336.html

https://xenojoshua.com/2019/04/kafka-note/

http://www.360linker.com/wfw/577.jhtml

https://www.jianshu.com/p/7c7218885d26

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章