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


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

DMA(Direct Memory Access)

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

為什么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內存文件映射。

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是目前實時數據傳輸管道的標准解決方案

轉自:https://www.jianshu.com/p/7863667d5fa7


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM