sendfile“零拷貝”、mmap內存映射、DMA

原創連接:https://blog.csdn.net/z69183787/article/details/104760890?utm_source=app,groupmessage

KAFKA推送消息用到了sendfile,落盤技術用到了mmap,DMA貫穿其中。

先說說零拷貝

零拷貝並不是不需要拷貝,而是減少不必要的拷貝次數。通常是說在IO讀寫過程中。

實際上,零拷貝是有廣義和狹義之分,目前我們通常聽到的零拷貝,包括上面這個定義減少不必要的拷貝次數都是廣義上的零拷貝。其實瞭解到這點就足夠了。

我們知道,減少不必要的拷貝次數,就是爲了提高效率。那零拷貝之前,是怎樣的呢?

聊聊傳統IO流程

比如:讀取文件,再用socket發送出去

傳統方式實現:

先讀取、再發送,實際經過1~4四次copy。

1、第一次:將磁盤文件,讀取到操作系統內核緩衝區;

2、第二次:將內核緩衝區的數據,copy到application應用程序的buffer;

3、第三步:將application應用程序buffer中的數據,copy到socket網絡發送緩衝區(屬於操作系統內核的緩衝區);

4、第四次:將socket buffer的數據,copy到網卡,由網卡進行網絡傳輸。

傳統方式,讀取磁盤文件並進行網絡發送,經過的四次數據copy是非常繁瑣的。實際IO讀寫,需要進行IO中斷,需要CPU響應中斷(帶來上下文切換),儘管後來引入DMA來接管CPU的中斷請求,但四次copy是存在“不必要的拷貝”的。(什麼是DMA?

其實DMA技術很容易理解,本質上,DMA技術就是我們在主板上放⼀塊獨立的芯片。在進行內存和I/O設備的數據傳輸的時候,我們不再通過CPU來控制數據傳輸,而直接通過 DMA控制器(DMA?Controller,簡稱DMAC)。這塊芯片,我們可以認爲它其實就是一個協處理器(Co-Processor))

重新思考傳統IO方式,會注意到實際上並不需要第二個和第三個數據副本。應用程序除了緩存數據並將其傳輸回套接字緩衝區之外什麼都不做。相反,數據可以直接從讀緩衝區傳輸到套接字緩衝區。

顯然,第二次和第三次數據copy 其實在這種場景下沒有什麼幫助反而帶來開銷,這也正是零拷貝出現的意義。

這種場景:是指讀取磁盤文件後,不需要做其他處理,直接用網絡發送出去。試想,如果讀取磁盤的數據需要用程序進一步處理的話,必須要經過第二次和第三次數據copy,讓應用程序在內存緩衝區處理。

爲什麼Kafka這麼快

kafka作爲MQ也好,作爲存儲層也好,無非是兩個重要功能,一是Producer生產的數據存到broker,二是 Consumer從broker讀取數據;我們把它簡化成如下兩個過程:

1、網絡數據持久化到磁盤 (Producer 到 Broker)

2、磁盤文件通過網絡發送(Broker 到 Consumer)

下面,先給出“kafka用了磁盤,還速度快”的結論

1、順序讀寫

磁盤順序讀或寫的速度400M/s,能夠發揮磁盤最大的速度。

隨機讀寫,磁盤速度慢的時候十幾到幾百K/s。這就看出了差距。

kafka將來自Producer的數據,順序追加在partition,partition就是一個文件,以此實現順序寫入。

Consumer從broker讀取數據時,因爲自帶了偏移量,接着上次讀取的位置繼續讀,以此實現順序讀。

順序讀寫,是kafka利用磁盤特性的一個重要體現。

2、零拷貝 sendfile(in,out)

數據直接在內核完成輸入和輸出,不需要拷貝到用戶空間再寫出去。

kafka數據寫入磁盤前,數據先寫到進程的內存空間。

3、mmap文件映射

虛擬映射只支持文件;

在進程 的非堆內存開闢一塊內存空間,和OS內核空間的一塊內存進行映射,

