學習SpringBoot:我在美團Java研發崗工作的那5年
Kafka 如何做到支援百萬級 TPS ?
先用一張思維導圖直接告訴你答案:
順序讀寫磁碟
生產者寫入資料和消費者讀取資料都是順序讀寫的,先來一張圖直觀感受一下順序讀寫和隨機讀寫的速度:
從圖中可以看出傳統硬碟或者SSD的順序讀寫甚至超過了記憶體的隨機讀寫,當然與記憶體的順序讀寫對比差距還是很大。
所以Kafka選擇順序讀寫磁碟也不足為奇了。
下面以傳統機械磁碟為例詳細介紹一下什麼是順序讀寫和隨機讀寫。
碟片和盤面:一塊硬碟一般有多塊碟片,碟片分為上下兩面,其中有效面稱為盤面,一般上下都有效,也就是說:盤面數 = 碟片數 * 2。
磁頭:磁頭切換磁軌讀寫資料時是通過機械裝置實現的,一般速度較慢;而磁頭切換盤面讀寫資料是通過電子裝置實現的,一般速度較快,因此磁頭一般是先讀寫完柱面後才開始尋道的(不用切換磁軌),這樣磁碟讀寫效率更快。
磁軌:磁軌就是以中間軸為圓心的圓環,一個盤面有多個磁軌,磁軌之間有間隙,磁軌也就是磁碟儲存資料的介質。磁軌上布有一層磁介質,通過磁頭可以使磁介質的極性轉換為資料訊號,即磁碟的讀,磁碟寫剛好與之相反。
柱面:磁碟中不同盤面中半徑相同的磁軌組成的,也就是說柱面總數 = 某個盤面的磁軌數。
扇區:單個磁軌就是多個弧形扇區組成的,盤面上的每個磁軌擁有的扇區數量是相等。扇區是最小儲存單元,一般扇區大小為512bytes。
如果系統每次只讀取一個扇區,那恐怕效率太低了,所以出現了block(塊)的概念。檔案讀取的最小單位是block,根據不同作業系統一個block一般由多個扇區組成。
有了磁碟的背景知識我們就可以很容易理解順序讀寫和隨機讀寫了。
插播維基百科定義:順序讀寫:是一種按記錄的邏輯順序進行讀、寫操作的存取方法 ,即按照資訊在儲存器中的實際位置所決定的順序使用資訊。?隨機讀寫:指的是當儲存器中的訊息被讀取或寫入時,所需要的時間與這段資訊所在的位置無關。
當讀取第一個block時,要經歷尋道、旋轉延遲、傳輸三個步驟才能讀取完這個block的資料。而對於下一個block,如果它在磁碟的其他任意位置,訪問它會同樣經歷尋道、旋轉、延時、傳輸才能讀取完這個block的資料,我們把這種方式叫做隨機讀寫。但是如果這個block的起始扇區剛好在剛才訪問的block的後面,磁頭就能立刻遇到,不需等待直接傳輸,這種就叫順序讀寫。
好,我們再回到 Kafka,詳細介紹Kafka如何實現順序讀寫入資料。
Kafka 寫入資料是順序的,下面每一個Partition 都可以當做一個檔案,每次接收到新資料後Kafka會把資料插入到檔案末尾,虛框部分代表檔案尾。
這種方法有一個問題就是刪除資料不方便,所以 Kafka 一般會把所有的資料都保留下來,每個消費者(Consumer)對每個Topic都有一個 offset 用來記錄讀取進度或者叫座標。
Memory Mapped Files(MMAP)
在文章開頭我們看到硬碟的順序讀寫基本能與記憶體隨機讀寫速度媲美,但是與記憶體順序讀寫相比還是太慢了,那 Kafka 如果有追求想進一步提升效率怎麼辦?可以使用現代作業系統分頁儲存來充分利用記憶體提高I/O效率,這也是下面要介紹的 MMAP 技術。
MMAP也就是記憶體對映檔案,在64位作業系統中一般可以表示 20G 的資料檔案,它的工作原理是直接利用作業系統的 Page 來實現檔案到實體記憶體的直接對映,完成對映之後對實體記憶體的操作會被同步到硬碟上。
通過MMAP技術程序可以像讀寫硬碟一樣讀寫記憶體(邏輯記憶體),不必關心記憶體的大小,因為有虛擬記憶體兜底。這種方式可以獲取很大的I/O提升,省去了使用者空間到核心空間複製的開銷。
也有一個很明顯的缺陷,寫到MMAP中的資料並沒有被真正的寫到硬碟,作業系統會在程式主動呼叫 flush 的時候才把資料真正的寫到硬碟。
Kafka提供了一個引數:producer.type 來控制是不是主動 flush,如果Kafka寫入到MMAP之後就立即flush然後再返回Producer叫同步(sync);寫入MMAP之後立即返回Producer不呼叫flush叫非同步(async)。
Zero Copy(零拷貝)
Kafka 另外一個黑技術就是使用了零拷貝,要想深刻理解零拷貝必須得知道什麼是DMA。
什麼是DMA?
眾所周知 CPU 的速度與磁碟 IO 的速度比起來相差幾個數量級,可以用烏龜和火箭做比喻。
一般來說 IO 操作都是由 CPU 發出指令,然後等待 IO 裝置完成操作後返回,那CPU會有大量的時間都在等待IO操作。
但是CPU 的等待在很多時候並沒有太多的實際意義,我們對於 I/O 裝置的大量操作其實都只是把記憶體裡面的資料傳輸到 I/O 裝置而已。比如進行大檔案複製,如果所有資料都要經過 CPU,實在是有點兒太浪費時間了。
基於此就有了DMA技術,翻譯過來也就是直接記憶體訪問(Direct Memory Access),有了這個可以減少 CPU 的等待時間。
Kafka 零拷貝原理
如果不使用零拷貝技術,消費者(consumer)從Kafka消費資料,Kafka從磁碟讀資料然後傳送到網路上去,資料一共發生了四次傳輸的過程。其中兩次是 DMA 的傳輸,另外兩次,則是通過 CPU 控制的傳輸。
第一次傳輸:從硬碟上將資料讀到作業系統核心的緩衝區裡,這個傳輸是通過 DMA 搬運的。
第二次傳輸:從核心緩衝區裡面的資料複製到分配的記憶體裡面,這個傳輸是通過 CPU 搬運的。
第三次傳輸:從分配的記憶體裡面再寫到作業系統的 Socket 的緩衝區裡面去,這個傳輸是由 CPU 搬運的。
第四次傳輸:從 Socket 的緩衝區裡面寫到網絡卡的緩衝區裡面去,這個傳輸是通過 DMA 搬運的。
實際上在kafka中只進行了兩次資料傳輸,如下圖:
第一次傳輸:通過 DMA從硬碟直接讀到作業系統核心的讀緩衝區裡面。
第二次傳輸:根據 Socket 的描述符資訊直接從讀緩衝區裡面寫入到網絡卡的緩衝區裡面。
我們可以看到同一份資料的傳輸次數從四次變成了兩次,並且沒有通過 CPU 來進行資料搬運,所有的資料都是通過 DMA 來進行傳輸的。沒有在記憶體層面去複製(Copy)資料,這個方法稱之為零拷貝(Zero-Copy)。
無論傳輸資料量的大小,傳輸同樣的資料使用了零拷貝能夠縮短 65% 的時間,大幅度提升了機器傳輸資料的吞吐量,這也是Kafka能夠支援百萬TPS的一個重要原因。
Batch Data(資料批量處理)
當消費者(consumer)需要消費資料時,首先想到的是消費者需要一條,kafka傳送一條,消費者再要一條kafka再發送一條。但實際上 Kafka 不是這樣做的,Kafka 耍小聰明瞭。
Kafka 把所有的訊息都存放在一個一個的檔案中,當消費者需要資料的時候 Kafka 直接把檔案傳送給消費者。比如說100萬條訊息放在一個檔案中可能是10M的資料量,如果消費者和Kafka之間網路良好,10MB大概1秒就能傳送完,既100萬TPS,Kafka每秒處理了10萬條訊息。
看到這裡你可以有疑問了,消費者只需要一條訊息啊,kafka把整個檔案都發送過來了,檔案裡面剩餘的訊息怎麼辦?不要忘了消費者可以通過offset記錄消費進度。
傳送檔案還有一個好處就是可以對檔案進行批量壓縮,減少網路IO損耗。
最後
再免費分享一波我的JAVA架構專題面試真題+解析+JAVA學習書籍:戳這裡免費領取