Netty-1

NIO深入理解

  1. 零拷貝
    • 在理解0拷貝之前 我們應(yīng)該先需要了解傳統(tǒng)IO的一個(gè)操作流程
      1. 傳統(tǒng)的io操作:首先需要進(jìn)行一個(gè) read操作 這里會(huì)發(fā)生一次用戶空間切換到內(nèi)核空間 內(nèi)核會(huì)采用DMA(直接內(nèi)存訪問(wèn)的方式)從磁盤(pán)讀取數(shù)據(jù)到內(nèi)核緩沖區(qū)
      2. 內(nèi)核緩沖區(qū)將數(shù)據(jù)拷貝到用戶空間 同時(shí)再次上下文切換到 用戶空間
      3. wirte 操作 也會(huì)發(fā)生一次上下文切換到內(nèi)核空間 同時(shí)將數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū)
      4. 內(nèi)核空間會(huì)將數(shù)據(jù)拷貝到 socket buffer 在由 socket buffer 寫(xiě)入到協(xié)議引擎進(jìn)行數(shù)據(jù)的發(fā)送(這里有一個(gè)異步的過(guò)程 寫(xiě)操作后 會(huì)切換到用戶空間)

      1. 用戶發(fā)送 sendfile 命令 進(jìn)行一次上下文切換到內(nèi)核空間 并采用DMA方式將數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū)
      2. 這里內(nèi)核緩沖區(qū)的數(shù)據(jù)不會(huì)拷貝到 socket buffer而是將文件的描述信息放入如 內(nèi)存地址 以及文件長(zhǎng)度信息(異步 進(jìn)行以此上下文切換到用戶空間)
      3. 協(xié)議引擎會(huì)根據(jù) socketbuffer 中的文件描述信息 直接從內(nèi)核緩沖區(qū)將數(shù)據(jù)寫(xiě)入 協(xié)議引擎發(fā)送出去
    • 什么是Reactor模式

      • 深入理解 Reactor模式對(duì)理解netty的設(shè)計(jì)模式有莫大的幫助,所以在理解netty的設(shè)計(jì)模式時(shí),我們需要理解Reactor他的一個(gè)設(shè)計(jì)理念:Reactor的五大角色構(gòu)成

        1. Handle (句柄或是描述符)
        • 即操作系統(tǒng)中的句柄,是操作系統(tǒng)對(duì)資源的一種抽象,可以是打開(kāi)的文件、一個(gè)連接(Socket)、Timer等。在網(wǎng)絡(luò)編程中,一般指Socket Handle,文件描述符(fd)。將這個(gè)Handle注冊(cè)到Synchronous Event Demultiplexer中,就可以它發(fā)生的事件,如READ、WRITE、CLOSE等事件;handle本身是事件產(chǎn)生的發(fā)源地
        1. Synchronous Event Demultiplexer
          • 同步事件多路分用器,本質(zhì)上是系統(tǒng)調(diào)用。比如linux中的select、poll、epoll等。它會(huì)一直阻塞直在handle上,直到有事件發(fā)生時(shí)才會(huì)返回 (在java nio領(lǐng)域中對(duì)應(yīng)的組件是selector,對(duì)應(yīng)的阻塞方法就是 select方法)
        2. Initiation Dispatcher
          • 初始分發(fā)器,它提供了注冊(cè)、刪除與轉(zhuǎn)發(fā)event handler的方法。當(dāng)Synchronous Event Demultiplexer檢測(cè)到handle上有事件發(fā)生時(shí),便會(huì)通知initiation dispatcher調(diào)用特定的event handler的回調(diào)(handle_event())方法。
        3. Event Handler
          • 事件處理器,定義事件處理的回調(diào)方法:handle_event(),以供InitiationDispatcher回調(diào)使用。(Netty相對(duì)于java nio在事件處理器上進(jìn)行了升級(jí),它為我們開(kāi)發(fā)者提供了大量的回調(diào)方法,供我們?cè)谔囟ǖ氖录a(chǎn)生時(shí)實(shí)現(xiàn)相應(yīng)的回調(diào)方法進(jìn)行業(yè)務(wù)邏輯的處理)
        4. Concrete Event Handler
          • 具體的事件處理器,繼承自Event Handler,在回調(diào)方法中會(huì)實(shí)現(xiàn)具體的業(yè)務(wù)邏輯
      • Reactor模式的流程

        1. 當(dāng)應(yīng)用向Initiation Dispatcher 注冊(cè)具體的事件處理器時(shí),應(yīng)用會(huì)標(biāo)識(shí)出該事件處理器希望Initiation Dispatcher在某個(gè)事件發(fā)生時(shí)向其通知的該事件,該事件與handle關(guān)聯(lián)
        2. Initiation Dispatcher會(huì)要求每個(gè)事件處理器向其傳遞內(nèi)部handle。該handle向操作系統(tǒng)標(biāo)識(shí)了事件處理器
        3. 當(dāng)所有的事件處理器注冊(cè)完畢后,應(yīng)用會(huì)調(diào)用handle_events方法來(lái)啟動(dòng)Initiation Dispatcher的事件循環(huán)。這時(shí),Initiation Dispatcher會(huì)將每個(gè)注冊(cè)的事件管理器的handle合并起來(lái),并使用同步事件分離器等待這些事件的發(fā)生。比如說(shuō):TCP協(xié)議層會(huì)使用select同步事件分離器操作來(lái)等待客戶端發(fā)送數(shù)據(jù)到達(dá)連接的socket handle上
        4. 當(dāng)與某個(gè)事件源對(duì)應(yīng)的Handle變?yōu)閞eady狀態(tài)時(shí)(比如:TCP socket變?yōu)榈却x狀態(tài)時(shí))。同步事件分離器就會(huì)通知Initiation Dispatcher
        5. Initiation Dispatcher會(huì)觸發(fā)事件處理器的回調(diào)方法,從而響應(yīng)這個(gè)處于ready狀態(tài)的Handle。當(dāng)事件發(fā)生時(shí),Initiation Dispatcher會(huì)將被事件源激活的Handle作為【key】來(lái)尋找并分發(fā)恰當(dāng)?shù)氖录幚砥鞯幕卣{(diào)方法。
        6. Initiation Dispatcher會(huì)回調(diào)事件處理器的handle_events回調(diào)方法來(lái)執(zhí)行特定與應(yīng)用的功能(開(kāi)發(fā)者自己編寫(xiě)功能)從而響應(yīng)這個(gè)事件。所發(fā)生的事件類型可以作為該方法的參數(shù)并被該方法內(nèi)部使用來(lái)執(zhí)行額外的特定于服務(wù)的分離與分發(fā)。
      • Netty 核心的幾個(gè)概念

        1. 一個(gè)EventLoopGroup當(dāng)中包含一個(gè)或多個(gè)EventLoop
        2. 一個(gè)EventLoop在它的整個(gè)生命周期當(dāng)中都只會(huì)與唯一一個(gè)Thread進(jìn)行綁定
        3. 所有由EventLoop所處理的各種I/O事件都將在它所關(guān)聯(lián)的那個(gè)Thread上進(jìn)行處理
        4. 一個(gè)Channel在它的整個(gè)生命周期中只會(huì)注冊(cè)在一個(gè)EventLoop上
        5. 一個(gè)EventLoop在運(yùn)行過(guò)程當(dāng)中,會(huì)被分配到多個(gè)Channel
      • 結(jié)論:

        1. 在Netty中,Channel的實(shí)現(xiàn)一定是線程安全的,基于此,我們可以存儲(chǔ)一個(gè)Channel的引用,并且在需要向遠(yuǎn)端發(fā)送數(shù)據(jù)時(shí),通過(guò)整個(gè)引用來(lái)調(diào)用Channel的相應(yīng)方法;即便當(dāng)時(shí)有很多線程都在使用它也不會(huì)出現(xiàn)線程問(wèn)題;而且消息一定會(huì)按照順序發(fā)送出去。
        2. 我們?cè)跇I(yè)務(wù)開(kāi)發(fā)中,不要將長(zhǎng)時(shí)間執(zhí)行的耗時(shí)任務(wù)放入到EventLoop的執(zhí)行隊(duì)列中,因?yàn)樗鼘?huì)一直阻塞該線程所對(duì)應(yīng)的所有Channel上的其他執(zhí)行任務(wù),如果我們需要進(jìn)行阻塞調(diào)用或是耗時(shí)的操作,那么我們就需要使用一個(gè)專門(mén)的EventExecutor(業(yè)務(wù)線程池)
        3. JDK所提供的Future只能通過(guò)手工的方式檢查執(zhí)行結(jié)果,而這個(gè)操作是會(huì)阻塞的;Netty則對(duì)ChannelFuture進(jìn)行了增強(qiáng),通過(guò)ChannelFutureListener以回調(diào)的方式獲取結(jié)果,去除了手工檢查的操作(觀察者模式);值得注意的是:ChannelFutureListener的operationComplete方法是由I/O線程執(zhí)行的,因此要注意的是不要在這里執(zhí)行耗時(shí)的操作,否則需要通過(guò)另外的線程或線程池來(lái)執(zhí)行。
        4. 在Netty中有兩種發(fā)送消息的方式,可以直接寫(xiě)到Channel中,也可以寫(xiě)到ChannelHandler所關(guān)聯(lián)的那個(gè)ChannelHandlerContext中。對(duì)于前一種方式來(lái)說(shuō),消息會(huì)從ChannelPipline的末尾開(kāi)始流動(dòng);對(duì)于后一種方式來(lái)說(shuō),消息將從ChannelPipline中的下一個(gè)ChannelHandler開(kāi)始流動(dòng)。
        5. ChannelHandlerContext與ChannelHandler之間的關(guān)聯(lián)綁定關(guān)系是永遠(yuǎn)都不會(huì)發(fā)生改變的,因此對(duì)其進(jìn)行緩存是沒(méi)有任何問(wèn)題的。
        6. 對(duì)于Channel的同名方法來(lái)說(shuō),ChannelHandlerContext的方法將會(huì)產(chǎn)生更短的事件流,所以我們應(yīng)該在可能的情況下利用這個(gè)特性來(lái)提升應(yīng)用的性能。
        7. 在實(shí)際的開(kāi)發(fā)中我們經(jīng)常會(huì)遇到一個(gè)服務(wù)端可能會(huì)去要調(diào)用另外一個(gè)客戶端,這時(shí)這個(gè)服務(wù)端的角色就相當(dāng)于即作為服務(wù)端也作為客戶端。這時(shí)我們需要注意在我們作為客戶端時(shí)我們應(yīng)該將對(duì)服務(wù)端和客戶端的channel綁定在同一個(gè)eventLoop上;
      • Netty 提供的三種緩沖區(qū)類型

        1. heap buffer 堆緩沖區(qū)
          • 優(yōu)點(diǎn):由于數(shù)據(jù)是存儲(chǔ)在JVM的堆中,因此可以快速的創(chuàng)建于快速的釋放,并且他提供了直接訪問(wèn)內(nèi)部字節(jié)數(shù)組的方法
          • 缺點(diǎn):每次的讀寫(xiě)操作,都需要先將數(shù)據(jù)復(fù)制到直接緩沖區(qū)中在進(jìn)行網(wǎng)絡(luò)傳輸
        2. direct buffer 直接緩沖區(qū)(在堆之外直接分配內(nèi)存空間,直接緩沖區(qū)不會(huì)占用堆的容量空間,因?yàn)樗怯刹僮飨到y(tǒng)在本地內(nèi)存進(jìn)行的數(shù)據(jù)分配)
          • 優(yōu)點(diǎn):在使用Sockte進(jìn)行數(shù)據(jù)傳遞時(shí),性能非常好,因?yàn)閿?shù)據(jù)直接位于操作系統(tǒng)的本地內(nèi)存中,所以不需要從JVM將數(shù)據(jù)復(fù)制到直接緩沖區(qū),性能很好。
          • 缺點(diǎn):因?yàn)镈irect Buffer是直接在操作系統(tǒng)內(nèi)存中,所以內(nèi)存空間的分配與釋放要比堆空間更加復(fù)雜,而且速度慢一些。(Netty通過(guò)提供內(nèi)存池來(lái)解決這個(gè)問(wèn)題)
          • 注意:直接緩沖區(qū)不支持通過(guò)字節(jié)數(shù)組的方式來(lái)直接訪問(wèn)數(shù)據(jù)(對(duì)于后端的業(yè)務(wù)消息的編解碼來(lái)說(shuō),推薦使用HeapByteBuf;對(duì)于I/O通信線程在讀寫(xiě)緩沖區(qū)時(shí),推薦使用DirectByteBuf)
        3. composite buffer 復(fù)合緩沖區(qū)
      • JDK的ByteBuffer和Netty的ByteBuf的差異比對(duì)

        1. Netty的ByteBuf采用讀寫(xiě)索引分離的策略(readerIndex與writerIndex),一個(gè)初始化(里面尚未有任何數(shù)據(jù))的ByteBuf的readerIndex與witerIndex值都為0
        2. 當(dāng)讀索引和寫(xiě)索引處于同一個(gè)位置時(shí),如果我們繼續(xù)讀取,那么就會(huì)拋出IndexOutofBoundsException
        3. 對(duì)于ByteBuf的任何讀寫(xiě)操作都會(huì)分別單獨(dú)維護(hù)讀索引與寫(xiě)索引。maxCapacity最大的容量默認(rèn)是 Integer.MAX_VALUE。

代碼實(shí)例:

    public static void main(String[] args) {
      // 創(chuàng)建一個(gè)長(zhǎng)度為10 的Bytebuf
      ByteBuf byteBuf = Unpooled.buffer(10);

      for (int i=0;i<10;i++) {
          byteBuf.writeByte(i);
      }

      for ( int i=0; i<byteBuf.capacity();i++){
          System.out.println(byteBuf.getByte(i));
      }
  }

  public static void main(String[] args) {
      ByteBuf byteBuf = Unpooled.copiedBuffer("hello你 world", Charset.forName("utf-8"));

      if( byteBuf.hasArray()){
          // hasArray 判斷這個(gè)byteBuf背后真正的支持是不是一個(gè)字節(jié)數(shù)組 如果是 表示這是一個(gè)堆上的緩沖
          // 獲取byteBuf背后的真正數(shù)據(jù)載體
          byte[] array = byteBuf.array();
          System.out.println(new String(array,Charset.forName("utf-8")));
          System.out.println(byteBuf);

          System.out.println(byteBuf.arrayOffset());
          System.out.println(byteBuf.readerIndex());
          System.out.println(byteBuf.writerIndex());
          System.out.println(byteBuf.capacity());
          System.out.println(byteBuf.readableBytes());

          while (byteBuf.isReadable()){
              System.out.println((char) byteBuf.readByte());
          }
      }
  }

 public static void main(String[] args) {
      // 創(chuàng)建一個(gè) CompositeBuffer 復(fù)合緩沖區(qū)
      CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();

      ByteBuf heapBuf = Unpooled.buffer(10);
      ByteBuf directBuf = Unpooled.directBuffer(8);
      // 將堆緩沖 和直接緩沖添加到復(fù)合緩沖區(qū)中
      compositeByteBuf.addComponents(heapBuf,directBuf);
      //compositeByteBuf.removeComponent(0);
      Iterator<ByteBuf> iterator = compositeByteBuf.iterator();
      while (iterator.hasNext()){
          System.out.println(iterator.next());
      }

      compositeByteBuf.forEach(System.out::println);
  }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容