Kafka為什麼吞吐量大、速度快?

語言: CN / TW / HK

Kafka是大數據領域無處不在的消息中間件,目前廣泛使用在企業內部的實時數據管道,並幫助企業構建自己的流計算應用程序。

Kafka雖然是基於磁盤做的數據存儲,但卻具有高性能、高吞吐、低延時的特點,其吞吐量動輒幾萬、幾十上百萬。

但是很多使用過Kafka的人,經常會被問到這樣一個問題,Kafka為什麼速度快,吞吐量大;大部分被問的人都是一下子就懵了,或者是隻知道一些簡單的點,本文就簡單的介紹一下Kafka為什麼吞吐量大,速度快。

一、順序讀寫

眾所周知Kafka是將消息記錄持久化到本地磁盤中的,一般人會認為磁盤讀寫性能差,可能會對Kafka性能如何保證提出質疑。實際上不管是內存還是磁盤,快或慢關鍵在於尋址的方式,磁盤分為順序讀寫與隨機讀寫,內存也一樣分為順序讀寫與隨機讀寫。基於磁盤的隨機讀寫確實很慢,但磁盤的順序讀寫性能卻很高,一般而言要高出磁盤隨機讀寫三個數量級,一些情況下磁盤順序讀寫性能甚至要高於內存隨機讀寫。

這裏給出著名學術期刊 ACM Queue 上的性能對比圖: https://queue.acm.org/detail.cfm?id=1563874

磁盤的順序讀寫是磁盤使用模式中最有規律的,並且操作系統也對這種模式做了大量優化,Kafka就是使用了磁盤順序讀寫來提升的性能。Kafka的message是不斷追加到本地磁盤文件末尾的,而不是隨機的寫入,這使得Kafka寫入吞吐量得到了顯著提升 。

上圖就展示了Kafka是如何寫入數據的, 每一個Partition其實都是一個文件 ,收到消息後Kafka會把數據插入到文件末尾(虛框部分)。

這種方法有一個缺陷—— 沒有辦法刪除數據 ,所以Kafka是不會刪除數據的,它會把所有的數據都保留下來,每個消費者(Consumer)對每個Topic都有一個offset用來表示 讀取到了第幾條數據 。

兩個消費者,Consumer1有兩個offset分別對應Partition0、Partition1(假設每一個Topic一個Partition);Consumer2有一個offset對應Partition2。這個offset是由客户端SDK負責保存的,Kafka的Broker完全無視這個東西的存在;一般情況下SDK會把它保存到zookeeper裏面。(所以需要給Consumer提供zookeeper的地址)。

如果不刪除硬盤肯定會被撐滿,所以Kakfa提供了兩種策略來刪除數據。一是基於時間,二是基於partition文件大小。具體配置可以參看它的配置文檔。

二、Page Cache

為了優化讀寫性能,Kafka利用了操作系統本身的Page Cache,就是利用操作系統自身的內存而不是JVM空間內存。這樣做的好處有:

1避免Object消耗:如果是使用 Java 堆,Java對象的內存消耗比較大,通常是所存儲數據的兩倍甚至更多。

2避免GC問題:隨着JVM中數據不斷增多,垃圾回收將會變得複雜與緩慢,使用系統緩存就不會存在GC問題

相比於使用JVM或in-memory cache等數據結構,利用操作系統的Page Cache更加簡單可靠。首先,操作系統層面的緩存利用率會更高,因為存儲的都是緊湊的字節結構而不是獨立的對象。其次,操作系統本身也對於Page Cache做了大量優化,提供了 write-behind、read-ahead以及flush等多種機制。再者,即使服務進程重啟,系統緩存依然不會消失,避免了in-process cache重建緩存的過程。

通過操作系統的Page Cache,Kafka的讀寫操作基本上是基於內存的,讀寫速度得到了極大的提升。

三、零拷貝

linux操作系統 “零拷貝” 機制使用了sendfile方法, 允許操作系統將數據從Page Cache 直接發送到網絡,只需要最後一步的copy操作將數據複製到 NIC 緩衝區, 這樣避免重新複製數據 。示意圖如下:

 

通過這種 “零拷貝” 的機制,Page Cache 結合 sendfile 方法,Kafka消費端的性能也大幅提升。這也是為什麼有時候消費端在不斷消費數據時,我們並沒有看到磁盤io比較高,此刻正是操作系統緩存在提供數據。

當Kafka客户端從服務器讀取數據時,如果不使用零拷貝技術,那麼大致需要經歷這樣的一個過程:

1.操作系統將數據從磁盤上讀入到內核空間的讀緩衝區中。

2.應用程序(也就是Kafka)從內核空間的讀緩衝區將數據拷貝到用户空間的緩衝區中。

3.應用程序將數據從用户空間的緩衝區再寫回到內核空間的socket緩衝區中。

4.操作系統將socket緩衝區中的數據拷貝到NIC緩衝區中,然後通過網絡發送給客户端。

no zero cop

從圖中可以看到,數據在內核空間和用户空間之間穿梭了兩次,那麼能否避免這個多餘的過程呢?當然可以,Kafka使用了零拷貝技術,也就是直接將數據從內核空間的讀緩衝區直接拷貝到內核空間的socket緩衝區,然後再寫入到NIC緩衝區,避免了在內核空間和用户空間之間穿梭。

zero copy

可見,這裏的零拷貝並非指一次拷貝都沒有,而是避免了在內核空間和用户空間之間的拷貝。如果真是一次拷貝都沒有,那麼數據發給客户端就沒了不是?不過,光是省下了這一步就可以帶來性能上的極大提升。

四、分區分段+索引

Kafka的message是按topic分類存儲的,topic中的數據又是按照一個一個的partition即分區存儲到不同broker節點。每個partition對應了操作系統上的一個文件夾,partition實際上又是按照segment分段存儲的。這也非常符合分佈式系統分區分桶的設計思想。

通過這種分區分段的設計,Kafka的message消息實際上是分佈式存儲在一個一個小的segment中的,每次文件操作也是直接操作的segment。為了進一步的查詢優化,Kafka又默認為分段後的數據文件建立了索引文件,就是文件系統上的.index文件。這種分區分段+索引的設計,不僅提升了數據讀取的效率,同時也提高了數據操作的並行度。

五、批量讀寫

Kafka數據讀寫也是批量的而不是單條的。

除了利用底層的技術外,Kafka還在應用程序層面提供了一些手段來提升性能。最明顯的就是使用批次。在向Kafka寫入數據時,可以啟用批次寫入,這樣可以避免在網絡上頻繁傳輸單個消息帶來的延遲和帶寬開銷。假設網絡帶寬為10MB/S,一次性傳輸10MB的消息比傳輸1KB的消息10000萬次顯然要快得多。

六、批量壓縮

在很多情況下,系統的瓶頸不是CPU或磁盤,而是網絡IO,對於需要在廣域網上的數據中心之間發送消息的數據流水線尤其如此。進行數據壓縮會消耗少量的CPU資源,不過對於kafka而言,網絡IO更應該需要考慮。

1>如果每個消息都壓縮,但是壓縮率相對很低,所以Kafka使用了批量壓縮,即將多個消息一起壓縮而不是單個消息壓縮

2>Kafka允許使用遞歸的消息集合,批量的消息可以通過壓縮的形式傳輸並且在日誌中也可以保持壓縮格式,直到被消費者解壓縮

3>Kafka支持多種壓縮協議,包括Gzip和Snappy壓縮協議

Kafka速度的祕訣在於,它把所有的消息都變成一個批量的文件,並且進行合理的批量壓縮,減少網絡IO損耗,通過mmap提高I/O速度,寫入數據的時候由於單個Partion是末尾添加所以速度最優;讀取數據的時候配合sendfile直接暴力輸出。

以上內容希望幫助到大家,很多PHPer在進階的時候總會遇到一些問題和瓶頸,業務代碼寫多了沒有方向感,更多PHP大廠PDF面試文檔,PHP進階架構視頻資料,PHP精彩好文免費獲取可以關注公眾號:PHP開源社區,或者訪問:

2021金三銀四大廠面試真題集錦,必看!

四年精華PHP技術文章整理合集——PHP框架篇

四年精華PHP技術文合集——微服務架構篇

四年精華PHP技術文合集——分佈式架構篇

四年精華PHP技術文合集——高併發場景篇

四年精華PHP技術文章整理合集——數據庫篇