零拷貝,NIO抡四,Reactor和Netty

1. NIO

閱讀本節(jié)前柜蜈,請(qǐng)先閱讀我的NIO基礎(chǔ)文章:http://www.reibang.com/nb/8788241

NIO是Java中的一種同步非阻塞IO,NIO是面向buffer的非阻塞IO指巡。其中最重要的的三個(gè)核心概念是:Channel淑履,Buffer和Selector。

Channel

Channel類似于BIO中的流藻雪,可以從中讀取或者寫入數(shù)據(jù)秘噪。但它和流有以下區(qū)別:

  1. Channel是雙向的,既可以讀又可以寫勉耀,而流是單向的缆娃。
  2. Channel可以進(jìn)行異步的讀寫。
  3. 對(duì)Channel的讀寫必須通過(guò)buffer對(duì)象瑰排。

在Java NIO中Channel主要有如下幾種類型:

  • FileChannel:從文件讀取數(shù)據(jù)的
  • DatagramChannel:讀寫UDP網(wǎng)絡(luò)協(xié)議數(shù)據(jù)
  • SocketChannel:讀寫TCP網(wǎng)絡(luò)協(xié)議數(shù)據(jù)
  • ServerSocketChannel:可以監(jiān)聽(tīng)TCP連接

Buffer

Buffer是NIO中用于存放待讀寫數(shù)據(jù)的容器。數(shù)據(jù)總是從Channel中讀取到Buffer中暖侨,或者從Buffer中寫入到Channel中椭住。

常見(jiàn)Buffer的實(shí)現(xiàn)類包括:ByteBuffer,CharBuffer字逗,DoubleBuffer京郑,F(xiàn)loatBuffer,IntBuffer葫掉,LongBuffer些举,ShortBuffer等。

其中ByteBuffer又包含兩個(gè)實(shí)現(xiàn)類:HeapBuffer和MappedByteBuffer(以及其實(shí)現(xiàn)類DirectBuffer)俭厚。

HeapBuffer這種緩沖區(qū)是分配在堆上面的户魏,而DirectBuffer則是直接指向了一塊堆外直接內(nèi)存。

零拷貝技術(shù)

下圖展示了一個(gè)IO讀寫流程:

image.png
  1. DMAread讀取磁盤文件內(nèi)容到內(nèi)核緩沖區(qū)
  2. 拷貝內(nèi)核緩沖區(qū)數(shù)據(jù)到應(yīng)用進(jìn)程緩沖區(qū)(內(nèi)核態(tài)和用戶態(tài)的切換)
  3. 從應(yīng)用進(jìn)程緩沖區(qū)copy數(shù)據(jù)到socket緩沖區(qū)(內(nèi)核態(tài)和用戶態(tài)的切換)
  4. DMA copy給網(wǎng)卡發(fā)送

可以清楚得看到,上述IO流程包含兩次用戶態(tài)和內(nèi)核態(tài)上下文切換叼丑,在高并發(fā)場(chǎng)景下关翎,這些會(huì)很致命。因此鸠信,Linux提出了零拷貝的概念:即避免用戶態(tài)和內(nèi)核態(tài)的切換纵寝,直接在內(nèi)核中進(jìn)行數(shù)據(jù)傳遞。Linux提供了兩個(gè)函數(shù)mmapsendfile來(lái)實(shí)現(xiàn)零拷貝:

  • mmap: 內(nèi)存映射文件星立,即將文件的一段直接映射到內(nèi)存爽茴,內(nèi)核和應(yīng)用進(jìn)程共用同一塊內(nèi)存地址
  • sendfile: 從上圖的內(nèi)核緩沖區(qū)直接復(fù)制到socket緩沖區(qū), 不需要向應(yīng)用進(jìn)程緩沖區(qū)拷貝

mmap

傳統(tǒng)的IO操作都是在內(nèi)核準(zhǔn)備好數(shù)據(jù)后,將數(shù)據(jù)從內(nèi)核中拷貝一份到用戶空間中绰垂。而直接內(nèi)存(mmap技術(shù))將文件直接映射到內(nèi)核空間的內(nèi)存室奏,返回一個(gè)操作地址(address),省去了內(nèi)核空間拷貝到用戶空間這一步操作辕坝。如下圖所示:

