提到武俠小說,自然就會想到古龍和金庸。在古龍的武俠世界裡,從不缺乏蓋世豪氣和磅礴場面。讓我印象最深的就是浪子,通常浪子武功都很厲害,如何厲害,那就是快、更快,還沒看到兵刃離鞘,敵手己斃。
Kafka在消息中間件領域,以快聞名(當然不僅僅是),正所謂,天下武功,唯快不破。在咱們計算機領域,也是一樣。希望通過此文,和大家一起來看看Kafka兵刃是怎麼離鞘的。
理論上,如果消費者拉取消息的速度,和發佈者發佈消息的速度一致,消息將不會落到硬盤上,這樣是最高效的情況。但大部分情況下,有很高的概率,消息是的發佈和消費是很難平衡的,如果發佈者和消費者分越多,出現這種讀寫文件的情況就會越多。
文件
也就是我們打開的文本,這裡可以具體聯想到Kafka所寫入的消息文件。存儲在硬盤上,通常一個文件存儲在硬盤上時,都是無序的,如下圖所示:
通常從硬盤讀取文件時,會放到Buffer Cache中,會以1KB為單位,是塊設備的緩衝區,如上圖的Disk。那為什麼下面的文件為以4KB為單位呢,因為還有一個cache叫Page Cache,每個Page Cache通常是4KB或6KB,這裡以4KB為例,這個純粹是從軟件邏輯層面給取的一個名字。下圖展示出Page Cache和Buffer Cache這間的關係:
可以看出Page Cache映射出了文件的邏輯狀態。再來看一個真實的情況:
通過free -m命令可以查看緩存的使用量,如上圖所示總物理內存是2G(total):
- buffers: Buffer Cache的內存,塊設備的讀寫緩衝區,更靠近存儲設備,或直接就是disk的緩衝區。上圖顯示系統分使用了155M
- cached: Page Cache的內存, 文件系統的cache,是memory的緩衝區。上圖顯示系統已使用了 686M
- -/+ buffers/cache,這一行顯示的是 1887 - (155 + 686) = 1045,表示除了buffer cache和page cache其它已經使用的量,如應用程序使用的內存等。113 + (155 + 686) = 954,表示可用的內存還有954M,也就是buffer cache和page cache並不是一直被佔用的,有可能被回收給其它更需要內存的情況使用。
可以看出物理內存是很寶貴的,使用時需要精打細算。不管摩爾定律怎麼說,我們都需要用高效的方法從眾多的Page Cache中查詢自己所需的內容是否已經在Page Cache裡了,如何快速查詢?這裡用到了radix tree:
可以很清楚的看到查找路徑。如果不在Page Cache裡,就需要走一遍較為費時的硬盤文件讀取了。為了提高命中率,感興趣的朋友可以google一下Page Cahce的預讀和替換機制。
讀(read) & 寫(write)
普通讀寫流程如上圖所示,步驟分解:
- 應用程序會分配(alloc)自己的應用內存,用來存儲自己需要文件信息,併發起讀請求
- VFS也就是虛擬文件系統會調用系統讀操,找到對應的文件並放到內核內存中
- DMA copy,直接內存訪問的好處是釋放了CPU,可以由直接操作內核內存,將文件加載到Page Cache中
- CPU copy,注意到這裡需要從左邊的內核空間(Kernel Space),通過CPU,拷貝到用戶空間(User Space),大家也喜歡叫這兩個空間分別為內核態和用戶態,不管怎麼叫,重點是當跨越狀態的操作發生時,效率會受到較到的影響,同時讓同一份內容,在內存中有多處拷貝,這是一種典型的浪費。
- 上述應用程序需要將讀取的文件,通過網絡發送出去,傳遞給服務器進程。這裡發出寫請求。
- 應用內存又一次以CPU copy的形式將同一份內容拷貝到套接字緩存(Socket Buffer)。到這裡實際上已經出現了三份內存冗餘。
- 從套接字緩存到網卡緩存(NIC buffer),使用的是DMA copy,相對幫了CPU的忙。最後發送給了服務器進程。
可以看出,步驟4和步驟6是需要得點優化的地方,因為不僅有從用戶空間到內核空間的切換,還有CPU時間的佔用,那Kafka是如何優化的呢?
讀(read) VS 內存映射(mmap)
<code>file_buf = mmap(file, len);/<code>
可以看出使用了mmap帶來的改變主要是4和6:
- 步驟4:應用緩存和內核緩存共享,這樣就減少了一次cup copy,同時相對降低了從內核空間到用戶空間的切換成本
- 步驟6:相比較之前,仍然用的是CPU copy,但不同的是都將發生在內核空間,減少了空間的切換
總體來說減少了一次數據內存的冗餘,和近兩次空間切換。至於關心mmap是如何完成這個魔術的同學,也可以進一步google,也將會是一個有趣的過程。
繼續優化。
寫(write) VS 文件發送(sendfile)
- 步驟5:sendfile系統調用,利用DMA引擎,將文件內容拷貝到內核緩衝區去
- 步驟6:不同於之前的CPU copy,這裡僅將帶有文件位置和長度信息的緩衝區描述符添加到 socket緩衝區中去,而不是將數據從操作系統內核緩衝區拷貝到socket緩衝區中
- 步驟7:DMA引擎會將數據直接從內核緩衝區拷貝到協議引擎(本例為網卡)中去,這樣又減少了最後一次數據拷貝
總體來說,直接利用DMA引擎的優勢,將內容直接拷貝到內核緩衝區,應用程序緩衝區沒有冗餘。同時將有限的信息傳給socket緩衝區,最後直接從內核緩衝區拷貝到網卡緩衝區。
全景圖:內存映射(mmap) & 文件發送(sendfile)
可以看出,結合了mmap和sendfile的讀寫操作,文件緩存只有一份,存在於內核緩衝區中。從而達到了零拷貝的要求。
Kafka在讀寫方面做了很多設計和優化,這篇是從Linux操作系統的角度解釋了Kafka的用心之處。希望對大家瞭解Kafka兵刃是如何離鞘的高深武學造詣,有一定的幫助。也歡迎廣大武學愛好者前來共同探討學習。
全文完