標籤:關閉 行業 linu 線程池 對象 指令 延遲 event base64
Netty是一個高效能、非同步事件驅動的NIO架構,它提供了對TCP、UDP和檔案傳輸的支援,作為一個非同步NIO架構,Netty的所有IO操作都是非同步非阻塞的,通過Future-Listener機制,使用者可以方便的主動擷取或者通過通知機制獲得IO操作結果。
作為當前最流行的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 對象,這樣上層業務側只需要關心處理商務邏輯即可,不需要感知底層 的協議差異和執行緒模式差異,實現了架構層面的分層隔離。
第三層:商務邏輯處理層,可以分為兩類:
純粹的商務邏輯 處理,例如訂單處理。
應用程式層協議管理,例如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線程池的線程參數,可以同時啟動多個序列化的線程並行運行,這種局部無鎖化的串列線程設計相比一個隊列-多個背景工作執行緒模型效能更優。
序列化方式
影響序列化效能的關鍵因素總結如下:
對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支援兩種流量整形模式:
優雅停機
Netty的優雅停機三部曲:
不再接收新訊息
退出前的預先處理操作
資源的釋放操作
Java的優雅停機通常通過註冊JDK的ShutdownHook來實現,當系統接收到退出指令後,首先標記系統處於退出狀態,不再接收新的訊息,然後將積壓的訊息處理完,最後調用資源回收介面將資源銷毀,最後各線程退出執行。
通常優雅退出需要有逾時控制機制,例如30S,如果到達逾時時間仍然沒有完成退出前的資源回收等操作,則由停機指令碼直接調用kill -9 pid,強制退出。
在實際項目中,Netty作為高效能的非同步NIO通訊架構,往往用作基礎通訊架構負責各種協議的接入、解析和調度等,例如在RPC和分布式服務架構中,往往會使用Netty作為內部私人協議的基礎通訊架構。
當應用進程優雅退出時,作為通訊架構的Netty也需要優雅退出,主要原因如下:
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。
單向認證:單向認證,即用戶端只驗證服務端的合法性,服務端不驗證用戶端。
雙向認證:與單向認證不同的是服務端也需要對用戶端進行安全認證。這就意味著用戶端的自我簽署憑證也需要匯入到服務端的數位憑證倉庫中。
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,期間不進行線程切換,這種序列化處理方式避免了多線程操作導致的鎖的競爭,從效能角度看是最優的。
讀懂Netty的高效能架構之道