【NIO系列】——之Netty 原 薦

這是NIO系列的第四篇,歡迎繼續關注:

  1. 【NIO系列】——之TCP探祕
  2. 【NIO系列】——之IO模型
  3. 【NIO系列】——之Reactor模型

如果你看過前面三篇文章,我們從最低層來分解NIO底層原理和使用方式,幫忙我們理解了NIO是什麼,解決了什麼問題,以及又有那些不足。

原則上NIO的出現,已經提升和加快了網絡IO的處理方式,但它只能幫忙我們解決了IO層次的讀寫問題,在軟件層次上我們需要更好的編程架構模型,來解決擴展性以及高併發的問題。Netty正是用來解決這些問題的,這一篇我們將詳細介紹Netty的知識點。

 

一、netty是什麼

1、是什麼

我們採用官方的話來說:

Netty是一個高性能、異步事件驅動的網絡應用框架。 基於Netty,可以快速的開發和部署高性能、 高可用的網絡服務端和客戶端應用。

簡單來說,它是一個網絡應用框架,幫你解決面向網絡開發中的三個問題:

  • 面向網絡IO的讀寫,如TCP的socket連接

  • 應用層協議編解碼,如HTTP協議

  • 高併發架構

那麼他有哪些優點?

  • Fast —性能強悍,高併發,低延遲

  • Easy —高擴展,API使用簡單,開發門檻低

  • Non-blocking — 無阻塞,支持NIO

 

2、有何不同

對於我們用慣了web容器的人來說,第一個疑惑就是netty究竟能幹什麼,爲何要用它。既然Netty是一個網絡應用框架,這些明明tomcat也能幫我們解決了,爲何還要用Netty?

netty和tomcat的不同,主要在以下幾點:

  • tomcat是一個「HTTP Server」,更準確的說是一個「Servlet/JSP」應用的容器,它主要解決的是 HTTP 協議層面的傳輸和訪問。

  • HTTP是一種應用層協議,應用層的協議除了HTTP以外,還有面向郵局協議的POP和IMAP,以及FTP、LDAP、SSH、TLS/SSL等其他協議。

  • Netty 不僅可以支持HTTP,還可以支持FTP、LDAP、SSH、TLS/SSL等應用層多數的協議,另外還支持自定義的應用層協議,只要你有這方面的需求,它足夠靈活。

  • Netty 雖然按歸類上算屬於OSI的第七層【應用層】,但它的存在是幫你支持第三層【傳輸層】的問題,比如面向TCP,面向UDP,以及SCTP網絡協議的開發,它都能很好的支持。所以他可以稱爲一個通信組件。

  • 原則上,Tomcat的網絡通信組件也可以採用Netty,但servlet3.0之前完全是同步阻塞模型,tomcat要遵循servlet規範所以不能最大發揮NIO的特性,而Netty不用遵循servlet規範,可以最大化發揮NIO的特性,性能更高一些。

 

二、爲何要用Netty

Netty的三大特性(fast,easy,no-blocking)可以詳細分解爲以下特性:

  • 支持NIO,異步編程

  • 性能強悍,高併發,低延遲,更少的資源佔用和內存使用率帶來更快的性能支撐,NIO領域最強

  • 成熟、穩定,你想到的一切tcp 問題,都已經解決,特別是NIO bug,以及完美解決了。

  • 不僅僅支持http,支持多種應用協議和網絡協議,如TCP/UDP/UDT/SCTP/FTP/SMTP等。

  • 功能強大,預製了多種編解碼處理器,支持多種主流應用協議

  • API使用簡單,開發門檻低

 

1.Fast

爲何快,可以參考上篇文章(【NIO系列】——之Reactor模型)介紹的Reactor IO模型。

 

Netty正是基於NIO實現了這種Reactor模型,Boss線程用來專門處理連接的建立,SubReactor專門用來處理IO的讀寫以及任務的處理。這種線程模型在充分利用CPU性能的情況下支撐大量的併發連接。

 

具體效果如何,我們看下測試數據:

以上是在techempower獲取的關於webFrame的純文本響應測試,可以看到netty在響應速度方面,排名第二,雖然近幾年高性能的web框架不斷的挑戰,netty排名有所下降,但依然還能保持前列位置。

 

以上是在dubbo的官方測試案例中,給定協議序列化TPS對比,可以看到採用netty的dubbo,在tps方面名列前茅。

 

2.更少的內存使用

Netty因爲要面對大量的網絡數據包處理,所以會有大量的網絡對象創建和銷燬,對JVM來說有很大的壓力。

Netty主要採用兩種方案來緩解JVM的壓力:

  • ByteBufAllocator 對象池。池化ByteBuf實例以提高性能並最小化內存碎片,後者每次調用時都返回一個新的實例。

  • 零拷貝。支持DirectBuffer的使用,通過JVM的本地調用分配內存,這可避免每次調用本地I / O操作之前(或之後)將緩衝區的內容複製到(或從)中間緩衝區。

以下是twitter對應Netty4的使用對象池應用壓測:

 

3.Easy,快速開發

我們知道牽扯到網絡上的編程會比較複雜,除了有一堆需要設置的TPC參數以外,還有一堆IO讀寫的問題要處理,同時爲了提高併發能力,還採用多線程,這就又帶來了線程安全的問題,往往給開發者帶來了很大的挑戰和複雜度。

