<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          網(wǎng)上關于“零拷貝”原理相關的文章滿天飛,但你知道如何使用零拷貝嗎?

          共 3809字,需瀏覽 8分鐘

           ·

          2021-11-18 14:59

          點擊上方“服務端思維”,選擇“設為星標

          回復”669“獲取獨家整理的精選資料集

          回復”加群“加入全國服務端高端社群「后端圈」


          作者 | 丁威
          出品?| 中間件興趣圈

          零拷貝是中間件相關面試中必考題,本文就和大家一起來總結一下NIO拷貝的原理,并結合Netty代碼,從代碼實現(xiàn)層面近距離觀摩如何使用java實現(xiàn)零拷貝。

          1、零拷貝實現(xiàn)原理

          **“零拷貝”**其實包括兩個層面的含義:

          • 拷貝 一份相同的數(shù)據(jù)從一個地方移動到另外一個地方的過程,叫拷貝。
          • 零 希望在IO讀寫過程中,CPU控制的數(shù)據(jù)拷貝到次數(shù)為0。

          在IO編程領域,當然是拷貝的次數(shù)越少越好,逐步優(yōu)化,將其拷貝次數(shù)將為0,最大化的提高性能。

          那接下來我們循序漸進來看一下如何減少數(shù)據(jù)復制。

          接下來我們將以RocketMQ消息發(fā)送、消息讀取場景來闡述IO讀寫過程中可能需要進行的數(shù)據(jù)復制與上下文切換。

          1.1 傳統(tǒng)的IO讀流程

          一次傳統(tǒng)的IO讀序列流程如下所示:java應用中,如果要將從文件中讀取數(shù)據(jù),其基本的流程如下所示:

          1. 當broker收到拉取請求時發(fā)起一次read系統(tǒng)調用,此時操作系統(tǒng)會進行一次上下文的切換,從用態(tài)間切換到內核態(tài)
          2. 通過直接存儲訪問器(DMA)從磁盤將數(shù)據(jù)加載到內核緩存區(qū)DMA Copy,這個階段不需要CPU參與,如果是阻塞型IO,該過程用戶線程會處于阻塞狀態(tài))
          3. 然后在CPU的控制下,將內核緩存區(qū)的數(shù)據(jù)copy到用戶空間的緩存區(qū)(由于這個是操作系統(tǒng)級別的行為,通常這里指的內存緩存區(qū),通常使用的是堆外內存),這里將發(fā)生一次CPU復制與一次上下文切換(從內核態(tài)切換到用戶態(tài))
          4. 堆外內存中的數(shù)據(jù)復制到應用程序的堆內存,供應用程序使用,本次復制需要經(jīng)過CPU控制。
          5. 將數(shù)據(jù)加載到堆空間,需要傳輸?shù)骄W(wǎng)卡,這個過程又要進入到內核空間,然后復制到sockebuffer,然后進入網(wǎng)卡協(xié)議引擎,從而進入到網(wǎng)絡傳輸中。該部分會在接下來會詳細介紹。

          溫馨提示:RocketMQ底層的工作機制并不是上述模型,是經(jīng)過優(yōu)化后的讀寫模型,本文將循序漸進的介紹優(yōu)化過程。

          1.2 傳統(tǒng)的IO寫流程

          一次傳統(tǒng)的IO寫入流程如下圖所示:核心關鍵步驟如下:

          1. 在broker收到消息時首先會在堆空間中創(chuàng)建一個堆緩存區(qū),用于存儲用戶需要寫入的數(shù)據(jù),然后需要將jvm堆內存中數(shù)據(jù)復制到操作系統(tǒng)內存(CPU COPY)
          2. 發(fā)起write系統(tǒng)調用,將用戶空間中的數(shù)據(jù)復制到內存緩存區(qū),**此過程發(fā)生一次上下文切換(用戶態(tài)切換到內核態(tài))**并進行一次CPU Copy。
          3. 通過直接存儲訪問器(DMA)將內核空間的數(shù)據(jù)寫入到磁盤,并返回結果,此過程發(fā)生一次DMA Copy 與一次上下文切換(內核態(tài)切換到用戶態(tài))

          1.3 讀寫優(yōu)化技巧

          從上面兩張流程圖,我們不能看出讀寫處理流程中存在太多復制,同樣的數(shù)據(jù)需要被復制多次,造成性能損耗,故IO讀寫通常的優(yōu)化方向主要為:減少復制次數(shù)、減少用戶態(tài)/內核態(tài)切換次數(shù)。

          1.3.1 引入堆外內存

          jvm堆空間中數(shù)據(jù)要發(fā)送到內核緩存區(qū),通常需要先將jvm堆空間中的數(shù)據(jù)拷貝到系統(tǒng)內存(一個非官方的理解,用C語言實現(xiàn)的本地方法調用中,首先需要將堆空間中數(shù)據(jù)拷貝到C語言相關的存儲結構),故提高性能的第一個措施:使用堆外內存。

          不過堆外內存中的數(shù)據(jù),通常還是需要從堆空間中獲取,從這個角度來看,貌似提升的性能有限。

          1.3.2 引入內存映射(MMap與write)

          通過引入內存映射機制,減少用戶空間與內核空間之間的數(shù)據(jù)復制,如下圖所示:內存映射的核心思想就是將內核緩存區(qū)、用戶空間緩存區(qū)映射到同一個物理地址上,可以減少用戶緩存區(qū)與內核緩存區(qū)之間的數(shù)據(jù)拷貝

          但由于內存映射機制并不會減少上下文切換次數(shù)。

          1.3.3 大名鼎鼎鼎sendfile

          在Linux 2.1內核引入了sendfile函數(shù)用于將文件通過socket傳送

          注意sendfile的傳播方向:使用于將文件中的內容直接傳播到Socket,通常使用客戶端從服務端文件中讀取數(shù)據(jù),在服務端內部實現(xiàn)零拷貝。

          在1.3.1中介紹客戶端從服務端讀取消息的過程中,并沒有展開介紹從服務端寫入到客戶端網(wǎng)絡中的過程,接下來看看sendfile的數(shù)據(jù)拷貝圖解:sendfile的主要特點是在內核空間中通過DMA將數(shù)據(jù)從磁盤文件拷貝到內核緩存區(qū),然后可以直接將內核緩存區(qū)中的數(shù)據(jù)在CPU控制下將數(shù)據(jù)復制到socket緩存區(qū),最終在DMA的控制下將socketbufer中拷貝到協(xié)議引擎,然后經(jīng)網(wǎng)卡傳輸?shù)侥繕硕恕?/p>

          sendfile的優(yōu)勢(特點):

          • 一次sendfile調用會只設計兩次上下文切換,比read+write減少兩次上下文切換。
          • 一次sendfile會存在3次copy,其中一次CPU拷貝,兩次DMA拷貝。
          1.3.4 Linux Gather

          Linux2.4內核引入了gather機制,用以消除最后一次CPU拷貝,即不再將內核緩存區(qū)中的數(shù)據(jù)拷貝到socketbuffer,而是將內存緩存區(qū)中的內存地址、需要讀取數(shù)據(jù)的長度寫入到socketbuffer中,然后DMA直接根據(jù)socketbuffer中存儲的內存地址,直接從內核緩存區(qū)中的數(shù)據(jù)拷貝到協(xié)議引擎(注意,這次拷貝由DMA控制)。

          從而實現(xiàn)真正的零拷貝。

          2、結合Netty談零拷貝實戰(zhàn)

          上面講述了“零拷貝”的實現(xiàn)原理,接下來將嘗試從Netty源碼去探究在代碼層面如何使用“零拷貝”

          從網(wǎng)上的資料可以得知,在java nio提供的類庫中真正能運用底層操作系統(tǒng)的零拷貝機制只有FileChannel的transferTo,而在Netty中也不出意料的對這種方式進行了封裝,其類圖如下:其主要的核心要點是FileRegion的transferTo方法,我們結合該方法再來介紹DefaultFileRegion各個核心屬性的含義。上述代碼并不復雜,我們不難得出如下觀點:

          • 首先介紹DefaultFileRegion的核心屬性含義:
            • File f 底層抽取數(shù)據(jù)來源的底層磁盤文件
            • FileChannel file 底層文件的文件通道。
            • long position 數(shù)據(jù)從通道中抽取的起始位置
            • long count 需要傳遞的總字節(jié)數(shù)
            • long transfered 已傳遞的字節(jié)數(shù)量。
          • 核心要點是調用java nio FileChannel的transferTo方法,底層調用的是操作系統(tǒng)的sendfile函數(shù),即真正的零拷貝。
          • 調用一次transferTo方法并不一定能將需要的數(shù)據(jù)全部傳輸完成,故該方法返回已傳輸?shù)淖止?jié)數(shù),是否需要再次調用該方法的判斷方法:已傳遞的字節(jié)數(shù)是否等于需要傳遞的總字節(jié)數(shù)(transfered == count)

          接下來我們看一下FileRegion的transferTo在netty中的調用鏈,從而推斷一下Netty中的零拷貝的觸發(fā)要點。在Netty中代表兩個類型的通道:

          • EpollSocketChannel 基于Epoll機制進行事件的就緒選擇機制。

          • NioSocketChannel

            基于select機制的事件就緒選擇。

          在Netty中調用通道Channel的flush或writeAndFlush方法,都會最終觸發(fā)底層通道的網(wǎng)絡寫事件,如果待寫入的對象是FileRegion,則會觸發(fā)零拷貝機制,接下來我們對兩個簡單介紹一下:

          2.1 EpollSocketChannel 通道零拷貝

          寫入的入口函數(shù)為如下:核心思想為:如果待寫入的消息是DefaultFileRegion,EpollSocketChannel將直接調用sendfile函數(shù)進行數(shù)據(jù)傳遞;如果是FileRegion類型,則按照約定調用FileRegion的transferTo進行數(shù)據(jù)傳遞,這種方式是否真正進行零拷貝取決于FileRegion的transferTo中是否調用了FileChannel的transferTo方法。

          溫馨提示:本文并沒有打算詳細分析Epoll機制以及編程實踐。

          2.2 NioSocketChannel 通道零拷貝實現(xiàn)

          實現(xiàn)入口為:從這里可知,NioSocketChannel就是中規(guī)中矩的調用FileRegion的transferTo方法,是否真正實現(xiàn)了零拷貝,取決于底層是否調用了FileChannel的transferTo方法。

          2.3 零拷貝實踐總結

          從Netty的實現(xiàn)中我們基本可以得出結論:是否是零拷貝,判斷的依據(jù)是是否調用了FileChannel的transferTo方法,更準備的表述是底層是否調用了操作系統(tǒng)的sendfile函數(shù),并且操作系統(tǒng)底層還需要支持gather機制,即linux的內核版本不低于2.4。

          — 本文結束 —


          ●?漫談設計模式在 Spring 框架中的良好實踐

          ●?顛覆微服務認知:深入思考微服務的七個主流觀點

          ●?人人都是 API 設計者

          ●?一文講透微服務下如何保證事務的一致性

          ●?要黑盒測試微服務內部服務間調用,我該如何實現(xiàn)?



          關注我,回復 「加群」 加入各種主題討論群。



          對「服務端思維」有期待,請在文末點個在看

          喜歡這篇文章,歡迎轉發(fā)、分享朋友圈


          在看點這里

          瀏覽 50
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  人人操操人人 | 女人扒开尿口让男人桶 | 久久艹久久 | 久久99日韩 | 中日亚洲国产特级黄片 |