kafka數據寫入、是寫入這塊內存空間,但實際這塊內存和OS內核內存有映射,也就是相當於寫在內核內存空間了,且這塊內核空間、內核直接能夠訪問到,直接落入磁盤。

這裏,我們需要清楚的是:內核緩衝區的數據,flush就能完成落盤。

我們來重點探究 kafka兩個重要過程、以及是如何利用兩個零拷貝技術sendfile和mmap的。

網絡數據持久化到磁盤 (Producer 到 Broker)

傳統方式實現:

先接收生產者發來的消息,再落入磁盤。

數據落盤通常都是非實時的,kafka生產者數據持久化也是如此。Kafka的數據並不是實時的寫入硬盤,它充分利用了現代操作系統分頁存儲來利用內存提高I/O效率。

對於kafka來說,Producer生產的數據存到broker,這個過程讀取到socket buffer的網絡數據,其實可以直接在OS內核緩衝區,完成落盤。並沒有必要將socket buffer的網絡數據,讀取到應用進程緩衝區;在這裏應用進程緩衝區其實就是broker,broker收到生產者的數據,就是爲了持久化。

在此特殊場景下:接收來自socket buffer的網絡數據,應用進程不需要中間處理、直接進行持久化時。——可以使用mmap內存文件映射。

Memory Mapped Files

簡稱mmap,簡單描述其作用就是:將磁盤文件映射到內存, 用戶通過修改內存就能修改磁盤文件。

它的工作原理是直接利用操作系統的Page來實現文件到物理內存的直接映射。完成映射之後你對物理內存的操作會被同步到硬盤上(操作系統在適當的時候)。

通過mmap,進程像讀寫硬盤一樣讀寫內存(當然是虛擬機內存),也不必關心內存的大小有虛擬內存爲我們兜底。

使用這種方式可以獲取很大的I/O提升,省去了用戶空間到內核空間複製的開銷。

mmap也有一個很明顯的缺陷——不可靠,寫到mmap中的數據並沒有被真正的寫到硬盤,操作系統會在程序主動調用flush的時候才把數據真正的寫到硬盤。Kafka提供了一個參數——producer.type來控制是不是主動flush;如果Kafka寫入到mmap之後就立即flush然後再返回Producer叫同步(sync);寫入mmap之後立即返回Producer不調用flush叫異步(async)。

Java NIO對文件映射的支持

Java NIO,提供了一個 MappedByteBuffer 類可以用來實現內存映射。

MappedByteBuffer只能通過調用FileChannel的map()取得,再沒有其他方式。

FileChannel.map()是抽象方法,具體實現是在 FileChannelImpl.c 可自行查看JDK源碼,其map0()方法就是調用了Linux內核的mmap的API。

使用 MappedByteBuffer類要注意的是:mmap的文件映射,在full gc時纔會進行釋放。當close時,需要手動清除內存映射文件,可以反射調用sun.misc.Cleaner方法。

磁盤文件通過網絡發送(Broker 到 Consumer)

傳統方式實現:

先讀取磁盤、再用socket發送,實際也是進過四次copy。

而 Linux 2.4+ 內核通過 sendfile 系統調用,提供了零拷貝。磁盤數據通過 DMA 拷貝到內核態 Buffer 後,直接通過 DMA 拷貝到 NIC Buffer(socket buffer),無需 CPU 拷貝。這也是零拷貝這一說法的來源。除了減少數據拷貝外,因爲整個讀文件 - 網絡發送由一個 sendfile 調用完成,整個過程只有兩次上下文切換,因此大大提高了性能。零拷貝過程如下圖所示。

相比於文章開始,對傳統IO 4步拷貝的分析,sendfile將第二次、第三次拷貝,一步完成。

其實這項零拷貝技術,直接從內核空間(DMA的)到內核空間(Socket的)、然後發送網卡。

應用的場景非常多,如Tomcat、Nginx、Apache等web服務器返回靜態資源等,將數據用網絡發送出去,都運用了sendfile。

