Kafka為什麼速度那麼快?
Kafka的訊息是儲存或快取在磁碟上的,一般認為在磁碟上讀寫資料是會降低效能的,因為定址會比較消耗時間,但是實際上,Kafka的特性之一就是高吞吐率。
即使是普通的伺服器,Kafka也可以輕鬆支援每秒百萬級的寫入請求,超過了大部分的訊息中介軟體,這種特性也使得Kafka在日誌處理等海量資料場景廣泛應用。
針對Kafka的基準測試可以參考,Apache Kafka基準測試:每秒寫入2百萬(在三臺廉價機器上)
下面從資料寫入和讀取兩方面分析,為什麼Kafka速度這麼快。
一、寫入資料
Kafka會把收到的訊息都寫入到硬碟中,它絕對不會丟失資料。為了優化寫入速度Kafka採用了兩個技術, 順序寫入和MMFile 。
1、順序寫入磁碟讀寫的快慢取決於你怎麼使用它,也就是順序讀寫或者隨機讀寫。在順序讀寫的情況下,磁碟的順序讀寫速度和記憶體持平。
因為硬碟是機械結構,每次讀寫都會定址->寫入,其中定址是一個“機械動作”,它是最耗時的。所以硬碟最討厭隨機I/O,最喜歡順序I/O。為了提高讀寫硬碟的速度,Kafka就是使用順序I/O。
而且Linux對於磁碟的讀寫優化也比較多,包括read-ahead和write-behind,磁碟快取等。如果在記憶體做這些操作的時候,一個是JAVA物件的記憶體開銷很大,另一個是隨著堆記憶體資料的增多,JAVA的GC時間會變得很長,使用磁碟操作有以下幾個好處:
-
磁碟順序讀寫速度超過記憶體隨機讀寫
-
JVM的GC效率低,記憶體佔用大。使用磁碟可以避免這一問題
-
系統冷啟動後,磁碟快取依然可用
下圖就展示了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檔案大小。
具體配置可以參看它的配置文件。
2、Memory Mapped Files即便是順序寫入硬碟,硬碟的訪問速度還是不可能追上記憶體。所以Kafka的資料並不是實時的寫入硬碟 ,它充分利用了現代作業系統分頁儲存來利用記憶體提高I/O效率。
Memory Mapped Files(後面簡稱mmap)也被翻譯成 記憶體對映檔案 ,在64位作業系統中一般可以表示20G的資料檔案,它的工作原理是直接利用作業系統的Page來實現檔案到實體記憶體的直接對映。
完成對映之後你對實體記憶體的操作會被同步到硬碟上(作業系統在適當的時候)。
通過mmap,程式像讀寫硬碟一樣讀寫記憶體(當然是虛擬機器記憶體),也不必關心記憶體的大小有虛擬記憶體為我們兜底。
使用這種方式可以獲取很大的I/O提升,省去了使用者空間到核心空間複製的開銷(呼叫檔案的read會把資料先放到核心空間的記憶體中,然後再複製到使用者空間的記憶體中。)
但也有一個很明顯的缺陷——不可靠,寫到mmap中的資料並沒有被真正的寫到硬碟,作業系統會在程式主動呼叫flush的時候才把資料真正的寫到硬碟。
Kafka提供了一個引數——producer.type來控制是不是主動flush,如果Kafka寫入到mmap之後就立即flush然後再返回Producer叫 同步 (sync);寫入mmap之後立即返回Producer不呼叫flush叫非同步 (async)。
二、讀取資料
Kafka在讀取磁碟時做了哪些優化?
2、基於sendfile實現Zero Copy傳統模式下,當需要對一個檔案進行傳輸的時候,其具體流程細節如下:
-
呼叫read函式,檔案資料被copy到核心緩衝區
-
read函式返回,檔案資料從核心緩衝區copy到使用者緩衝區
-
write函式呼叫,將檔案資料從使用者緩衝區copy到核心與socket相關的緩衝區。
-
資料從socket緩衝區copy到相關協議引擎。
以上細節是傳統read/write方式進行網路檔案傳輸的方式,我們可以看到,在這個過程當中,檔案資料實際上是經過了四次copy操作:
硬碟—>核心buf—>使用者buf—>socket相關緩衝區—>協議引擎而sendfile系統呼叫則提供了一種減少以上多次copy,提升檔案傳輸效能的方法。
在核心版本2.1中,引入了sendfile系統呼叫,以簡化網路上和兩個本地檔案之間的資料傳輸。sendfile的引入不僅減少了資料複製,還減少了上下文切換。
sendfile(socket, file, len);執行流程如下:
-
sendfile系統呼叫,檔案資料被copy至核心緩衝區
-
再從核心緩衝區copy至核心中socket相關的緩衝區
-
最後再socket相關的緩衝區copy到協議引擎
相較傳統read/write方式,2.1版本核心引進的sendfile已經減少了核心緩衝區到user緩衝區,再由user緩衝區到socket相關緩衝區的檔案copy,而在核心版本2.4之後,檔案描述符結果被改變,sendfile實現了更簡單的方式,再次減少了一次copy操作。
在Apache、Nginx、lighttpd等web伺服器當中,都有一項sendfile相關的配置,使用sendfile可以大幅提升檔案傳輸效能。
Kafka把所有的訊息都存放在一個一個的檔案中,當消費者需要資料的時候Kafka直接把檔案傳送給消費者,配合mmap作為檔案讀寫方式,直接把它傳給sendfile。
2、批量壓縮在很多情況下,系統的瓶頸不是CPU或磁碟,而是網路IO,對於需要在廣域網上的資料中心之間傳送訊息的資料流水線尤其如此。進行資料壓縮會消耗少量的CPU資源,不過對於kafka而言,網路IO更應該需要考慮。
-
如果每個訊息都壓縮,但是壓縮率相對很低,所以Kafka使用了批量壓縮,即將多個訊息一起壓縮而不是單個訊息壓縮
-
Kafka允許使用遞迴的訊息集合,批量的訊息可以通過壓縮的形式傳輸並且在日誌中也可以保持壓縮格式,直到被消費者解壓縮
-
Kafka支援多種壓縮協議,包括Gzip和Snappy壓縮協議
三、總結
Kafka速度的祕訣在於,它把所有的訊息都變成一個批量的檔案,並且進行合理的批量壓縮,減少網路IO損耗,通過mmap提高I/O速度,寫入資料的時候由於單個Partion是末尾新增所以速度最優;讀取資料的時候配合sendfile直接暴力輸出。