一篇文章,讀懂 Netty 的高性能架構之道


原文

Netty是一個高性能、異步事件驅動的NIO框架,它提供了對TCP、UDP和文件傳輸的支持,作為一個異步NIO框架,Netty的所有IO操作都是異步非阻塞的,通過Future-Listener機制,用戶可以方便的主動獲取或者通過通知機制獲得IO操作結果。

Netty

作為當前最流行的NIO框架,Netty在互聯網領域、大數據分布式計算領域、游戲行業、通信行業等獲得了廣泛的應用,一些業界著名的開源組件也基於Netty的NIO框架構建。

為什么選擇Netty

Netty是業界最流行的NIO框架之一,它的健壯性、功能、性能、可定制性和可擴展性在同類框架中都是首屈一指的,它已經得到成百上千的商用項目驗證,例如Hadoop的RPC框架avro使用Netty作為底層通信框架;很多其他業界主流的RPC框架,也使用Netty來構建高性能的異步通信能力。

通過對Netty的分析,我們將它的優點總結如下:

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

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

  • 定制能力強,可以通過ChannelHandler對通信框架進行靈活地擴展;

  • 性能高,通過與其他業界主流的NIO框架對比,Netty的綜合性能最優;

  • 成熟、穩定,Netty修復了已經發現的所有JDK NIO BUG,業務開發人員不需要再為NIO的BUG而煩惱;

  • 社區活躍,版本迭代周期短,發現的BUG可以被及時修復,同時,更多的新功能會加入;

  • 經歷了大規模的商業應用考驗,質量得到驗證。在互聯網、大數據、網絡游戲、企業應用、電信軟件等眾多行業得到成功商用,證明了它已經完全能夠滿足不同行業的商業應用了。

Netty架構分析

Netty 采用了比較典型的三層網絡架構進行設計,邏輯架構圖如下所示:

第一層:Reactor 通信調度層,它由一系列輔助類完成,包括 Reactor 線程 NioEventLoop 以及其父類、NioSocketChannel/NioServerSocketChannel 以及其父 類、ByteBuffer 以及由其衍生出來的各種 Buffer、Unsafe 以及其衍生出的各種內 部類等。該層的主要職責就是監聽網絡的讀寫和連接操作,負責將網絡層的數據 讀取到內存緩沖區中,然后觸發各種網絡事件,例如連接創建、連接激活、讀事 件、寫事件等等,將這些事件觸發到 PipeLine 中,由 PipeLine 充當的職責鏈來 進行后續的處理。

第二層:職責鏈 PipeLine,它負責事件在職責鏈中的有序傳播,同時負責動態的 編排職責鏈,職責鏈可以選擇監聽和處理自己關心的事件,它可以攔截處理和向 后/向前傳播事件,不同的應用的 Handler 節點的功能也不同,通常情況下,往往 會開發編解碼 Hanlder 用於消息的編解碼,它可以將外部的協議消息轉換成內部 的 POJO 對象,這樣上層業務側只需要關心處理業務邏輯即可,不需要感知底層 的協議差異和線程模型差異,實現了架構層面的分層隔離。

第三層:業務邏輯處理層,可以分為兩類:

  1. 純粹的業務邏輯 處理,例如訂單處理。

  2. 應用層協議管理,例如HTTP協議、FTP協議等。

接下來,我從影響通信性能的三個方面(I/O模型、線程調度模型、序列化方式)來談談Netty的架構。

I/O模型

傳統同步阻塞I/O模式如下圖所示:

它的弊端有很多:

  • 性能問題:一連接一線程模型導致服務端的並發接入數和系統吞吐量受到極大限制;

  • 可靠性問題:由於I/O操作采用同步阻塞模式,當網絡擁塞或者通信對端處理緩慢會導致I/O線程被掛住,阻塞時間無法預測;

  • 可維護性問題:I/O線程數無法有效控制、資源無法有效共享(多線程並發問題),系統可維護性差;

幾種I/O模型的功能和特性對比:

Netty的I/O模型基於非阻塞I/O實現,底層依賴的是JDK NIO框架的Selector。

Selector提供選擇已經就緒的任務的能力。簡單來講,Selector會不斷地輪詢注冊在其上的Channel,如果某個Channel上面有新的TCP連接接入、讀和寫事件,這個Channel就處於就緒狀態,會被Selector輪詢出來,然后通過SelectionKey可以獲取就緒Channel的集合,進行后續的I/O操作。

一個多路復用器Selector可以同時輪詢多個Channel,由於JDK1.5_update10版本(+)使用了epoll()代替傳統的select實現,所以它並沒有最大連接句柄1024/2048的限制。這也就意味着只需要一個線程負責Selector的輪詢,就可以接入成千上萬的客戶端,這確實是個非常巨大的技術進步。