而Netty 正是針對這些難點統一做了簡易化的封裝,除了讓API更加簡單易用以外,比如:TCP服務器的配置啓動,數據包buffByte的讀寫等。另外基於事件模式,對網絡事件進行串行處理,在保證了高效的同時,又降低了編程的複雜度。

爲何串行化執行會提升netty的性能:

  • 串行無鎖化設計,在IO線程內部進行串行操作,避免多線程競爭導致的性能下降。

  • 通過調整NIO線程池的線程參數,可以同時啓動多個串行化的線程並行運行,這種局部無鎖化的串行線程設計相比一個隊列-多個工作線程模型性能更優

  • 減少上下文切換,以及狀態數據的同步

 

以下 是ChannelPipeline用來處理網絡事件的職責鏈,負責管理和執行ChannelHandler。網絡事件以事件流的形式在ChannelPipeline中流轉。支持插拔模式,擴展性也很強。

 

4.可靠穩定

在網絡方面,應用常常要面對複雜的網絡環境,比如網絡環境差,常會出現一些網絡閃斷,單通以及網絡阻塞等等一系列問題。另外作爲基礎的通信組件,還需要考慮健壯性的問題,因爲一旦出現bug,則會導致依賴的大量業務中斷。

Netty 在版本迭代中不斷的加入一些可靠性特性來滿足用戶日益高漲的可靠性與健壯性需求。比如NIO的select空轉bug,比如TCP的斷線重連,keep-alive檢測等問題,Netty已經幫你解決了。

特別是Netty在推出4.0之後,已經被各家大廠採納爲通信組件,這是對其可靠性驗證,也是社區對其穩定性的認可。以下是在使用netty的一些公司,更多參考netty.Adopters

 

三、如何用

上面介紹了這麼多Netty如何厲害,到編碼階段不會出現一堆代碼來要處理吧。

我們來一個簡單的TCPServer來進行示例.

 

需求:

Client:接收用戶輸入的內容,併發送給Server,同時收並顯示Server返回的內容

Server:監聽8088端口,接收並顯示Client發送的內容,並給Client相應的回覆

 

代碼:

      // step 1 設置boss 和 work
      EventLoopGroup bossGroup = new NioEventLoopGroup();
      EventLoopGroup workerGroup = new NioEventLoopGroup();
      try {
          // step 2 服務啓動輔助類
          ServerBootstrap b = new ServerBootstrap(); 
          // 設置線程池
          b.group(bossGroup, workerGroup)
              // step3 設置channel
              .channel(NioServerSocketChannel.class) 
              // setp4 設置channel hanlder
              .childHandler(new ChildChannelHandler())
              //設置發送和接受緩衝區大小
              .option(ChannelOption.SO_SNDBUF,256)
              .option(ChannelOption.SO_RCVBUF,256)
              // 小封包自動連接
              .option(ChannelOption.TCP_NODELAY,true)
              .childOption(ChannelOption.SO_KEEPALIVE, true)
              .handler(new LoggingHandler(LogLevel.INFO));
          // step 7 Bind 監聽端口.
          ChannelFuture f = b.bind(PORT).sync(); 
          logger.info("tcp server start success... ");
          f.channel().closeFuture().sync();
      } finally {
      }

ChildChannelHander 擴展Pipeline的執行策略:

protected void initChannel(SocketChannel channel) throws Exception {
      ChannelPipeline pipeline = channel.pipeline();
      pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
      pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
      pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
      pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
      pipeline.addLast(new TcpHelloServerHandler());
​
  }

 

通過以上代碼,我們大概需要通過7個步驟,就能創建一個TCP的Server服務器了,看似7步較多,其實看代碼量來說,已經很少了。

通過以上的執行流程圖,我們可以看到:

  • ServerBootstrap是服務啓動(引導)輔助類,採用無參構造器(Builder模式)提供一系統方法用於設置啓動相關的參數

  • EventLoopGroup:Netty的Reactor線程池,負責維護一組EventLoop的調度工作,通常是bossEventLoop用來維護連接,subEventLoop來維護所有已註冊的Channel的I/O操作處理用戶自定義的Task和定時任務的Task

  • Channel:對Java原生的NIO類庫進行了封裝對一般用戶而言,不需要關心底層實現細節和工作原理,只需要指定具體使用哪種Channel,用以連接IO設備(socket)的紐帶,提供與IO設備異步I/O操作的支持(如讀、寫、連接和綁定)

  • ChildChannelHandler,用來配置ChannelPipeline的執行策略。用來擴展的關鍵接口,用戶可以完成大多數的功能定製,如消息編解碼、心跳、安全認證、TSL/SSL認證、流量控制等。

 

四、最後

以上我們通過Netty 是什麼、怎麼用、爲何用三個方面闡述了Netty的優勢以及基本的介紹,想必能夠對Netty做一個基本的瞭解。後續我們將對Netty高性能架構,以及多線程高效編程方面,結合源碼,會有更多的剖析。我們不僅僅關注源碼是如何實現了,還會結合整個架構,來分析設計理念,以及探討魯棒性很強的代碼的編寫方式。若有興趣,歡迎持續關注。

 

更多架構知識,歡迎關注我的公衆號,大碼候(cool_wier)

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