kafka-高性能祕密

利用Partition實現並行處理

機器間的並行處理
磁盤間的並行處理
一個Partition只能被一個Consumer消費 Partition的個數決定了最大並行度

ISR實現CAP中可用性與數據一致性的動態平衡

由於Leader可移除不能及時與之同步的Follower,故與同步複製相比可避免最慢的Follower拖慢整體速度,也即ISR提高了系統可用性。
ISR中的所有Follower都包含了所有Commit過的消息,而只有Commit過的消息纔會被Consumer消費,故從Consumer的角度而言,ISR中的所有Replica都始終處於同步狀態,從而與異步複製方案相比提高了數據一致性。
ISR可動態調整,極限情況下,可以只包含Leader,極大提高了可容忍的宕機的Follower的數量。與Majority Quorum方案相比,容忍相同個數的節點失敗,所要求的總節點數少了近一半。

順序寫磁盤

每一個Partition其實都是一個文件 ,收到消息後Kafka會把數據插入到文件末尾
Kafka是不會自動刪除數據的,它會把所有的數據都保留下來,每個消費者(Consumer)對每個Topic都有一個offset用來表示 讀取到了第幾條數據
Kakfa提供了兩種策略來刪除數據。一是基於時間,二是基於partition文件大小

Page Cache

I/O Scheduler會將連續的小塊寫組裝成大塊的物理寫從而提高性能。
I/O Scheduler會嘗試將一些寫操作重新按順序排好,從而減少磁頭移動時間。
讀操作可以直接在Page Cache內進行。如果消費和生產速度相當,甚至不需要通過物理磁盤交換數據。
如果進程重啓,JVM內的Cache會失效,但Page Cache仍然可用

Kafka 重度依賴底層操作系統提供的 PageCache 功能。當上層有寫操作時,操作系統只是將數據寫入 PageCache,同時標記 Page 屬性爲 Dirty。當讀操作發生時,先從 PageCache 中查找,如果發生缺頁才進行磁盤調度,最終返回需要的數據。

實際上 PageCache 是把儘可能多的空閒內存都當做了磁盤緩存來使用。同時如果有其他進程申請內存,回收 PageCache 的代價又很小,所以現代的 OS 都支持 PageCache。使用 PageCache 功能同時可以避免在 JVM 內部緩存數據,JVM 爲我們提供了強大的 GC 能力,同時也引入了一些問題不適用與 Kafka 的設計。
如果在 Heap 內管理緩存,JVM 的 GC 線程會頻繁掃描 Heap 空間,帶來不必要的開銷。如果 Heap過大,執行一次 Full GC 對系統的可用性來說將是極大的挑戰。
所有在在 JVM 內的對象都不免帶有一個 Object Overhead(千萬不可小視),內存的有效空間利用率會因此降低。
所有的 In-Process Cache 在 OS 中都有一份同樣的 PageCache。所以通過將緩存只放在 PageCache,可以至少讓可用緩存空間翻倍。
如果 Kafka 重啓,所有的 In-Process Cache 都會失效,而 OS 管理的 PageCache 依然可以繼續使用。

sendfile和transferTo實現零拷貝

OS 從硬盤把數據讀到內核區的 PageCache。
用戶進程把數據從內核區 Copy 到用戶區。
然後用戶進程再把數據寫入到 Socket,數據流入內核區的 Socket Buffer 上。
OS 再把數據從 Buffer 中 Copy 到網卡的 Buffer 上,這樣完成一次發送。
這裏寫圖片描述
整個過程共經歷兩次 Context Switch,四次 System Call。同一份數據在內核 Buffer 與用戶 Buffer 之間重複拷貝,效率低下。其中 2、3 兩步沒有必要,完全可以直接在內核區完成數據拷貝。這也正是Sendfile 所解決的問題,經過 Sendfile 優化後,整個 I/O 過程就變成了下面這個樣子。

批處理

send方法並不是立即將消息發送出去,而是通過batch.size和linger.ms控制實際發送頻率,從而實現批量發送。

數據壓縮

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

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

高效的序列化

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

Memory Mapped Files

Java NIO,它給我提供了一個mappedbytebuffer類可以用來實現內存映射

內存映射文件 ,在64位操作系統中一般可以表示20G的數據文件,它的工作原理是直接利用操作系統的Page來實現文件到物理內存的直接映射。完成映射之後你對物理內存的操作會被同步到硬盤上(操作系統在適當的時候)。
通過mmap,進程像讀寫硬盤一樣讀寫內存(當然是虛擬內存)

省去了用戶空間到內核空間 複製的開銷(調用文件的read會把數據先放到內核空間的內存中,然後再複製到用戶空間的內存中)

寫到mmap中的數據並沒有被真正的寫到硬盤,操作系統會在程序主動調用flush的時候才把數據真正的寫到硬盤。 Kafka提供了一個參數——producer.type來控制是不是主動flush,如果Kafka寫入到mmap之後就立即flush然後再返回Producer叫 同步 (sync);寫入mmap之後立即返回Producer不調用flush叫 異步 (async)。

參考:
https://www.cnblogs.com/davidwang456/articles/7884677.html

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