簡單理解 sendfile(in,out)就是,磁盤文件讀取到操作系統內核緩衝區後、直接扔給網卡,發送網絡數據。

Java NIO對sendfile的支持就是FileChannel.transferTo()/transferFrom()。

fileChannel.transferTo( position, count, socketChannel);

把磁盤文件讀取OS內核緩衝區後的fileChannel,直接轉給socketChannel發送;底層就是sendfile。消費者從broker讀取數據,就是由此實現。

具體來看,Kafka 的數據傳輸通過 TransportLayer 來完成,其子類 PlaintextTransportLayer 通過Java NIO 的 FileChannel 的 transferTo 和 transferFrom 方法實現零拷貝。

注: transferTo 和 transferFrom 並不保證一定能使用零拷貝。實際上是否能使用零拷貝與操作系統相關,如果操作系統提供 sendfile 這樣的零拷貝系統調用,則這兩個方法會通過這樣的系統調用充分利用零拷貝的優勢,否則並不能通過這兩個方法本身實現零拷貝。

Kafka總結

總的來說Kafka快的原因:

1、partition順序讀寫,充分利用磁盤特性,這是基礎;

2、Producer生產的數據持久化到broker,採用mmap文件映射,實現順序的快速寫入;

3、Customer從broker讀取數據,採用sendfile,將磁盤文件讀到OS內核緩衝區後,直接轉到socket buffer進行網絡發送。

mmap 和 sendfile總結

1、都是Linux內核提供、實現零拷貝的API;

2、sendfile 是將讀到內核空間的數據,轉到socket buffer,進行網絡發送;

3、mmap將磁盤文件映射到內存,支持讀和寫,對內存的操作會反映在磁盤文件上。

RocketMQ 在消費消息時,使用了 mmap。kafka 使用了 sendFile。

 

關於DMA

爲什麼那麼快?一起來看Kafka的實現原理

1、它究竟是怎麼利用DMA的?

Kafka是一個用來處理實時數據的管道,我們常常用它來做一個消息隊列,或者用來收集和落地海量的日誌。作爲一個處理實時數據和日誌的管道,瓶頸自然也在I/O層面。

2、Kafka裏面兩種常用的海量數據傳輸的情況是什麼?

Kafka裏面會有兩種常用的海量數據傳輸的情況。一種是從網絡絡中接收上游的數據,然後需要落地到本地的磁盤上,確保數據不丟失。

另一種情況呢,則是從本地磁盤上讀取出來,通過網絡發送出去。

我們來看一看後一種情況,從磁盤讀數據發送到網絡上去。如果我們自己寫一個簡單的程序,最直觀的辦法,自然是用個一件讀操作,從磁盤上把數據讀到內存裏面來,

然後再用個Socket,把這些數據發送到網絡上去。

 

3、我們只是要“搬運”一份數據,結果卻整整搬運了四次

在這個過程中,數據一共發生了四次傳輸的過程。其中兩次是DMA的傳輸,另外兩次,則是通過CPU控制的傳輸。下面我們來具體看看這個過程。

第一次傳輸,是從硬盤上,讀到操作系統內核的緩衝區裏。這個傳輸是通過DMA搬運的。

第二次傳輸,需要從內核緩衝區裏面的數據,複製到我們應用分配的內存裏面。這個傳輸是通過CPU搬運的。

第三次傳輸,要從我們應用的內存裏面,再寫到操作系統的Socket的緩衝區裏面去。這個傳輸,還是由CPU搬運的。

最後一次傳輸,需要再從Socket的緩衝區裏面,寫到網卡的緩衝區裏面去。這個傳輸又是通過DMA搬運的。

 

這個時候,你可以回過頭看看這個過程。我們只是要“搬運”⼀份數據,結果卻整整搬運了四次。而且這裏面,從內核的讀緩衝區傳輸到應用的內存裏,

再從應用的內存裏傳輸到Socket的緩衝區裏,其實都是把同一份數據在內存裏面搬運來搬運去,特別沒有效率。