使用非阻塞I/O模型之后,Netty解決了傳統同步阻塞I/O帶來的性能、吞吐量和可靠性問題。

線程調度模型

常用的Reactor線程模型有三種,分別如下:

  • Reactor單線程模型:Reactor單線程模型,指的是所有的I/O操作都在同一個NIO線程上面完成。對於一些小容量應用場景,可以使用單線程模型。

  • Reactor多線程模型:Rector多線程模型與單線程模型最大的區別就是有一組NIO線程處理I/O操作。主要用於高並發、大業務量場景。

  • 主從Reactor多線程模型:主從Reactor線程模型的特點是服務端用於接收客戶端連接的不再是個1個單獨的NIO線程,而是一個獨立的NIO線程池。利用主從NIO線程模型,可以解決1個服務端監聽線程無法有效處理所有客戶端連接的性能不足問題。

事實上,Netty的線程模型並非固定不變,通過在啟動輔助類中創建不同的EventLoopGroup實例並通過適當的參數配置,就可以支持上述三種Reactor線程模型。

在大多數場景下,並行多線程處理可以提升系統的並發性能。但是,如果對於共享資源的並發訪問處理不當,會帶來嚴重的鎖競爭,這最終會導致性能的下降。為了盡可能的避免鎖競爭帶來的性能損耗,可以通過串行化設計,即消息的處理盡可能在同一個線程內完成,期間不進行線程切換,這樣就避免了多線程競爭和同步鎖。

為了盡可能提升性能,Netty采用了串行無鎖化設計,在I/O線程內部進行串行操作,避免多線程競爭導致的性能下降。表面上看,串行化設計似乎CPU利用率不高,並發程度不夠。但是,通過調整NIO線程池的線程參數,可以同時啟動多個串行化的線程並行運行,這種局部無鎖化的串行線程設計相比一個隊列-多個工作線程模型性能更優。

序列化方式

影響序列化性能的關鍵因素總結如下:

  • 序列化后的碼流大小(網絡帶寬占用)

  • 序列化&反序列化的性能(CPU資源占用)

  • 並發調用的性能表現:穩定性、線性增長、偶現的時延毛刺等

對Java序列化和二進制編碼分別進行性能測試,編碼100萬次,測試結果表明:Java序列化的性能只有二進制編碼的6.17%左右。

Netty默認提供了對Google Protobuf的支持,通過擴展Netty的編解碼接口,用戶可以實現其它的高性能序列化框架,例如Thrift的壓縮二進制編解碼框架。

不同的應用場景對序列化框架的需求也不同,對於高性能應用場景Netty默認提供了Google的Protobuf二進制序列化框架,如果用戶對其它二進制序列化框架有需求,也可以基於Netty提供的編解碼框架擴展實現。

Netty架構剖析之可靠性

Netty面臨的可靠性挑戰:

  • 作為RPC框架的基礎網絡通信框架,一旦故障將導致無法進行遠程服務(接口)調用。

  • 作為應用層協議的基礎通信框架,一旦故障將導致應用協議棧無法正常工作。

  • 網絡環境復雜(例如手游或者推送服務的GSM/3G/WIFI網絡),故障不可避免,業務卻不能中斷。

從應用場景看,Netty是基礎的通信框架,一旦出現Bug,輕則需要重啟應用,重則可能導致整個業務中斷。它的可靠性會影響整個業務集群的數據通信和交換,在當今以分布式為主的軟件架構體系中,通信中斷就意味着整個業務中斷,分布式架構下對通信的可靠性要求非常高。

從運行環境看,Netty會面臨惡劣的網絡環境,這就要求它自身的可靠性要足夠好,平台能夠解決的可靠性問題需要由Netty自身來解決,否則會導致上層用戶關注過多的底層故障,這將降低Netty的易用性,同時增加用戶的開發和運維成本。

Netty的可靠性是如此重要,它的任何故障都可能會導致業務中斷,蒙受巨大的經濟損失。因此,Netty在版本的迭代中不斷加入新的可靠性特性來滿足用戶日益增長的高可靠和健壯性需求。

鏈路有效性檢測

Netty提供的心跳檢測機制分為三種:

  • 讀空閑,鏈路持續時間t沒有讀取到任何消息;

  • 寫空閑,鏈路持續時間t沒有發送任何消息;

  • 讀寫空閑,鏈路持續時間t沒有接收或者發送任何消息。


當網絡發生單通、連接被防火牆Hang住、長時間GC或者通信線程發生非預期異常時,會導致鏈路不可用且不易被及時發現。特別是異常發生在凌晨業務低谷期間,當早晨業務高峰期到來時,由於鏈路不可用會導致瞬間的大批量業務失敗或者超時,這將對系統的可靠性產生重大的威脅。

