Netty2

NIO入門

  • 傳統的同步阻塞式I/O編程

    • 傳統BIO

      • 服務端提供位置信息(綁定的IP地址和監聽端口),客戶端通過連接操作向服務端監聽的地址發起連接請求,通過三次握手建立連接,如果連接成功,雙方就可以通過網絡套接字(Socket)進行通信

      • ServerSocket 負責綁定IP地址,啓動監聽端口

      • Socket負責發起連接操作。連接成功之後,雙方通過輸入流和輸出流進行同步阻塞式通信

    • BIO通信模型

      • 採用BIO通信模型的服務端,通常由一個獨立的Acceptor線程負責監聽客戶端的連接,它接收到客戶端連接請求之後,爲每個客戶端創建一個新的線程進行鏈路處理,處理完成之後,通過輸出流返回應答客戶端,線程消毀。一請求一應答通信模型

      • 缺乏彈性伸縮能力,線程數膨脹,系統的性能將急劇下降,系統會發生線程堆棧溢出,創建新線程失敗等問題

    • 僞異步

      • 爲了改進一線程一連接模型,後來又演進出了一種通過線程池或者消息隊列實現1個或者多個線程處理N個客戶端的請求,由於它的底層通信機制依然使用同步阻塞I/O,所以被稱爲“僞異步”

    • 僞異步I/O編程

      • 爲了解決一個鏈接需要一個線程處理的問題,後來有人對它的線程模型進行了優化,後端通過一個線程池來處理多個客戶端的請求接入,形成客戶端個數M:線程池最大線程N的比例關係,其中M可以遠遠大於N,設置線程的最大值,防止由於海量併發接入導致線程耗盡

      • 將客戶端的Scoket封裝成一個Task,投遞到後端的線程池中去進行處理,線程池可以設置消息隊列的大小和最大線程數,資源佔用是可控的。

      • 由於線程池和消息隊列都有有界的,因此,無論客戶端併發連接數多大,它都不會導致線程個數膨脹或者內存溢出,想比較一連一線程模型,是一種改良。

      • 優點:採用線程池,避免了爲每個請求都創建一個獨立線程造成的線程資源耗盡問題,

      • 缺點:底層的通信依然採用同步阻塞模型,因此無法從根本解決問題

    • 僞異步I/O弊端分享

      • TCP知識

        • 當消息的接收方處理緩慢的時候,將不能及時地從TCP緩存區讀取數據,這將會導致發送方的TCPwindow size不斷減少,直到爲0,雙方處於Keep-Alive狀態,消息發送方將不能再向TCP緩衝區寫入消息,這時如果採用的是同步阻塞I/O,write操作將會被無限期阻塞,直到TCP window size大於0或者發生I/O異常

        • 阻塞的時間取決於對方的I/O線程的處理速度和網絡I/O的傳輸速度。本質上講,我們無法保證生產環境的網絡狀態和對端的應用程序能足夠快

        • 級聯故障

          • 服務端處理緩慢,返回應答消息耗費60s

          • 採用僞異步I/O的線程正在讀取故障服務節點的響應,由於讀取輸入流是阻塞的,因此,它也會被同步阻塞

          • 假如所有可用線程都被故障服務器阻塞,那後續所有I/O消息都將在隊列中排隊

          • 由於線程池採用阻塞隊列實現,當隊列積滿之後,後續入列的操作將被阻塞

          • 前端只有一個Acceptor線程接受客戶端接入,它的阻塞在線程池的同步阻塞隊列之後,新的客端請求將被拒絕,客戶端會發送大量的超時

  • 基於NIO的非阻塞編程

    • 概念

      • 與socket類,和ServerSocket類相對應,NIO也提供了SocketChannel和ServerSocketChannel兩種不同的套接字通道實現

      • 兩種新增的通道都支持阻塞和非阻塞兩種模式

      • 提供了高速的,面向快的I/O

    • 緩存區Buffer

      • 任何時候訪問NIO中的數據,都是通過緩衝區進行操作的,讀直接讀到緩衝區中的,寫入數據,是寫入到緩衝區中的

      • 緩衝區實質上是個數組。緩衝區提供了對數據的結構化訪問以及維護讀寫位置等信息

      • 子節緩衝區,ByteBuffer,CharBuffer 字符緩衝區 。。。

      • 方便網絡讀寫

    • 通道Channel

      • Channel是一個通道,可以通過它讀取和寫入數據,它就是自來水管

      • 通道與流不同之處就在於通道是雙向的,流只是一個方向上移動,一個流必須是inputStream或者OutputStream的子類,而且通道可以用於讀寫,或同時讀寫

      • 因爲channel是全雙工的,所以它可以比較好的映射底層操作系統的API,

      • 實際上channel可以分爲兩大類,分別是用於網絡讀取的SelectableChannel和用於文件操作的FileChannel,ServerSocketChannel和SocketChannel都是SelectableChannel的子類

    • 多路複用器Selector

      • 多路複用器提供選擇已經就緒的任務的能力,Selector會不斷地輪詢註冊在其上的Channel,如果某個Channel上面有新的TCP連接接入,讀和寫實際,這個Channel就處於就緒狀態,會被Seclettor輪詢出來,然後通過SelectionKey,獲取就緒的Channel的集合,然後進行後續的I/O操作

      • 一個多路複用器Selector可以同時輪詢多個Channel,由於jdk使用了epoll代替了傳統的select實現,所以它並沒有最大連接句柄1024/2048的限制·,這也意味着只需要一個線程負責Seclector的輪詢,就可以接入成千上萬的客戶端,這是個很大的進行。

      • 實現步驟

        • 打開iServerSocketChannel 用於監聽客戶端的連接,它是所有客戶端連接的父管道

          ServerSocketChannel server = ServerSocketChannel.open();

        • 綁定監聽端口,設置連接爲非阻塞模式

          server.bind(new InetSocketAddress(this.port));

          server.configureBlocking(false);

        • 創建Reactor線程,創建多路複用器並啓動線程

          Selector selector = Selector.open();

        • 將ServerSocketChannel註冊到Reactor線程的多路複用器Seclector上,監聽ACCEPT事件

          server.register(selector, SelectionKey.OP_ACCEPT);

        • 多路複用器在線程run方法的無限循環體內輪詢準備就緒的Key

          while (true) {
              //大堂經理再叫號
              selector.select();
              //每次都拿到所有的號子
              Set<SelectionKey> keys = selector.selectedKeys();
              Iterator<SelectionKey> iter = keys.iterator();
              //不斷地迭代,就叫輪詢
              //同步體現在這裏,因爲每次只能拿一個key,每次只能處理一種狀態
              while (iter.hasNext()) {
                  SelectionKey key = iter.next();
                  iter.remove();
                  //每一個key代表一種狀態
                  //沒一個號對應一個業務
                  //數據就緒、數據可讀、數據可寫 等等等等
                  process(key);
              }
          }
      • 多路複用器監聽到有新的客戶端接入,處理新的接入請求,完成TCP三次握手,建立物理鏈路;設置客戶端鏈路爲非阻塞模式;將新接入的客戶端連接註冊到Reactor線程的多路複用器上,監聽讀操作,用來讀取客戶端發送的網絡信息

      • if (key.isAcceptable()) {
            ServerSocketChannel server = (ServerSocketChannel) key.channel();
            //這個方法體現非阻塞,不管你數據有沒有準備好
            //你給我一個狀態和反饋
            SocketChannel channel = server.accept();
            //一定一定要記得設置爲非阻塞
            channel.configureBlocking(false);
            //當數據準備就緒的時候,將狀態改爲可讀
            key = channel.register(selector, SelectionKey.OP_READ);
        }
      • 異步讀取客戶端請求信息到緩衝區

        ​
            //具體辦業務的方法,坐班櫃員
            //每一次輪詢就是調用一次process方法,而每一次調用,只能幹一件事
            //在同一時間點,只能幹一件事
            // 每個都會跑完,每給都走不同的邏輯,就是不阻塞,然後線程可以去跑其他的,這就是不阻塞
            private void process(SelectionKey key) throws IOException {
                //針對於每一種狀態給一個反應
                // 是否就緒,
                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    //這個方法體現非阻塞,不管你數據有沒有準備好
                    //你給我一個狀態和反饋
                    SocketChannel channel = server.accept();
                    //一定一定要記得設置爲非阻塞
                    channel.configureBlocking(false);
                    //當數據準備就緒的時候,將狀態改爲可讀
                    key = channel.register(selector, SelectionKey.OP_READ);
                }
                // 是否可讀
                else if (key.isReadable()) {
                    //key.channel 從多路複用器中拿到客戶端的引用
                    SocketChannel channel = (SocketChannel) key.channel();
                    int len = channel.read(buffer);
                    if (len > 0) {
                        //
                        buffer.flip();
                        String content = new String(buffer.array(), 0, len);
                        key = channel.register(selector, SelectionKey.OP_WRITE);
                        //在key上攜帶一個附件,一會再寫出去
                        key.attach(content);
                        System.out.println("讀取內容:" + content);
                    }
                    // 是否可寫
                } else if (key.isWritable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    String content = (String) key.attachment();
                    channel.write(ByteBuffer.wrap(("輸出:" + content).getBytes()));
        ​
                    channel.close();
                }
            }

         

    • 優點:

      • 客戶端發起的連接操作是異步的,可以通過在多路複用器註冊OP_CINNECT等待後續結果,不需要像之前的客戶端那樣被同步阻塞

      • SocketChannel的讀寫操作是異步的,如果沒有可讀寫的數據它不會同步等待,直接返回,這樣I/O通信線程就可以處理其他鏈路,

  • 基於NIO2.0的異步非阻塞(AIO)編程

    • 以後加

  • 爲什麼要使用NIO編程

    • 異步非阻塞

      • JDK 1.7 2002年 提供的NIO2 新增了異步的套接字通道,纔是真正的異步I/O,在異步I/O操作的時候,可以傳遞信號變量,當操作完之後,會回調相關的方法

      • JDK1.4 提供的NIO 實際上只能被稱爲非阻塞I/O,不能叫異步非阻塞

    • 多路複用器

      • 多路複用的核心,是通過Selector不斷地輪詢註冊在其上的Channel,如果某個Channel上面有新的TCP連接接入,讀和寫實際,這個Channel就處於就緒狀態,會被Seclettor輪詢出來,然後通過SelectionKEy,獲取就緒的Channel的集合,然後進行後續的I/O操作

    • 僞異步I/O

      • 線程池處理,爲了處理Tomcat通信線程同步I/O導致業務線程被掛住的問題

  • 爲什麼選擇Netty

    • 因爲Netty是NIO的升級版本,這叫好事者,自己寫太難了,

    • API使用簡單,門檻低

    • 功能強大,預置了多種編解碼功能,支持多種主流協議

    • 性能高

 

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