mmap.PNG

在NIO中窍奋,MappedByteBuffer則對(duì)應(yīng)著mmap技術(shù)。下面是MappedByteBuffer的使用例子:

FileChannel f1 = new FileInputStream(file1).getChannel();
// FileOutputStream打開(kāi)的FileChannel只能寫入
FileChannel f2 = new FileOutputStream(file2).getChannel();) 
// 將file1的數(shù)據(jù)全部映射成ByteBuffer
MappedByteBuffer mbb = f1.map(MapMode.READ_ONLY, 0, file.length());
// 將buffer里的數(shù)據(jù)寫入到file2中
f2.write(mbb);
mbb.clear();

HeapBuffer的數(shù)據(jù)結(jié)構(gòu)類似于:

public Class HeapBuffer {
    byte[] data;
    int position, limit, int capacity;
}

而DirectBuffer則直接指向一個(gè)內(nèi)存地址:

public Class DirectBuffer {
    long address;
    int position, limit, int capacity;
}

當(dāng)我們把一個(gè)Direct Buffer寫入Channel的時(shí)候酱畅,就好比是“內(nèi)核緩沖區(qū)”的內(nèi)容直接寫入了Channel琳袄,這樣顯然快了,減少了數(shù)據(jù)拷貝纺酸。而當(dāng)我們把一個(gè)Heap Buffer寫入Channel的時(shí)候窖逗,實(shí)際上底層實(shí)現(xiàn)會(huì)先構(gòu)建一個(gè)臨時(shí)的Direct Buffer,然后把Heap Buffer的內(nèi)容復(fù)制到這個(gè)臨時(shí)的Direct Buffer上餐蔬,再把這個(gè)Direct Buffer寫出去碎紊。當(dāng)然,如果我們多次調(diào)用write方法樊诺,把一個(gè)Heap Buffer寫入Channel仗考,底層實(shí)現(xiàn)可以重復(fù)使用臨時(shí)的Direct Buffer,這樣不至于因?yàn)轭l繁地創(chuàng)建和銷毀Direct Buffer影響性能词爬。

Direct Buffer創(chuàng)建和銷毀的代價(jià)很高秃嗜,所以要用在盡可能重用的地方。 比如周期長(zhǎng)傳輸文件大采用Direct Buffer顿膨,不然一般情況下就直接用heap buffer 就好锅锨。

sendfile

sendfile不存在內(nèi)存映射, 同時(shí)保留了mmap的不需要來(lái)回拷貝優(yōu)點(diǎn),適用于應(yīng)用進(jìn)程不需要對(duì)讀取的數(shù)據(jù)做任何處理的場(chǎng)景恋沃。如圖:

sendfile.PNG

Java中Channel.transferTo(Channel destination)對(duì)應(yīng)著sendfile技術(shù)必搞。

Selector

Selector用于監(jiān)聽(tīng)多個(gè)Channel的事件。

2. Reactor模型

Reactor模型中主要有三種角色:

  • Reactor:內(nèi)部封裝了一個(gè)selector囊咏,循環(huán)調(diào)用select方法獲得就緒channel恕洲。然后將就緒channel dispatch給對(duì)應(yīng)handler執(zhí)行真的讀寫邏輯塔橡。
  • Acceptor:監(jiān)聽(tīng)客戶端連接,并為客戶端的SocketChannel向Reactor注冊(cè)對(duì)應(yīng)的handler研侣。
  • Handlers:真正執(zhí)行非阻塞讀/寫任務(wù)邏輯谱邪。

Reactor模型從復(fù)雜程度又可以分為三種:?jiǎn)蜶eactor單線程模型,單Reactor多線程模型和多Reactor多線程模型庶诡。

2.1 單Reactor單線程模型

單Reactor單線程模型.PNG

下面是其實(shí)現(xiàn):

/**
    * 等待事件到來(lái)惦银,分發(fā)事件處理
    */
  class Reactor implements Runnable {
?
      private Reactor() throws Exception {
?
          SelectionKey sk =
                  serverSocket.register(selector,
                          SelectionKey.OP_ACCEPT);
          // attach Acceptor 處理新連接
          sk.attach(new Acceptor());
      }
?
      public void run() {
          try {
              while (!Thread.interrupted()) {
                  selector.select();
                  Set selected = selector.selectedKeys();
                  Iterator it = selected.iterator();
                  while (it.hasNext()) {
                      it.remove();
                      //分發(fā)事件處理
                      dispatch((SelectionKey) (it.next()));
                  }
              }
          } catch (IOException ex) {
              //do something
          }
      }
?
      void dispatch(SelectionKey k) {
          // 若是連接事件獲取是acceptor
          // 若是IO讀寫事件獲取是handler
          Runnable runnable = (Runnable) (k.attachment());
          if (runnable != null) {
              runnable.run();
          }
      }
?
  }
  /**
    * 連接事件就緒,處理連接事件
    */
  class Acceptor implements Runnable {
      @Override
      public void run() {
          try {
              SocketChannel c = serverSocket.accept();
              if (c != null) {// 注冊(cè)讀寫
                  new Handler(c, selector);
              }
          } catch (Exception e) {
?
          }
      }
  }
  /**
    * 處理讀寫業(yè)務(wù)邏輯
    */
  class Handler implements Runnable {
      public static final int READING = 0, WRITING = 1;
      int state;
      final SocketChannel socket;
      final SelectionKey sk;
?
      public Handler(SocketChannel socket, Selector sl) throws Exception {
          this.state = READING;
          this.socket = socket;
          sk = socket.register(selector, SelectionKey.OP_READ);
          sk.attach(this);
          socket.configureBlocking(false);
      }
?
      @Override
      public void run() {
          if (state == READING) {
              read();
          } else if (state == WRITING) {
              write();
          }
      }
?
      private void read() {
          process();
          //下一步處理寫事件
          sk.interestOps(SelectionKey.OP_WRITE);
          this.state = WRITING;
      }
?
      private void write() {
          process();
          //下一步處理讀事件
          sk.interestOps(SelectionKey.OP_READ);
          this.state = READING;
      }
?
      /**
        * task 業(yè)務(wù)處理
        */
      public void process() {
          //do something
      }
  }

這是最基本的單Reactor單線程模型。其中Reactor線程末誓,負(fù)責(zé)多路分離Socket扯俱,有新的客戶端連接觸發(fā)accept事件之后,Reactor交由Acceptor進(jìn)行處理喇澡。當(dāng)有IO讀寫事件就緒后則交給Hanlder 處理迅栅。

Acceptor主要任務(wù)就是構(gòu)建Handler ,在獲取到和client相關(guān)的SocketChannel之后 晴玖,注冊(cè)讀寫事件到Reactor(Selector)上读存,并綁定對(duì)應(yīng)的Hanlder。對(duì)應(yīng)的SocketChannel有讀寫事件之后呕屎,Reactor再交給對(duì)應(yīng)的Hanlder進(jìn)行處理让簿。

2.2 單Reactor多線程模型

單Reactor多線程模型.PNG

單Reactor多線程模型,在單Reactor的基礎(chǔ)上秀睛,增加了一個(gè)Worker線程池尔当,用于Handler的執(zhí)行。

/**
    * 多線程處理讀寫業(yè)務(wù)邏輯
    */
  class MultiThreadHandler implements Runnable {
      public static final int READING = 0, WRITING = 1;
      int state;
      final SocketChannel socket;
      final SelectionKey sk;
?
      //多線程處理業(yè)務(wù)邏輯
      ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
?
?
      public MultiThreadHandler(SocketChannel socket, Selector sl) throws Exception {
          this.state = READING;
          this.socket = socket;
          sk = socket.register(selector, SelectionKey.OP_READ);
          sk.attach(this);
          socket.configureBlocking(false);
      }
?
      @Override
      public void run() {
          if (state == READING) {
              read();
          } else if (state == WRITING) {
              write();
          }
      }
?
      private void read() {
          //任務(wù)異步處理
          executorService.submit(() -> process());
?
          //下一步處理寫事件
          sk.interestOps(SelectionKey.OP_WRITE);
          this.state = WRITING;
      }
?
      private void write() {
          //任務(wù)異步處理
          executorService.submit(() -> process());
?
          //下一步處理讀事件
          sk.interestOps(SelectionKey.OP_READ);
          this.state = READING;
      }
?
      /**
        * task 業(yè)務(wù)處理
        */
      public void process() {
          //do IO ,task,queue something
      }
  }