從技術層面看,要解決鏈路的可靠性問題,必須周期性的對鏈路進行有效性檢測。目前最流行和通用的做法就是心跳檢測。

心跳檢測機制分為三個層面:

  • TCP層面的心跳檢測,即TCP的Keep-Alive機制,它的作用域是整個TCP協議棧;

  • 協議層的心跳檢測,主要存在於長連接協議中。例如SMPP協議;

  • 應用層的心跳檢測,它主要由各業務產品通過約定方式定時給對方發送心跳消息實現。

心跳檢測的目的就是確認當前鏈路可用,對方活着並且能夠正常接收和發送消息。做為高可靠的NIO框架,Netty也提供了基於鏈路空閑的心跳檢測機制:

  • 讀空閑,鏈路持續時間t沒有讀取到任何消息;

  • 寫空閑,鏈路持續時間t沒有發送任何消息;

  • 讀寫空閑,鏈路持續時間t沒有接收或者發送任何消息。

流量整形

流量整形(Traffic Shaping)是一種主動調整流量輸出速率的措施。Netty的流量整形有兩個作用:

  • 防止由於上下游網元性能不均衡導致下游網元被壓垮,業務流程中斷;

  • 防止由於通信模塊接收消息過快,后端業務線程處理不及時導致的“撐死”問題。

流量整形的原理示意圖如下:

流量整形(Traffic Shaping)是一種主動調整流量輸出速率的措施。一個典型應用是基於下游網絡結點的TP指標來控制本地流量的輸出。流量整形與流量監管的主要區別在於,流量整形對流量監管中需要丟棄的報文進行緩存——通常是將它們放入緩沖區或隊列內,也稱流量整形(Traffic Shaping,簡稱TS)。當令牌桶有足夠的令牌時,再均勻的向外發送這些被緩存的報文。流量整形與流量監管的另一區別是,整形可能會增加延遲,而監管幾乎不引入額外的延遲。

Netty支持兩種流量整形模式:

  • 全局流量整形:全局流量整形的作用范圍是進程級的,無論你創建了多少個Channel,它的作用域針對所有的Channel。用戶可以通過參數設置:報文的接收速率、報文的發送速率、整形周期。

  • 鏈路級流量整形:單鏈路流量整形與全局流量整形的最大區別就是它以單個鏈路為作用域,可以對不同的鏈路設置不同的整形策略。

優雅停機

Netty的優雅停機三部曲:

  • 不再接收新消息

  • 退出前的預處理操作

  • 資源的釋放操作

Java的優雅停機通常通過注冊JDK的ShutdownHook來實現,當系統接收到退出指令后,首先標記系統處於退出狀態,不再接收新的消息,然后將積壓的消息處理完,最后調用資源回收接口將資源銷毀,最后各線程退出執行。

通常優雅退出需要有超時控制機制,例如30S,如果到達超時時間仍然沒有完成退出前的資源回收等操作,則由停機腳本直接調用kill -9 pid,強制退出。

在實際項目中,Netty作為高性能的異步NIO通信框架,往往用作基礎通信框架負責各種協議的接入、解析和調度等,例如在RPC和分布式服務框架中,往往會使用Netty作為內部私有協議的基礎通信框架。 
當應用進程優雅退出時,作為通信框架的Netty也需要優雅退出,主要原因如下:

  • 盡快的釋放NIO線程、句柄等資源;

  • 如果使用flush做批量消息發送,需要將積攢在發送隊列中的待發送消息發送完成;

  • 正在write或者read的消息,需要繼續處理;

  • 設置在NioEventLoop線程調度器中的定時任務,需要執行或者清理。

Netty架構剖析之安全性

Netty面臨的安全挑戰:

  • 對第三方開放

  • 作為應用層協議的基礎通信框架

安全威脅場景分析:

  • 對第三方開放的通信框架:如果使用Netty做RPC框架或者私有協議棧,RPC框架面向非授信的第三方開放,例如將內部的一些能力通過服務對外開放出去,此時就需要進行安全認證,如果開放的是公網IP,對於安全性要求非常高的一些服務,例如在線支付、訂購等,需要通過SSL/TLS進行通信。

  • 應用層協議的安全性。作為高性能、異步事件驅動的NIO框架,Netty非常適合構建上層的應用層協議。由於絕大多數應用層協議都是公有的,這意味着底層的Netty需要向上層提供通信層的安全傳輸功能。

SSL/TLS

Netty安全傳輸特性:

  • 支持SSL V2和V3

  • 支持TLS

  • 支持SSL單向認證、雙向認證和第三方CA認證。

