從網絡IO到Thrift網絡模型

I/O多路複用

IO多路複用就是經過一種機制,一個進程能夠監聽多個文件描述符,一個某個描述符就緒(通常是讀就緒或寫就緒),就可以通知程序進行相應的讀寫操做。select、poll、epoll本質上都是同步IO,由於他們須要在讀寫事件就緒後本身負責讀寫,即這個讀寫過程是阻塞的,而異步IO則無需本身負責讀寫,異步IO的實現會把數據從內核拷貝到用戶空間。linux

select

基本原理

select 函數監視的文件描述符分3類,分別是writefds、readfds、和exceptfds。調用後select函數會阻塞,直到有描述符就緒(有數據 可讀、可寫、或者有except),或者超時(timeout指定等待時間,若是當即返回設爲null便可),函數返回。當select函數返回後,能夠經過遍歷fdset,來找到就緒的描述符。segmentfault

select的缺點

  • 單進程所能打開的文件描述符有必定限制,32位機默認1028,64位機默認2048。
  • 對socket進程掃描時是線性掃描,效率很低。
  • 用來存放文件描述符的數據結構,在用戶空間和內核空間的複製開銷極大。

poll

poll與select相似,略過。網絡

epoll

epoll是在linux 2.6內核中提出的,是select和poll的加強版本。數據結構

基本原理

epoll支持水平觸發和邊緣觸發,最大的特色在於邊緣觸發,它只告訴進程哪些fd剛剛變爲就緒態,而且只會通知一次。還有一個特色是,epoll使用「事件」的就緒通知方式,一旦該fd就緒,內核就會採用相似callback的回調機制來激活該fd。併發

epoll的優勢

  • 沒有最大鏈接數的限制,1G內存約能監聽10W個端口。
  • 不採用輪詢的方式,不會隨着FD數目的增長效率降低。只有活躍可用的FD纔會回調。
  • 內存拷貝,epoll使用mmap減小複製開銷。(注:mmap本質就是繞過從網卡、磁盤拷貝數據到內核再拷貝到用戶空間的方式,直接從網卡拷貝數據到用戶空間,性能爆炸。)

Thrift網絡服務模型

thrift提供的網絡服務模型有阻塞服務模型、非阻塞服務模型:負載均衡

  • 阻塞服務模型:TSimpleServer、TThreadPoolServer
  • 非阻塞服務模型:TNonblockingServer、THsHaServer和TThreadedSelectorServer

TSimpleServer

該模式採用最簡單的阻塞IO,一次只能接收並處理一個socket,處理流程以下:
alt text
此種模式效率低下,生產不會使用,略過。異步

TThreadPoolServer

TThreadPoolServer模式採用阻塞socket方式工做,主線程負責阻塞式(劃重點,不是select的方式)監聽是否有新socket到來,具體的業務處理交由一個線程池來處理。socket

accept部分的代碼以下:函數

protected TSocket acceptImpl() throws TTransportException {
    if (serverSocket_ == null) {
      throw new TTransportException(TTransportException.NOT_OPEN, "No underlying server socket.");
    }
    try {
      // 阻塞式監聽新的鏈接
      Socket result = serverSocket_.accept();
      TSocket result2 = new TSocket(result);
      result2.setTimeout(clientTimeout_);
      return result2;
    } catch (IOException iox) {
      throw new TTransportException(iox);
    }
  }

具體模型以下:
alt text高併發

TThreadPoolServer本質是One Thread Per Connection模型。模型受限於線程池的最大線程數,在鏈接數很大話,請求只能排隊,對於高併發的場景,此模型並不合適。

TNonblockingServer

TNonblockingServer模式也是單線程工做,可是採用NIO的模式,藉助Channel/Selector機制, 採用IO事件模型來處理。本質是一種event-loop模型。

具體模型以下:
alt text

event-loop的核心代碼以下:

private void select() {
      try {
        // 等待事件,jdk7以前的版本存在問題,會存在會將CPU打滿的狀況,沒有事件,select卻返回,從而將CPU打滿;Netty中經過threshold,解決了該問題
        selector.select();

        // 獲取IO事件
        Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
        while (!stopped_ && selectedKeys.hasNext()) {
          SelectionKey key = selectedKeys.next();
          selectedKeys.remove();

          // skip if not valid
          if (!key.isValid()) {
            cleanupSelectionKey(key);
            continue;
          }

          // if the key is marked Accept, then it has to be the server
          // transport.
          // 處理鏈接事件
          if (key.isAcceptable()) {
            handleAccept();
          } else if (key.isReadable()) {
            // deal with reads
            // 處理讀事件
            handleRead(key);
          } else if (key.isWritable()) {
            // deal with writes
            // 處理寫事件
            handleWrite(key);
          } else {
            LOGGER.warn("Unexpected state in select! " + key.interestOps());
          }
        }
      } catch (IOException e) {
        LOGGER.warn("Got an IOException while selecting!", e);
      }
    }

這個模型通常由一個event dispatcher等待各種事件,待事件發生後原地調用對應的event handler,所有調用完後等待更多事件,故爲"loop"。這個模型的實質是把多段邏輯按事件觸發順序交織在一個系統線程中。一個event-loop只能使用一個核,故此類程序要麼是IO-bound,要麼是每一個handler有肯定的較短的運行時間(好比http server),不然一個耗時漫長的回調就會卡住整個程序,產生高延時。在實踐中這類程序不適合多開發者參與,一我的寫了阻塞代碼可能就會拖慢其餘代碼的響應。因爲event handler不會同時運行,不太會產生複雜的race condition,一些代碼不須要鎖。此類程序主要靠部署更多進程增長擴展性。

THsHaServer

THsHaServer繼承於TNonblockingServer,引入了線程池提升了任務處理的併發能力。THsHaServer是半同步半異步(Half-Sync/Half-Async)的處理模式,Half-Aysnc用於IO事件處理(Accept/Read/Write),Half-Sync用於業務handler對rpc的同步處理上。

具體模型以下:

alt text

THsHaServer與TNonblockingServer模式相比,THsHaServer在完成數據讀取以後,將業務處理過程交由一個線程池來完成,主線程直接返回進行下一次循環操做,效率大大提高。

可是,主線程仍然須要處理accpet、read、write時間,當併發量很是大,讀取或者發送的數據量比較大時,會將主線程阻塞住,新的鏈接沒法被及時處理。

TThreadedSelectorServer

TThreadedSelectorServer是對THsHaServer的一種改進,它將selector中的read/write事件從主線程中剝離出來。

TThreadedSelectorServer是thrift提供的最高效的網絡模型。具體模型以下:
alt text

構成以下:

  • 一個Accpet線程,專門用來處理新的socket
  • n個SelectorThread,用來處理read/write事件,讀取、返回數據都是有這些線程完成的,每一個SelectorThread會與若干個socket綁定,每一個SelectorThread會處理與它綁定socket的read/write事件。
  • 一個負載均衡器SelectorThreadLoadBalancer對象,在accpet線程接收到新的socket之後,由SelectorThreadLoadBalancer決定將socket與哪一個SelectorThread綁定(其實就是一個next函數,每分配一個socket,就調用next)。
  • 一個ExecutorService類型的worker線程池,在SelectorThread讀取數據以後,將其包裝成一個task,分配給worker線程池,處理業務邏輯。

總結:TThreadedSelectorServer模式,其實就是標準的Reactor模式,Tomcat7之後的版本、Cobar、MyCat(分庫分表proxy)基本都是這個套路,具體實現略有差別。

原文連接

https://segmentfault.com/a/11...

相關文章
相關標籤/搜索