4、我們就需要儘可能地減少數據搬運的需求

像Kafka這樣的應用場景,其實一部分最終利用到的硬件資源,其實又都是在幹這個搬運數據的事兒。所以,我們就需要儘可能地減少數據搬運的需求。

事實上,Kafka做的事情就是,把這個數據搬運的次數,從上面的四次,變成了兩次,並且只有DMA來進行數據搬運,而不需要CPU。

 

Kafka的代碼調用了Java NIO庫,具體是FileChannel裏面的transferTo方法。我們的數據並沒有讀到中間的應用內存裏面,而是直接通過Channel,寫入到對應的網絡設備裏。

並且,對於Socket的操作,也不是寫入到Socket的Buffer裏面,而是直接根據描述符(Descriptor)寫到到網卡的緩衝區裏面。於是,在這個過程之中,我們只進行了兩次數據傳輸。

5、同一份數據傳輸的次數從四次變成了兩次

 

第一次,是通過DMA,從硬盤直接讀到操作系統內核的讀緩衝區裏面。第二次,則是根據Socket的描述符信息,直接從讀緩衝區裏面,寫入到網卡的緩衝區裏面。

這樣,我們同一份數據傳輸的次數從四次變成了兩次,並且沒有通過CPU來進行數據搬運,所有的數據都是通過DMA來進行傳輸的。

6、什麼是零拷貝?

在這個方法裏面,我們沒有在內存層面去“複製(Copy)”數據,所以這個方法,也被稱之爲零拷貝(Zero-Copy)。IBM Developer Works裏面有一篇文章,專們寫過程序來測試過在同樣的硬件下,使用零拷貝能夠帶來的性能提升。我在這裏放上這篇文章鏈接。在這篇文章最後,你可以看到,無論傳輸數據量的大小,傳輸同樣的數據,使用了零拷貝能夠縮短65%的時間,大幅度提升了機器傳輸數據的吞吐量。想要深入瞭解零拷貝,建議你可以仔細讀讀讀這篇文章。

DMA總結

講到這裏,相信你對DMA的原理、作用和效果都有所理解了。那麼,我們⼀起來回顧總結一下。、

如果我們始終讓CPU來進行各種數據傳輸工作,會特別浪費。一方面,我們的數據傳輸工作用不到多少CPU核新的“計算”功能。另一方面,CPU的運轉速度也比I/O操作要快很多。

所以,我們希望能夠給CPU“減負”。

於是,工程師們就在主板上放上了DMAC這樣一個協處理器芯片。通過這個芯片,CPU只需要告訴DMAC,我們要傳輸什麼數據,從哪裏來,到哪裏去,就可以放心離開了。

後續的實際數據傳輸工作,都會有DMAC來完成。隨着現代計算機各種外設硬件越來越多,光一個通用的DMAC芯片不夠了,我們在各個外設上都加上了DMAC芯片,

使得CPU很少再需要關注數據傳輸的工作了。

在我們實際的系統開發過程中,利用好DMA的數據傳輸機制,也可以大幅提升I/O的吞吐率。最典型的例子就是Kafka。

傳統地從硬盤讀取數據,然後再通過網卡上向外發送,我們需要進行四次數據傳輸,其中有兩次是發生在內存裏的緩衝區和對應的硬件設備之間,我們沒法節省掉。

但是還有兩次,完全是通過CPU在內存裏面進行數據複製。

在Kafka裏,通過Java的NIO裏面FileChannel的transferTo方法調用,我們可以不用把數據複製到我們應用程序的內存裏面。通過DMA的方式,

我們可以把數據從內存緩衝區直接寫到網卡的緩衝區裏面。在使用了這樣的零拷貝的方法之後呢,我們傳輸同樣數據的時間,可以縮減爲原來的1/3,相當於提升了3倍的吞吐率。

這也是爲什麼,Kafka是目前實時數據傳輸管道的標準解決方案

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章