Netty總結

一、IO模型

  1. 阻塞IO(bloking IO):優點:能夠及時返回數據,無延遲;方便調試;缺點:需要付出等待的代價
  2. 非阻塞IO(non-blocking IO):優點:不需要等待任務 ,而是把時間花費到其它任務上,也就是這個當前線程同時處理多個任務;缺點:導致任務完成的響應延遲增大,因爲每隔一段時間纔去執行詢問的動作,但是任務可能在兩個詢問動作的時間間隔內完成,這會導致整體數據吞吐量的降低
  3. 多路複用IO(multiplexing IO):優點:能夠同時處理多個連接,系統開銷小,系統不需要創建新的額外進程或者線程,也不需要維護這些進程和線程的運行,降低了系統的維護工作量,節省了系統資源;      缺點:如果處理的連結數目不高的話,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大。(因爲阻塞可以保證沒有延遲,但是多路複用是處理先存在的數據,所以數據的順序則不管,導致處理一個完整的任務的時間上有延遲)
  4. 信號驅動式IO(signal-driven IO)
  5. 異步IO(asynchronous IO)

    前四種是同步的,最後一種是異步的

二、阻塞和非阻塞,同步和異步

  • 同步與異步:描述的是用戶線程與內核的交互方式,同步指用戶線程發起IO請求後需要等待或者輪詢內核IO操作完成後才能繼續執行;而異步是指用戶線程發起IO請求後仍然繼續執行,當內核IO操作完成後會通知用戶線程,或者調用用戶線程註冊的回調函數。
  • 阻塞與非阻塞:描述是用戶線程調用內核IO操作的方式,阻塞是指IO操作需要徹底完成後才返回到用戶空間;而非阻塞是指IO操作被調用後立即返回給用戶一個狀態值,無需等到IO操作徹底完成

以銀行取款爲例: 

  • 同步 : 自己親自出馬持銀行卡到銀行取錢(使用同步IO時,Java自己處理IO讀寫);
  • 異步 : 委託一小弟拿銀行卡到銀行取錢,然後給你(使用異步IO時,Java將IO讀寫委託給OS處理,需要將數據緩衝區地址和大小傳給OS(銀行卡和密碼),OS需要支持異步IO操作API);
  • 阻塞 : ATM排隊取款,你只能等待(使用阻塞IO時,Java調用會一直阻塞到讀寫完成才返回);
  • 非阻塞 : 櫃檯取款,取個號,然後坐在椅子上做其它事,等號廣播會通知你辦理,沒到號你就不能去,你可以不斷問大堂經理排到了沒有,大堂經理如果說還沒到你就不能去(使用非阻塞IO時,如果不能讀寫Java調用會馬上返回,當IO事件分發器會通知可讀寫時再繼續進行讀寫,不斷循環直到讀寫完成)

三、BIO、NIO、AIO

  • BIO(同步阻塞IO):一個連接一個線程,客戶端有連接請求時服務器端就需要啓動一個線程進行處理。線程開銷大。
  • NIO(同步非阻塞IO):一個請求一個線程,但客戶端發送的連接請求都會註冊到多路複用器上,多路複用器輪詢到連接有I/O請求時才啓動一個線程進行處理
  • AIO(異步非阻塞IO,NIO.2):一個有效請求一個線程,客戶端的I/O請求都是由OS先完成了再通知服務器應用去啓動線程進行處理
  • BIO是面向流的,流是單向阻塞的。NIO是面向緩衝區,非阻塞的,NIO的channel是雙向的

BIO、NIO、AIO適用場景分析:

  • BIO方式適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,JDK1.4以前的唯一選擇,但程序直觀簡單易理解。
  • NIO方式適用於連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,併發侷限於應用中,編程比較複雜,JDK1.4開始支持。
  • AIO方式使用於連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用OS參與併發操作,編程比較複雜,JDK7開始支持。

四、NIO介紹

    4.1 NIO的特點

      事件驅動模型、單線程處理多任務、非阻塞I/O,基於block的傳輸比基於流的傳輸更高效、更高級的IO函數zero-copy、IO多路複用大大提高了Java網絡應用的可伸縮性和實用性。基於Reactor線程模型

    4.2 NIO編程

  1. Selector(選擇器、多路複用器:Selector會不斷的輪詢註冊在其上的通道(Channel),如果某個通道發生了讀寫操作,這個通道就處於就緒狀態,會被Selector輪詢出來,然後通過SelectionKey可以取得就緒的Channel集合,從而進行後續的IO操作。有多種的狀態位(SelectionKey.OP_CONNECT,SelectionKey.OP_ACCEPT,SelectionKey.OP_READ,SelectionKey.OP_WRITE)。一個多路複用器(Selector)可以負責成千上萬的通道(Channel),沒有上限。這也是JDK使用了epoll代替傳統的select實現,獲得連接句柄(客戶端)沒有限制。
  2. Channel(管道、通道):通道是雙向的,而流只能在一個方向上移動(一個流必須是InputStream或者OutputStream的子類),而通道可以用於讀、寫或者二者同時進行。通道分爲兩大類:一類是用於網絡讀寫的SelectableChannel,另一類是用於文件操作的FileChannel,我們使用的SocketChannel和ServerSocketChannel都是SelectableChannel的子類。
  3. Buffer(緩衝區):在NIO類庫中,所有的數據都是用緩衝區處理的(讀寫)。 緩衝區實質上是一個數組,通常它是一個字節數組(ByteBuffer),也可以使用其他類型的數組。這個數組爲緩衝區提供了訪問數據的讀寫等操作屬性,如位置、容量、上限等概念。Buffer類型:最常使用的是ByteBuffer,實際上每一種java基本類型都對應了一種緩存區(除了Boolean類型)。①ByteBuffer②CharBuffer③ShortBuffer④IntBuffer⑤LongBuffer⑥FloatBuffer⑦DoubleBuffer。    當向 Buffer 寫入數據時,Buffer 會記錄下寫了多少數據。一旦要讀取數據,需要通過 flip() 方法將 Buffer 從寫模式切換到讀模式。在讀模式下,可以讀取之前寫入到 Buffer 的所有數據。一旦讀完了所有的數據,就需要清空緩衝區,讓它可以再次被寫入。有兩種方式能清空緩衝區:調用 clear() 或 compact() 方法。clear() 方法會清空整個緩衝區。compact() 方法只會清除已經讀過的數據。任何未讀的數據都被移到緩衝區的起始處,新寫入的數據將放到緩衝區未讀數據的後面

    案例詳情訪問https://blog.csdn.net/qq_33314107/article/details/80353427

五、select、poll和epoll區別

     1、支持一個進程所能打開的最大連接數

           

    2、FD(套接字數量)劇增後帶來的IO效率問題

           

   3、消息傳遞方式

         

綜上,表面上看epoll的性能最好,但是在連接數少並且連接都十分活躍的情況下,select和poll的性能可能比epoll好,畢竟epoll的通知機制需要很多函數回調。select低效是因爲每次它都需要輪詢。但低效也是相對的,視情況而定,也可通過良好的設計改善。

詳情訪問https://blog.csdn.net/qq_33314107/article/details/81712589

六、Netty和Mina的比較

  1. Netty的文檔更清晰,很多Mina的特性在Netty裏都有;
  2. Netty更新週期更短,新版本的發佈比較快;
  3. 它們的架構差別不大,Mina靠apache生存,而Netty靠jboss,和jboss的結合度非常高,Netty有對google protocal buf的支持,有更完整的ioc容器支持(spring,guice,jbossmc和osgi);
  4. Netty比Mina使用起來更簡單,Netty裏你可以自定義的處理upstream events或/和downstream events,可以使用decoder和encoder來解碼和編碼發送內容;
  5. Netty和Mina在處理UDP時有一些不同,Netty將UDP無連接的特性暴露出來;而Mina對UDP進行了高級層次的抽象,可以把UDP當成”面向連接”的協議,而要Netty做到這一點比較困難
  6. Mina將內核和一些特性的聯繫過於緊密,使得用戶在不需要這些特性的時候無法脫離,相比下性能會有所下降,Netty解決了這個設計問題;

七、Netty介紹

  • 一個高性能、異步事件驅動的NIO框架,它提供了對TCP、UDP和文件傳輸的支持
  • 使用更高效的socket底層,對epoll空輪詢引起的cpu佔用飆升在內部進行了處理,避免了直接使用NIO的陷阱,簡化了NIO的處理方式。
  • 採用多種decoder/encoder 支持,對TCP粘包/分包進行自動化處理
  • 可使用接受/處理線程池,提高連接效率,對重連、心跳檢測的簡單支持
  • 可配置IO線程數、TCP參數, TCP接收和發送緩衝區使用直接內存代替堆內存,通過內存池的方式循環利用ByteBuf
  • 通過引用計數器及時申請釋放不再引用的對象,降低了GC頻率
  • 使用單線程串行化的方式,高效的Reactor線程模型
  • 大量使用了volitale、使用了CAS和原子類、線程安全類的使用、讀寫鎖的使用

八、Netty線程模型

  • Netty通過Reactor模型基於多路複用器接收並處理用戶請求,內部實現了兩個線程池,boss線程池和work線程池,其中boss線程池的線程負責處理請求的accept事件,當接收到accept事件的請求時,把對應的socket封裝到一個NioSocketChannel中,並交給work線程池,其中work線程池負責請求的read和write事件,由對應的Handler處理。

  • 單線程模型:所有I/O操作都由一個線程完成,即多路複用、事件分發和處理都是在一個Reactor線程上完成的。既要接收客戶端的連接請求,向服務端發起連接,又要發送/讀取請求或應答/響應消息。一個NIO 線程同時處理成百上千的鏈路,性能上無法支撐,速度慢,若線程進入死循環,整個程序不可用,對於高負載、大併發的應用場景不合適。

  • 多線程模型:有一個NIO 線程(Acceptor) 只負責監聽服務端,接收客戶端的TCP 連接請求;NIO 線程池負責網絡IO 的操作,即消息的讀取、解碼、編碼和發送;1 個NIO 線程可以同時處理N 條鏈路,但是1 個鏈路只對應1 個NIO 線程,這是爲了防止發生併發操作問題。但在併發百萬客戶端連接或需要安全認證時,一個Acceptor 線程可能會存在性能不足問題。

  • 主從多線程模型:Acceptor 線程用於綁定監聽端口,接收客戶端連接,將SocketChannel 從主線程池的Reactor 線程的多路複用器上移除,重新註冊到Sub 線程池的線程上,用於處理I/O 的讀寫等操作,從而保證mainReactor只負責接入認證、握手等操作;

   詳情訪問https://blog.csdn.net/cj2580/article/details/78124780

九、TCP 粘包/拆包

  TCP是以流的方式來處理數據,一個完整的包可能會被TCP拆分成多個包進行發送,也可能把小的封裝成一個大的數據包發送

  產生的原因:

  1. 應用程序寫入的字節大小大於套接字發送緩衝區的大小,會發生拆包現象,而應用程序寫入數據小於套接字緩衝區大小,網卡將應用多次寫入的數據發送到網絡上,這將會發生粘包現象;
  2. 進行MSS大小的TCP分段,當TCP報文長度-TCP頭部長度>最大報文段長度MSS的時候將發生拆包
  3. 以太網幀的payload(淨荷)大於最大傳輸單元MTU(1500字節)進行ip分片

 解決方案:

  1. 消息定長;例如:每個報文的大小固定爲200個字節,如果不夠,空位補空格;對應Netty中的定長類 :FixedLengthFrameDecoder
  2. 在包尾都增加特殊字符進行分割;例如:加回車、加換行、FTP協議等;對應Netty中的類:1)自定義分隔符類 :DelimiterBasedFrameDecoder   2)行分隔符類:LineBasedFrameDecoder
  3. 將消息分爲消息頭和消息體;例:在消息頭中包含表示消息總長度的字段,然後進行業務邏輯的處理。對應Netty中的基於消息頭指定消息長度類:LengthFieldBasedFrameDecoder
  4. 更復雜的應用層協議;解決TCP粘包/分包問題的實例請閱讀我的下一篇博文:解決TCP粘包/分包的實例

十、序列化

   10.1 概念

      序列化(編碼)將對象序列化爲二進制形式(字節數組),主要用於網絡傳輸、數據持久化等;反序列化(解碼)是將將從網絡、磁盤等讀取的字節數組還原成原始對象,主要用於網絡傳輸對象的解碼,以便完成遠程調用

   10.2  影響序列化性能的關鍵因素

  1. 序列化後的碼流大小(網絡帶寬的佔用);
  2. 序列化的性能(CPU資源佔用);
  3. 是否支持跨語言(異構系統的對接和開發語言切換)

   10.3 Serializable序列化問題

  1. 序列化 ID 不同,無法相互序列化和反序列化
  2. 序列化並不保存靜態變量
  3. Transient 關鍵字能阻止該變量被序列化到文件
  4. 子類實現 Serializable 接口,父類不實現,父類的字段數據將不被序列化
  5. 當寫入文件的爲同一對象時,並不會再將對象的內容進行存儲,而只是再次存儲一份引用
  6. 在序列化過程中,虛擬機會試圖調用對象類裏的 writeObject 和 readObject 方法,進行用戶自定義的序列化和反序列化,如果沒有這樣的方法,則默認調用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程,比如可以在序列化的過程中動態改變序列化的數值。基於這個原理,可以在實際應用中得到使用,用於敏感字段的加密工作

   10.4 方式

  1. Java默認提供的序列化:必須實現Serializable接口。無法跨語言、序列化後的碼流太大、序列化的性能差
  2. XML,優點:人機可讀性好,可指定元素或特性的名稱。缺點:序列化數據只包含數據本身以及類的結構,不包括類型標識和程序集信息;只能序列化公共屬性和字段;不能序列化方法;文件龐大,文件格式複雜,傳輸佔帶寬。適用場景:當做配置文件存儲數據,實時數據轉換
  3. JSON:Json序列化一般會使用jackson包,通過ObjectMapper類來進行一些操作,比如將對象轉化爲byte數組或者將json串轉化爲對象,優點:兼容性高、數據格式比較簡單,易於讀寫、序列化後數據較小,可擴展性好,兼容性好、與XML相比,其協議比較簡單,解析速度比較快。缺點:數據的描述性比XML差、不適合性能要求爲ms級別的情況、額外空間開銷比較大。適用場景(可替代XML):跨防火牆訪問、可調式性要求高、基於Web browser的Ajax請求、傳輸數據量相對小,實時性要求相對低(例如秒級別)的服務
  4. Fastjson,阿里巴巴開發的一個性能很好的Java 語言實現的 Json解析器和生成器。優點:接口簡單易用、目前java語言中最快的json庫。缺點:過於注重快,而偏離了“標準”及功能性、代碼質量不高,文檔不全。適用場景:協議交互、Web輸出、Android客戶端
  5. Thrift,不僅是序列化協議,還是一個RPC框架。優點:序列化後的體積小, 速度快、支持多種語言和豐富的數據類型、對於數據字段的增刪具有較強的兼容性、支持二進制壓縮編碼。缺點:使用者較少、跨防火牆訪問時,不安全、不具有可讀性,調試代碼時相對困難、不能與其他傳輸層協議共同使用(例如HTTP)、無法支持向持久層直接讀寫數據,即不適合做數據持久化序列化協議。適用場景:分佈式系統的RPC解決方案
  6. Avro,Hadoop的一個子項目,解決了JSON的冗長和沒有IDL的問題。優點:支持豐富的數據類型、簡單的動態語言結合功能、具有自我描述屬性、提高了數據解析速度、快速可壓縮的二進制數據形式、可以實現遠程過程調用RPC、支持跨編程語言實現。缺點:對於習慣於靜態類型語言的用戶不直觀。適用場景:在Hadoop中做Hive、Pig和MapReduce的持久化數據格式。
  7. Protobuf,將數據結構以.proto文件進行描述,通過代碼生成工具可以生成對應數據結構的POJO對象和Protobuf相關的方法和屬性。優點:序列化後碼流小,性能高、結構化數據存儲格式(XML JSON等)、通過標識字段的順序,可以實現協議的前向兼容、結構化的文檔更容易管理和維護。缺點:需要依賴於工具生成代碼、支持的語言相對較少,官方只支持Java 、C++ 、python。適用場景:對性能要求高的RPC調用、具有良好的跨防火牆的訪問屬性、適合應用層對象的持久化
  8. 其它

  • protostuff 基於protobuf協議,但不需要配置proto文件,直接導包即可
  • Jboss marshaling 可以直接序列化java類, 無須實java.io.Serializable接口
  • Message pack 一個高效的二進制序列化格式
  • Hessian 採用二進制協議的輕量級remoting onhttp工具
  • kryo 基於protobuf協議,只支持java語言,需要註冊(Registration),然後序列化(Output),反序列化(Input)

十一、Netty的zero-copy(零拷貝)

  1. Netty的接收和發送ByteBuffer採用Direct Buffer,使用堆外直接內存進行Socket讀寫,不需要進行字節緩衝區的二次拷貝。堆內存多了一次內存拷貝,JVM會將堆內存Buffer拷貝一份到直接內存中,然後才寫入Socket中。ByteBuffer由ChannelConfig分配,而ChannelConfig創建ByteBufAllocator默認使用Direct Buffer
  2. CompositeByteBuf 類可以將多個 ByteBuf 合併爲一個邏輯上的 ByteBuf, 避免了傳統通過內存拷貝的方式將幾個小Buffer合併成一個大的Buffer。addComponents方法將 header 與 body 合併爲一個邏輯上的 ByteBuf, 這兩個 ByteBuf 在CompositeByteBuf 內部都是單獨存在的, CompositeByteBuf 只是邏輯上是一個整體
  3. 通過 FileRegion 包裝的FileChannel.tranferTo方法 實現文件傳輸, 可以直接將文件緩衝區的數據發送到目標 Channel,避免了傳統通過循環write方式導致的內存拷貝問題。
  4. 通過 wrap方法, 我們可以將 byte[] 數組、ByteBuf、ByteBuffer等包裝成一個 Netty ByteBuf 對象, 進而避免了拷貝操作
  5. Selector BUG:若Selector的輪詢結果爲空,也沒有wakeup或新消息處理,則發生空輪詢,CPU使用率100%
  6. Netty的解決辦法:對Selector的select操作週期進行統計,每完成一次空的select操作進行一次計數,若在某個週期內連續發生N次空輪詢,則觸發了epoll死循環bug。重建Selector,判斷是否是其他線程發起的重建請求,若不是則將原SocketChannel從舊的Selector上去除註冊,重新註冊到新的Selector上,並將原來的Selector關閉

十二、Netty的高性能表現

  1.  心跳,對服務端:會定時清除閒置會話inactive(netty5),對客戶端:用來檢測會話是否斷開,是否重來,檢測網絡延遲,其中idleStateHandler類 用來檢測會話狀態
  2. 串行無鎖化設計,即消息的處理儘可能在同一個線程內完成,期間不進行線程切換,這樣就避免了多線程競爭和同步鎖。表面上看,串行化設計似乎CPU利用率不高,併發程度不夠。但是,通過調整NIO線程池的線程參數,可以同時啓動多個串行化的線程並行運行,這種局部無鎖化的串行線程設計相比一個隊列-多個工作線程模型性能更優。
  3. 可靠性,鏈路有效性檢測:鏈路空閒檢測機制,讀/寫空閒超時機制;內存保護機制:通過內存池重用ByteBuf;ByteBuf的解碼保護;優雅停機:不再接收新消息、退出前的預處理操作、資源的釋放操作。
  4. Netty安全性:支持的安全協議:SSL V2和V3,TLS,SSL單向認證、雙向認證和第三方CA認證。
  5. 高效併發編程的體現:volatile的大量、正確使用;CAS和原子類的廣泛使用;線程安全容器的使用;通過讀寫鎖提升併發性能。IO通信性能三原則:傳輸(AIO)、協議(Http)、線程(主從多線程)
  6. 流量整型的作用(變壓器):防止由於上下游網元性能不均衡導致下游網元被壓垮,業務流中斷;防止由於通信模塊接受消息過快,後端業務線程處理不及時導致撐死問題。
  7. TCP參數配置:SO_RCVBUF和SO_SNDBUF:通常建議值爲128K或者256K;SO_TCPNODELAY:NAGLE算法通過將緩衝區內的小封包自動相連,組成較大的封包,阻止大量小封包的發送阻塞網絡,從而提高網絡應用效率。但是對於時延敏感的應用場景需要關閉該優化算法;

十三、Netty案例

    詳情訪問https://www.cnblogs.com/myJavaEE/p/6793332.html

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