相對(duì)于第一種單線程的模式來(lái)說(shuō)蹂安,在處理業(yè)務(wù)邏輯椭迎,也就是獲取到IO的讀寫事件之后,交由線程池來(lái)處理田盈,這樣可以減小主reactor的性能開(kāi)銷畜号,從而更專注的做事件分發(fā)工作了,從而提升整個(gè)應(yīng)用的吞吐允瞧。

2.3 多Reactor多線程模式

多Reactor多線程模型.PNG

相比較于第二種模型简软,多Reactor多線程模式將Reactor分為兩種:

  • MainReactor:負(fù)責(zé)監(jiān)聽(tīng)socket連接,用來(lái)處理新連接的建立瓷式,將建立的socketChannel指定注冊(cè)給SubReactor。
  • SubReactor:維護(hù)自己的selector, 基于MainReactor注冊(cè)的socketChannel语泽,監(jiān)聽(tīng)讀寫就緒事件贸典,讀寫就緒后將Handler扔給worker線程池來(lái)完成。
/**
    * 多work 連接事件Acceptor,處理連接事件
    */
  class MultiWorkThreadAcceptor implements Runnable {
?
      // cpu線程數(shù)相同多work線程
      int workCount =Runtime.getRuntime().availableProcessors();
      SubReactor[] workThreadHandlers = new SubReactor[workCount];
      volatile int nextHandler = 0;
?
      public MultiWorkThreadAcceptor() {
          this.init();
      }
?
      public void init() {
          nextHandler = 0;
          for (int i = 0; i < workThreadHandlers.length; i++) {
              try {
                  workThreadHandlers[i] = new SubReactor();
              } catch (Exception e) {
              }
?
          }
      }
?
      @Override
      public void run() {
          try {
              SocketChannel c = serverSocket.accept();
              if (c != null) {// 注冊(cè)讀寫
                  synchronized (c) {
                      // 順序獲取SubReactor踱卵,然后注冊(cè)channel 
                      SubReactor work = workThreadHandlers[nextHandler];
                      work.registerChannel(c);
                      nextHandler++;
                      if (nextHandler >= workThreadHandlers.length) {
                          nextHandler = 0;
                      }
                  }
              }
          } catch (Exception e) {
          }
      }
  }
  /**
    * 多work線程處理讀寫業(yè)務(wù)邏輯
    */
  class SubReactor implements Runnable {
      final Selector mySelector;
?
      //多線程處理業(yè)務(wù)邏輯
      int workCount =Runtime.getRuntime().availableProcessors();
      ExecutorService executorService = Executors.newFixedThreadPool(workCount);
?
?
      public SubReactor() throws Exception {
          // 每個(gè)SubReactor 一個(gè)selector 
          this.mySelector = SelectorProvider.provider().openSelector();
      }
?
      /**
        * 注冊(cè)chanel
        *
        * @param sc
        * @throws Exception
        */
      public void registerChannel(SocketChannel sc) throws Exception {
          sc.register(mySelector, SelectionKey.OP_READ | SelectionKey.OP_CONNECT);
      }
?
      @Override
      public void run() {
          while (true) {
              try {
              //每個(gè)SubReactor 自己做事件分派處理讀寫事件
                  selector.select();
                  Set<SelectionKey> keys = selector.selectedKeys();
                  Iterator<SelectionKey> iterator = keys.iterator();
                  while (iterator.hasNext()) {
                      SelectionKey key = iterator.next();
                      iterator.remove();
                      if (key.isReadable()) {
                          read();
                      } else if (key.isWritable()) {
                          write();
                      }
                  }
?
              } catch (Exception e) {
?
              }
          }
      }
?
      private void read() {
          //任務(wù)異步處理
          executorService.submit(() -> process());
      }
?
      private void write() {
          //任務(wù)異步處理
          executorService.submit(() -> process());
      }
?
      /**
        * task 業(yè)務(wù)處理
        */
      public void process() {
          //do IO ,task,queue something
      }
  }

在多Reactor多線程模型中廊驼,MainReactor 主要是用來(lái)處理網(wǎng)絡(luò)IO建立連接操作据过,而SubReactor則主要復(fù)雜監(jiān)聽(tīng)I(yíng)O就緒事件,分派任務(wù)執(zhí)行妒挎。此種模型中绳锅,每個(gè)模塊的工作更加專一,耦合度更低酝掩,性能和穩(wěn)定性也大量的提升鳞芙,支持的可并發(fā)客戶端數(shù)量可達(dá)到上百萬(wàn)級(jí)別。

參考文章:https://juejin.im/post/5b4570cce51d451984695a9b

3. Netty

首先關(guān)于Netty的使用demo期虾,請(qǐng)參考:http://www.reibang.com/p/e58674eb4c7a

Netty的架構(gòu)類似于多Reactor多線程模型原朝,但是Netty默認(rèn)不使用Worker線程池執(zhí)行Handler,而是直接使用IO線程執(zhí)行讀寫任務(wù)镶苞。下圖是Netty的線程模型:

Netty.PNG

如圖所示喳坠,Netty中包含兩個(gè)NioEventLoopGroup,一個(gè)是boss茂蚓,另一個(gè)是worker壕鹉。boss負(fù)責(zé)監(jiān)聽(tīng)網(wǎng)絡(luò)連接,而worker負(fù)責(zé)分發(fā)讀寫事件聋涨。每一個(gè)NioEventLoopGroup都包含多個(gè)NioEventLoop晾浴,一個(gè)NioEventLoop本質(zhì)上是一個(gè)包含了一個(gè)Selector的SingleThreadPool。

事實(shí)上牛郑,boss類型的NioEventLoopGroup通常只包含一個(gè)NioEventLoop怠肋。

每個(gè)boss NioEventLoop循環(huán)執(zhí)行的任務(wù)包含3步:

  • 第1步:輪詢accept事件;
  • 第2步:處理io任務(wù)淹朋,即accept事件笙各,與client建立連接,生成NioSocketChannel础芍,并將NioSocketChannel注冊(cè)到某個(gè)worker NioEventLoop的selector上杈抢;
  • 第3步:處理任務(wù)隊(duì)列中的任務(wù),runAllTasks仑性。任務(wù)隊(duì)列中的任務(wù)包括用戶調(diào)用eventloop.execute或schedule執(zhí)行的任務(wù)惶楼,或者其它線程提交到該eventloop的任務(wù)。

每個(gè)worker NioEventLoop循環(huán)執(zhí)行的任務(wù)包含3步:

  • 第1步:輪詢r(jià)ead诊杆、write事件歼捐;
  • 第2步:處理io任務(wù),即read晨汹、write事件豹储,在NioSocketChannel可讀、可寫事件發(fā)生時(shí)進(jìn)行處理淘这;
  • 第3步:處理任務(wù)隊(duì)列中的任務(wù)剥扣,runAllTasks巩剖。

Client端的Netty架構(gòu)圖如下:

client netty.PNG

client端啟動(dòng)時(shí)connect到server,建立NioSocketChannel钠怯,并注冊(cè)到某個(gè)NioEventLoop的selector上佳魔。client端只包含1個(gè)NioEventLoopGroup,每個(gè)NioEventLoop循環(huán)執(zhí)行的任務(wù)包含3步:

  • 第1步:輪詢connect晦炊、read鞠鲜、write事件;
  • 第2步:處理io任務(wù)刽锤,即connect镊尺、read、write事件并思,在NioSocketChannel連接建立庐氮、可讀、可寫事件發(fā)生時(shí)進(jìn)行處理宋彼;
  • 第3步:處理非io任務(wù)弄砍,runAllTasks。

3.1 Netty模式

下面是多Reactor的使用模式:

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)

bossGroup中只有一個(gè)線程(EventLoop)输涕,而workerGroup中的線程是 CPU 核心數(shù)乘以2音婶, 因此對(duì)應(yīng)的到 Reactor 線程模型中,我們知道莱坎,這樣設(shè)置的 NioEventLoopGroup 其實(shí)就是多Reactor模型衣式。

下面是單Reactor單線程的使用模式:

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup).channel(NioServerSocketChannel.class)

注意, 我們實(shí)例化了一個(gè) NioEventLoopGroup,構(gòu)造器參數(shù)是1檐什,表示 NioEventLoopGroup 的線程池大小是1碴卧。然后接著我們調(diào)用 b.group(bossGroup) 設(shè)置了服務(wù)器端的 EventLoopGroup。此時(shí)bossGroup和workerGroup就是同一個(gè)NioEventLoopGroup乃正,并且這個(gè) NioEventLoopGroup只有一個(gè)線程(EventLoop)住册,那么對(duì)應(yīng)到Reactor的線程模型中,就相當(dāng)于單Reactor單線程模型瓮具。

3.2 耗時(shí)任務(wù)

由于Netty中的EventLoop既要處理IO荧飞,又要執(zhí)行Handler。因此需要使用特殊手段執(zhí)行耗時(shí)任務(wù)名党。主要有兩種方式:

  • Handler中加入自定義線程池
  • Pipeline中加入線程池

方法一:自定義線程池

public class ServerBusinessThreadPoolHandler extends SimpleChannelInboundHandler { 
    public static final ChannelHandler INSTANCE = new ServerBusinessThreadPoolHandler(); 
    private static ExecutorService threadPool = Executors.newFixedThreadPool(1000); 

    @Override 
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { 
        ByteBuf data = Unpooled.directBuffer(); 
        data.writeBytes(msg); 
        threadPool.submit(() -> { 
            try { 
                //耗時(shí)的操作 
               Thread.sleep(1 * 1000); 
            } catch (InterruptedException e) { 
                e.printStackTrace(); 
            } 
           Object result = getResult(data); 
           ctx.channel().writeAndFlush(result); 
         }); 
    } 
}

方法二:Pipeline中加入線程池

ServerBootstrap bootstrap = new ServerBootstrap();
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
bootstrap.group(boss, worker);
EventLoopGroup businessGroup = new NioEventLoopGroup(1000);  //大小為1000的線程池
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline p = socketChannel.pipeline();
        p.addLast(businessGroup, new NettyServerHandler()); // 添加NettyServerHandler叹阔,用來(lái)處理Server端接收和處理消息的邏輯
     }
});

如圖,通過(guò)ChannelPipeline.addLast(EventExecutorGroup group, ChannelHandler handler)API為對(duì)應(yīng)的Handler提供了優(yōu)先選擇的executor传睹。如果直接ChannelPipeline.addLast(ChannelHandler handler)方法耳幢,那么Handler執(zhí)行時(shí)默認(rèn)使用對(duì)應(yīng)的NioEventLoop來(lái)執(zhí)行。而通過(guò)ChannelPipeline.addLast(EventExecutorGroup group, ChannelHandler handler)API Netty將使用給定的EventExecutorGroup來(lái)執(zhí)行handler蒋歌。

3.3 Netty避免線程切換

為了盡可能的提升性能帅掘,Netty在很多地方進(jìn)行了無(wú)鎖化設(shè)計(jì),例如在IO線程內(nèi)部進(jìn)行串行操作堂油,避免多線程競(jìng)爭(zhēng)導(dǎo)致的性能下降問(wèn)題修档。表面上看,串行化設(shè)計(jì)似乎CPU利用率不高府框,并發(fā)程度不夠吱窝,但是,通過(guò)調(diào)整NIO線程池的線程參數(shù)迫靖,可以同時(shí)啟動(dòng)多個(gè)串行化的線程并行運(yùn)行院峡,這種局部無(wú)鎖化的串行線程設(shè)計(jì)相比一個(gè)隊(duì)列---多個(gè)工作線程的模型性能更優(yōu)。

Netty的NioEventLoop讀取到消息之后系宜,直接調(diào)用ChannelPipeline的fireChannelRead(Object msg)照激。只要用戶不主動(dòng)切換線程,一直都是由NioEventLoop調(diào)用用戶的Handler盹牧,期間不進(jìn)行線程切換俩垃。這種串行處理方式避免了多線程操作導(dǎo)致的鎖的競(jìng)爭(zhēng),從性能角度看是最優(yōu)的汰寓。

4. tomcat

Tomcat也采用了Reactor模型的設(shè)計(jì)理念口柳,如下圖所示:

Tomcat.PNG
Tomcat模型.PNG

如圖所示,Tomcat線程模型中包含四個(gè)關(guān)鍵角色:

  • Acceptor:負(fù)責(zé)處理Socket連接有滑。獲得SocketChannel對(duì)象跃闹,然后封裝在一個(gè)tomcat的實(shí)現(xiàn)類org.apache.tomcat.util.net.NioChannel對(duì)象中。然后將NioChannel對(duì)象封裝在一個(gè)PollerEvent對(duì)象中毛好,并將PollerEvent對(duì)象壓入Poller Event Queue里望艺。
  • Poller:每一個(gè)Poller線程都維護(hù)了一個(gè)Selector對(duì)象,主要負(fù)責(zé)消費(fèi)Event Queue中的數(shù)據(jù)睛榄,并注冊(cè)到內(nèi)部的Selector上荣茫,然后不斷監(jiān)聽(tīng)Socket讀寫就緒事件。讀寫就緒后场靴,將就緒的SocketChannel傳遞給Worker線程池執(zhí)行讀寫任務(wù)啡莉。
  • Poller Event Queue:存儲(chǔ)PollerEvent對(duì)象的消息隊(duì)列。注意旨剥,這個(gè)消息隊(duì)列實(shí)際上存儲(chǔ)在Poller中咧欣,Poller中包含了一個(gè)private final SynchronizedQueue<PollerEvent> events = new SynchronizedQueue<>();屬性用于存儲(chǔ)PollerEvent。并且Poller開(kāi)放了void addEvent(PollerEvent event)方法轨帜,從而Acceptor能夠?qū)ollerEvent傳遞給Poller魄咕。
  • Worker:實(shí)際的IO讀寫線程。

多個(gè)Worker線程蚌父,有時(shí)候也叫IO線程哮兰,就是專門負(fù)責(zé)IO讀寫的毛萌。一種實(shí)現(xiàn)方式就是像Netty一樣,每個(gè)Worker線程都有自己的Selector喝滞,可以負(fù)責(zé)多個(gè)連接的IO讀寫事件阁将,每個(gè)連接歸屬于某個(gè)線程。另一種方式實(shí)現(xiàn)方式就是有專門的線程負(fù)責(zé)IO事件監(jiān)聽(tīng)右遭,這些線程有自己的Selector做盅,一旦監(jiān)聽(tīng)到有IO讀寫事件,并不是像第一種實(shí)現(xiàn)方式那樣(自己去執(zhí)行IO操作)窘哈,而是將IO操作封裝成一個(gè)Runnable交給Worker線程池來(lái)執(zhí)行吹榴,這種情況每個(gè)連接可能會(huì)被多個(gè)線程同時(shí)操作,相比第一種并發(fā)性提高了滚婉,但是也可能引來(lái)多線程問(wèn)題图筹,在處理上要更加謹(jǐn)慎些。tomcat的NIO模型就是第二種让腹。

參考文章:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末婿斥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子哨鸭,更是在濱河造成了極大的恐慌民宿,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件像鸡,死亡現(xiàn)場(chǎng)離奇詭異活鹰,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)只估,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門志群,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蛔钙,你說(shuō)我怎么就攤上這事锌云。” “怎么了吁脱?”我有些...
    開(kāi)封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵桑涎,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我兼贡,道長(zhǎng)攻冷,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任遍希,我火速辦了婚禮等曼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己禁谦,他們只是感情好胁黑,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著州泊,像睡著了一般别厘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拥诡,一...
    開(kāi)封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音氮发,去河邊找鬼渴肉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛爽冕,可吹牛的內(nèi)容都是我干的仇祭。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼颈畸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼乌奇!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起眯娱,我...
    開(kāi)封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤礁苗,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后徙缴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體试伙,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年于样,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了疏叨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡穿剖,死狀恐怖蚤蔓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情糊余,我是刑警寧澤秀又,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站贬芥,受9級(jí)特大地震影響涮坐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜誓军,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一袱讹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦捷雕、人聲如沸椒丧。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)壶熏。三九已至,卻和暖如春浦译,著一層夾襖步出監(jiān)牢的瞬間棒假,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工精盅, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帽哑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓叹俏,卻偏偏與公主長(zhǎng)得像妻枕,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子粘驰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容