「Linux」零拷貝 - 對Kafka文件系統讀寫操作的影響和優化

提到武俠小說,自然就會想到古龍和金庸。在古龍的武俠世界裡,從不缺乏蓋世豪氣和磅礴場面。讓我印象最深的就是浪子,通常浪子武功都很厲害,如何厲害,那就是快、更快,還沒看到兵刃離鞘,敵手己斃。

「Linux」零拷貝 - 對Kafka文件系統讀寫操作的影響和優化

來自網絡

Kafka在消息中間件領域,以快聞名(當然不僅僅是),正所謂,天下武功,唯快不破。在咱們計算機領域,也是一樣。希望通過此文,和大家一起來看看Kafka兵刃是怎麼離鞘的。

理論上,如果消費者拉取消息的速度,和發佈者發佈消息的速度一致,消息將不會落到硬盤上,這樣是最高效的情況。但大部分情況下,有很高的概率,消息是的發佈和消費是很難平衡的,如果發佈者和消費者分越多,出現這種讀寫文件的情況就會越多。

文件

也就是我們打開的文本,這裡可以具體聯想到Kafka所寫入的消息文件。存儲在硬盤上,通常一個文件存儲在硬盤上時,都是無序的,如下圖所示:

「Linux」零拷貝 - 對Kafka文件系統讀寫操作的影響和優化

通常從硬盤讀取文件時,會放到Buffer Cache中,會以1KB為單位,是塊設備的緩衝區,如上圖的Disk。那為什麼下面的文件為以4KB為單位呢,因為還有一個cache叫Page Cache,每個Page Cache通常是4KB或6KB,這裡以4KB為例,這個純粹是從軟件邏輯層面給取的一個名字。下圖展示出Page Cache和Buffer Cache這間的關係:

「Linux」零拷貝 - 對Kafka文件系統讀寫操作的影響和優化

可以看出Page Cache映射出了文件的邏輯狀態。再來看一個真實的情況:

「Linux」零拷貝 - 對Kafka文件系統讀寫操作的影響和優化

通過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:

「Linux」零拷貝 - 對Kafka文件系統讀寫操作的影響和優化

來自網絡

可以很清楚的看到查找路徑。如果不在Page Cache裡,就需要走一遍較為費時的硬盤文件讀取了。為了提高命中率,感興趣的朋友可以google一下Page Cahce的預讀和替換機制。

讀(read) & 寫(write)

「Linux」零拷貝 - 對Kafka文件系統讀寫操作的影響和優化

普通讀寫流程如上圖所示,步驟分解:

  1. 應用程序會分配(alloc)自己的應用內存,用來存儲自己需要文件信息,併發起讀請求
  2. VFS也就是虛擬文件系統會調用系統讀操,找到對應的文件並放到內核內存中
  3. DMA copy,直接內存訪問的好處是釋放了CPU,可以由直接操作內核內存,將文件加載到Page Cache中
  4. CPU copy,注意到這裡需要從左邊的內核空間(Kernel Space),通過CPU,拷貝到用戶空間(User Space),大家也喜歡叫這兩個空間分別為內核態和用戶態,不管怎麼叫,重點是當跨越狀態的操作發生時,效率會受到較到的影響,同時讓同一份內容,在內存中有多處拷貝,這是一種典型的浪費。
  5. 上述應用程序需要將讀取的文件,通過網絡發送出去,傳遞給服務器進程。這裡發出寫請求。
  6. 應用內存又一次以CPU copy的形式將同一份內容拷貝到套接字緩存(Socket Buffer)。到這裡實際上已經出現了三份內存冗餘。
  7. 從套接字緩存到網卡緩存(NIC buffer),使用的是DMA copy,相對幫了CPU的忙。最後發送給了服務器進程。

可以看出,步驟4和步驟6是需要得點優化的地方,因為不僅有從用戶空間到內核空間的切換,還有CPU時間的佔用,那Kafka是如何優化的呢?

讀(read) VS 內存映射(mmap)

「Linux」零拷貝 - 對Kafka文件系統讀寫操作的影響和優化

<code>file_buf = mmap(file, len);/<code>

可以看出使用了mmap帶來的改變主要是4和6:

  • 步驟4:應用緩存和內核緩存共享,這樣就減少了一次cup copy,同時相對降低了從內核空間到用戶空間的切換成本
  • 步驟6:相比較之前,仍然用的是CPU copy,但不同的是都將發生在內核空間,減少了空間的切換

總體來說減少了一次數據內存的冗餘,和近兩次空間切換。至於關心mmap是如何完成這個魔術的同學,也可以進一步google,也將會是一個有趣的過程。

繼續優化。

寫(write) VS 文件發送(sendfile)

「Linux」零拷貝 - 對Kafka文件系統讀寫操作的影響和優化

  • 步驟5:sendfile系統調用,利用DMA引擎,將文件內容拷貝到內核緩衝區去
  • 步驟6:不同於之前的CPU copy,這裡僅將帶有文件位置和長度信息的緩衝區描述符添加到 socket緩衝區中去,而不是將數據從操作系統內核緩衝區拷貝到socket緩衝區中
  • 步驟7:DMA引擎會將數據直接從內核緩衝區拷貝到協議引擎(本例為網卡)中去,這樣又減少了最後一次數據拷貝

總體來說,直接利用DMA引擎的優勢,將內容直接拷貝到內核緩衝區,應用程序緩衝區沒有冗餘。同時將有限的信息傳給socket緩衝區,最後直接從內核緩衝區拷貝到網卡緩衝區。

全景圖:內存映射(mmap) & 文件發送(sendfile)

「Linux」零拷貝 - 對Kafka文件系統讀寫操作的影響和優化

可以看出,結合了mmap和sendfile的讀寫操作,文件緩存只有一份,存在於內核緩衝區中。從而達到了零拷貝的要求。

Kafka在讀寫方面做了很多設計和優化,這篇是從Linux操作系統的角度解釋了Kafka的用心之處。希望對大家瞭解Kafka兵刃是如何離鞘的高深武學造詣,有一定的幫助。也歡迎廣大武學愛好者前來共同探討學習。

全文完

「Linux」零拷貝 - 對Kafka文件系統讀寫操作的影響和優化


分享到:


相關文章: