大家都知道Kafka是將資料儲存於磁碟的,而磁碟讀寫效能往往很差,但Kafka官方測試其資料讀寫速率能達到600M/s,那麼為什麼Kafka效能會這麼高呢?
首先producer往broker傳送訊息時,採用batch的方式即批量而非一條一條的傳送,這種方式可以有效降低網路IO的請求次數,提升效能。此外這些批次訊息會"暫存"在緩衝池中,避免頻繁的GC問題。批量傳送的訊息可以進行壓縮並且傳輸的時候可以進行高效的序列化,從而減少資料大小。
Kafka除了在producer傳送訊息方面做了很多優化,還有很多其他的優化,比如Kafka利用了sequence IO、PageCache、SendFile這3種處理方案:
sequence IO
首先來了解一下磁碟的特性:快速順序讀寫、慢速隨機讀寫。因為磁碟是典型的IO塊裝置,每次讀寫都會經歷定址,其中定址中尋道是比較耗時的。隨機讀寫會導致定址時間延長,從而影響磁碟的讀寫速度。
大家有沒有想過MapReduce進行shuffle的時候,為什麼map端和reduce端要進行排序,不排序不也不影響正常業務的處理,排序反而因為消耗資源增加了處理時間?
以map端為例,執行過程中會產生很多小檔案,這些小檔案要經歷歸併排序等一系列處理後才會被reduce端進行處理。提前對未合併的檔案進行排序正是利用了磁碟快速順序讀寫的特性來提高歸併排序的速度。
而Kafka在將資料持久化到磁碟時,採用只追加的順序寫,有效降低了定址時間,提高效率。下圖展示了Kafka寫入資料到partition的方式:
可以看到Kafka會將資料插入到檔案末尾,並且Kafka不會"直接"刪除資料,而是把所有資料儲存到磁碟,每個consumer會指定一個offset來記錄自己訂閱的topic的partition中消費的位置。當然我們可以設定策略來清理資料,比如通過引數log.retention.hours指定過期時間,當達到過期時間時,Kafka會清理資料。
PageCache
PageCache是系統級別的快取,它把儘可能多的空閒記憶體當作磁碟快取使用來進一步提高IO效率,同時當其他程式申請記憶體,回收PageCache的代價也很小。
當上層有寫操作時,作業系統只是將資料寫入PageCache,同時標記Page屬性為Dirty。當讀操作發生時,先從PageCache中查詢,如果發生缺頁才進行磁碟排程,最終返回需要的資料。
PageCache同時可以避免在JVM內部快取資料,避免不必要的GC、以及記憶體空間佔用。對於In-Process Cache,如果Kafka重啟,它會失效,而作業系統管理的PageCache依然可以繼續使用。
對應到Kafka生產和消費訊息中:
producer把訊息發到broker後,資料並不是直接落入磁碟的,而是先進入PageCache。PageCache中的資料會被核心中的處理執行緒採用同步或非同步的方式寫回到磁碟。
Consumer消費訊息時,會先從PageCache獲取訊息,獲取不到才回去磁碟讀取,並且會預讀出一些相鄰的塊放入PageCache,以方便下一次讀取
如果Kafka producer的生產速率與consumer的消費速率相差不大,那麼幾乎只靠對broker PageCache的讀寫就能完成整個生產和消費過程,磁碟訪問非常少。
SendFile
傳統的網路I/O過程:
1. 作業系統從磁碟把資料讀到核心區
2. 使用者程式把資料從核心區copy到使用者區
3. 然後使用者程式再把資料寫入到socket,資料流入核心區的Socket Buffer上
4. 最後把資料從socket Buffer中傳送到到網路卡,這樣完成一次傳送
可以發現,同一份資料在核心Buffer與使用者Buffer之間拷貝兩次:
但是通過SendFile(又稱zero copy)優化後,直接把資料從核心區copy到socket,然後傳送到網路卡,避免了在核心Buffer與使用者Buffer來回拷貝的弊端:
不僅是Kafka,Java的NIO提供的FileChannle,它的transferTo、transferFrom方法也利用了這種在核心區完成資料傳輸的功能。
關注微信公眾號:大資料學習與分享,獲取更對技術乾貨