SSL單向認證流程圖如下:

Netty通過SslHandler提供了對SSL的支持,它支持的SSL協議類型包括:SSL V2、SSL V3和TLS。

  1. 單向認證:單向認證,即客戶端只驗證服務端的合法性,服務端不驗證客戶端。

  2. 雙向認證:與單向認證不同的是服務端也需要對客戶端進行安全認證。這就意味着客戶端的自簽名證書也需要導入到服務端的數字證書倉庫中。

  3. CA認證:基於自簽名的SSL雙向認證,只要客戶端或者服務端修改了密鑰和證書,就需要重新進行簽名和證書交換,這種調試和維護工作量是非常大的。因此,在實際的商用系統中往往會使用第三方CA證書頒發機構進行簽名和驗證。我們的瀏覽器就保存了幾個常用的CA_ROOT。每次連接到網站時只要這個網站的證書是經過這些CA_ROOT簽名過的。就可以通過驗證了。

可擴展的安全特性

通過Netty的擴展特性,可以自定義安全策略:

  • IP地址黑名單機制

  • 接入認證

  • 敏感信息加密或者過濾機制

IP地址黑名單是比較常用的弱安全保護策略,它的特點就是服務端在與客戶端通信的過程中,對客戶端的IP地址進行校驗,如果發現對方IP在黑名單列表中,則拒絕與其通信,關閉鏈路。

接入認證策略非常多,通常是較強的安全認證策略,例如基於用戶名+密碼的認證,認證內容往往采用加密的方式,例如Base64+AES等。

Netty架構剖析之擴展性

通過Netty的擴展特性,可以自定義安全策略:

  • 線程模型可擴展

  • 序列化方式可擴展

  • 上層協議棧可擴展

  • 提供大量的網絡事件切面,方便用戶功能擴展

Netty的架構可擴展性設計理念如下:

  • 判斷擴展點,事先預留相關擴展接口,給用戶二次定制和擴展使用;

  • 主要功能點都基於接口編程,方便用戶定制和擴展。

精彩問答

問:據我之前了解到,Java的NIO selector底層在Windows下的實現是起兩個隨機端口互聯來監測連接或讀寫事件,在Linux上是利用管道實現的;我有遇到過這樣的需求,需要占用很多個固定端口做服務端,如果在Windows下,利用NIO框架(Mina或Netty)就有可能會造成端口沖突,這種情況有什么好的解決方案嗎?

你說的問題確實存在,Linux使用Pipe實現網絡監聽,Windows要啟動端口。目前沒有更好的辦法,建議的方式是作為服務端的端口可以規划一個范圍,然后根據節點和進程信息動態生成,如果發現端口沖突,可以在規划范圍內基於算法重新生成一個新的端口。

問:請我,我現在將Spring與Netty做了整合,使用Spring的Service開啟 Netty主線程,但是停止整個運行容器的時候,Netty的TCP Server端口不能釋放?退出處理時,有什么好的辦法釋放Netty Server端口么?

實際上,由誰拉起Netty 主線程並不重要。我們需要做的就是當應用容器退出的時候(Spring Context銷毀),在退出之前調用Netty 的優雅退出接口即可實現端口、NIO線程資源的釋放。請參考這篇文章:http://www.infoq.com/cn/articles/netty-elegant-exit-mechanism-and-principles

問:適合用Netty寫Web通信么?

Netty不是Web框架,無法解析JSP、HTML、JS等,但是它可以做Web 通信,例如可以使用Netty重寫Tomcat的HTTP/HTTPS 通信協議棧。

問:能不能講解一下Netty的串行無鎖化設計,如何在串行和並行中達到最優?

為了盡可能提升性能,Netty采用了串行無鎖化設計,在IO線程內部進行串行操作,避免多線程競爭導致的性能下降。表面上看,串行化設計似乎CPU利用率不高,並發程度不夠。但是,通過調整NIO線程池的線程參數,可以同時啟動多個串行化的線程並行運行,這種局部無鎖化的串行線程設計相比一個隊列-多個工作線程模型性能更優。Netty的NioEventLoop讀取到消息之后,直接調用ChannelPipeline的fireChannelRead(Object msg),只要用戶不主動切換線程,一直會由NioEventLoop調用到用戶的Handler,期間不進行線程切換,這種串行化處理方式避免了多線程操作導致的鎖的競爭,從性能角度看是最優的。

REFER:
Netty系列之Netty高性能之道
http://www.infoq.com/cn/articles/netty-high-performance/http://www.infoq.com/cn/articles/netty-high-performance/
談談如何使用Netty開發實現高性能的RPC服務器
http://www.cnblogs.com/jietang/p/5615681.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM