爲什麼要使用Netty

有興趣的同學可以移步筆者的個人博客 更多博客

爲什麼使用netty

Netty是一個網絡通信框架,其出現的原因主要是爲了解決NIO的不足。如:

  1. NIO的類庫和API繁雜,使用麻煩,你需要熟練掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等;
  2. 需要具備其它的額外技能做鋪墊,例如熟悉Java多線程編程,因爲NIO編程涉及到Reactor模式,你必須對多線程和網路編程非常熟悉,才能編寫出高質量的NIO程序;
  3. 可靠性能力補齊,工作量和難度都非常大。例如客戶端面臨斷連重連、網絡閃斷、半包讀寫、失敗緩存、網絡擁塞和異常碼流的處理等等,NIO編程的特點是功能開發相對容易,但是可靠性能力補齊工作量和難度都非常大;

NIO(Non-blocking I/O,在Java領域,也稱爲New I/O),是一種同步非阻塞的I/O模型,也是I/O多路複用的基礎,已經被越來越多地應用到大型應用服務器,成爲解決高併發與大量連接、I/O處理問題的有效方式。

本文會從傳統的阻塞I/O和線程池模型面臨的問題講起,然後對比幾種常見I/O模型,一步步分析NIO怎麼利用事件模型處理I/O,解決線程池瓶頸處理海量連接,包括利用面向事件的方式編寫服務端/客戶端程序。

傳統BIO模型分析

{
 ExecutorService executor = Excutors.newFixedThreadPollExecutor(100);//線程池
 ServerSocket serverSocket = new ServerSocket();
 serverSocket.bind(8088);
 while(!Thread.currentThread.isInturrupted()){//主線程死循環等待新連接到來
 Socket socket = serverSocket.accept();
 executor.submit(new ConnectIOnHandler(socket));//爲新的連接創建新的線程
}
class ConnectIOnHandler extends Thread{
    private Socket socket;
    public ConnectIOnHandler(Socket socket){
       this.socket = socket;
    }
    public void run(){
      while(!Thread.currentThread.isInturrupted()&&!socket.isClosed()){死循環處理讀寫事件
          String someThing = socket.read()//讀取數據
          if(someThing!=null){
             //處理數據
             socket.write()//寫數據
          }
      }
    }
}

上面的代碼就是典型的傳統BIO服務端監聽代碼,由於accept()read()write() 這三個方法是阻塞的、耗時的。如果是單線程的話,在執行阻塞代碼是會使cpu空等,並且最重要的是,如果有10000個併發的話,那麼等待時間是不能夠接受的。

使用多線程的本質就是:

  1. 利用多核。
  2. 當I/O阻塞系統,但CPU空閒的時候,可以利用多線程使用CPU資源。

但是傳統IO在使用多線程解決上面的問題時會嚴重依賴於線程,意味着每個請求都要建立一個線程,當客戶端數量達到萬級時,就需要建立萬級的線程。線程佔用的內存,切換線程資源昂貴,最終導致這種方案無法保證系統的伸縮性。

傳統NIO模型分析

所有的系統I/O都分爲兩個階段:等待就緒和操作。舉例來說,讀函數,分爲等待系統可讀和真正的讀;同理,寫函數分爲等待網卡可以寫和真正的寫。

需要說明的是等待就緒的阻塞是不使用CPU的,是在“空等”;而真正的讀寫操作的阻塞是使用CPU的,真正在"幹活",而且這個過程非常快,屬於memory copy,帶寬通常在1GB/s級別以上,可以理解爲基本不耗時。


interface ChannelHandler{
     void channelReadable(Channel channel);
     void channelWritable(Channel channel);
  }
  class Channel{
    Socket socket;
    Event event;//讀,寫或者連接
  }

  //IO線程主循環:
  class IoThread extends Thread{
  public void run(){
  Channel channel;
  while(channel=Selector.select()){//選擇就緒的事件和對應的連接
     if(channel.event==accept){
        registerNewChannelHandler(channel);//如果是新連接,則註冊一個新的讀寫處理器
     }
     if(channel.event==write){
        getChannelHandler(channel).channelWritable(channel);//如果可以寫,則執行寫事件
     }
     if(channel.event==read){
         getChannelHandler(channel).channelReadable(channel);//如果可以讀,則執行讀事件
     }
   }
  }
  Map<Channel,ChannelHandler> handlerMap;//所有channel的對應事件處理器
 }

NIO由原來的阻塞讀寫(佔用線程)變成了單線程輪詢事件,找到可以進行讀寫的網絡描述符進行讀寫。除了事件的輪詢是阻塞的(沒有可乾的事情必須要阻塞),剩餘的I/O操作都是純CPU操作,沒有必要開啓多線程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章