Java NIO通信框架在電信領域的實踐

1. 華為電信軟件技術架構演進

1.1. 電信軟件

從廣義上看電信軟件的範圍非常廣,細分實際可以分為兩大類:系統軟件和業務應用軟件。

系統軟件包括路由器底層的信令機軟件、手機操作系統等,業務應用軟件主要包括客戶關係管理CRM、網上營業廳、融合計費OCS和各類消息網關,例如短信網關、彩信網關等。

本文重點介紹電信業務應用軟件的技術變遷歷史,以及華為電信軟件架構演進和Java NIO框架在技術變遷中起到的關鍵作用。

1.2. 華為電信軟件的技術演進史

1.2.1. C和C++主導的第一代架構

在2005年之前,華為軟件公司的核心繫統主要以C和C++進行開發,由於C和C++開源框架非常少,加之那個時代開源社區並不成熟,大部分的系統都採用自研開發,包括協議棧、系統調度、數據訪問層和日誌。

大多數的軟件都運行在服務端,對外提供高性能、低時延和高併發的系統調用,協議棧大多數都採用電信私有協議棧,對於部分有前臺管理Portal的系統,往往基於原生的HTML或者Struts等WEB框架開發,通過HTTP協議與後端進行交互,它的邏輯架構圖如下:

Java NIO通信框架在電信領域的實踐

圖1-1 華為電信軟件V1版邏輯架構圖

在那個時代,電信軟件絕大多數都部署在高性能的小機中,處理各種信令、電信私有協議的接入和解析、複雜業務邏輯處理,系統對處理性能、時延、多核處理的要求非常高。當時Java主流版本還是JDK 1.4.2(1.4.X),它在傳統的Web應用、電子商務網站和政企系統中得到了比較廣泛的應用,但是在電信領域並沒有大的應用,主要原因如下:

1) 在JDK1.5之前的早期版本中,Java在多線程編程、並行處理等方面能力很差,無法在電信軟件服務器端使用;

2) JDK 1.4.X對非阻塞I/O的支持並不好,相關NIO編程的可參考資料和開源框架很少,傳統的阻塞I/O模型在電信高性能、高可靠場景中力不從心;

3) 業界很少有Java高性能服務端處理成功的案例,大家普遍對Java 支持電信級應用場景持懷疑態度;

4) 那個時代電信領域的開發者都是C/C++出身,大家對新技術和語言有種天生的排斥。

2005年之後,隨著Java在各領域的快速普及和應用,以及基於Java的各種開源框架井噴式增長,華為越來越多的產品開始嘗試切換到Java進行開發,主流架構隨即演進到了以Java為主的V2版本。

1.2.2. Spring + Struts + Tomcat 的第二代架構

2005年-2008年間,華為電信軟件大多數產品線都切換到Java語言進行新產品的設計和開發,當時隨著Struts的MVC模式以及Spring對J2EE複雜企業應用對象生命週期的配置式管理的流行,華為電信軟件絕大多數產品採用基於Spring + Struts + Tomcat模式進行開發,數據訪問中間件主要採用iBatis和Hibernate,它的邏輯架構如下所示:

Java NIO通信框架在電信領域的實踐

圖1-2 華為電信軟件V2 MVC版邏輯架構圖

切換到以Spring + J2EE容器為基礎技術框架之後,應用開發的難度迅速降低,開發效率獲得了極大提升。短短1-2年時間,公司大多數以C/C++的項目切換到了Java語言和V2 架構上。

1.2.3. 以SOA為中心的第三代架構

當垂直應用越來越多,應用之間交互不可避免,將核心業務抽取出來,作為獨立的服務,逐漸形成穩定的服務中心,使前端應用能更快速的響應多變的市場需求。

隨著電信業務的快速發展,電信原有系統和新建設系統之間存在語言、協議、運行環境等諸多差異。如何整合異構系統,實現高效企業集成,也是一個巨大的挑戰,此時,企業服務總線(ESB)是個不錯的選擇。

為了滿足電信業務的需求,華為軟件研發了SOA中間件,它的邏輯架構圖如下:

Java NIO通信框架在電信領域的實踐

圖1-3 以SOA服務化為核心的V3架構

SOA是一種粗粒度、松耦合的以服務為中心的架構,接口之間通過定義明確的協議和接口進行通信。SOA幫助工程師們站在一個新的高度理解企業級架構中各種組件的開發和部署形式,它可以幫助企業系統架構師以更迅速、可靠和可重用的形式規劃整個業務系統。相比於傳統的垂直架構,SOA能夠更加從容的應對複雜企業系統集成和需求的快速變化。

1.2.4. 以分佈式、雲化為核心的第四代架構

隨著業務的不斷髮展,硬件成本的下降,基於X86架構的廉價硬件 + 分佈式軟件的模式在互聯網行業得到了大規模應用,分佈式架構日趨成熟。

從運營商業務看,儘管高性能的小機仍然是標配,但是運營商業務向數字化轉型和雲化降成本逐漸成為一種趨勢。

傳統SOA架構中的一些缺陷逐步暴露,例如企業集成總線ESB是實體總線,性能線性擴展能力有限;硬件負載均衡器的壓力越來越大,不斷擴容導致硬件成本增加;隨著業務規模的不斷增長,傳統的數據庫、配置中心等逐漸成為單點瓶頸等。

我們需要通過新的分佈式架構來解決電信軟件面臨的成本高、性能無法線性增長等問題,以分佈式技術為核心構建的華為分佈式中間件應用而生,它主要包括如下組件:

1) 高性能、低時延的分佈式服務框架;

2) 分佈式消息隊列MQ;

3) 分佈式緩存;

4) 分佈式數據庫訪問中間件,支持跨庫操作,支持異構數據庫;

5) 軟負載SLB;

6) 分佈式日誌採集和檢索(Flume + ELK);

7) 分佈式實時流式計算框架;

8) 分佈式消息跟蹤系統;

9) 其它......

自從亞馬遜的雲計算服務面世以來,雲計算技術作為應對笨重的傳統IT架構的戰略,已經成為越來越多的政府和企業的選擇, “雲”已經成為ICT技術和服務領域的“常態”。

運營商基礎設施雲化的主要原因如下:

1) IT資源規模比較大,如何高效的使用這些設備,提升效率,虛擬化是個不錯的選擇;

2) 資源的孤島現象是比較嚴重,大部分IT系統,依然採用傳統的豎井式的建設模式,IT系統的資源無法在跨系統間進行共享,同時因為各系統建各系統的特點,使得資源的利用率非常低,各個業務有峰值的時候,雖然業務之間峰值不在一塊,但是依然起不到消峰的作用,佔用的資源比較大;

3) 系統的壓力也是不均衡,資源由於沒法共享,只能採用被動的採購,使得更大容量的設備採購,來應付電信業務增長所需要的擴容;

4) 系統部署的週期長、運維也比較難。

為了滿足運營商雲化的需求,華為相繼研發了IaaS、PaaS等用於支撐運營商IT和基礎設施雲化,下面讓我們一起看下華為軟件雲化後的邏輯架構:

Java NIO通信框架在電信領域的實踐

圖1-4 以分佈式、雲化為核心的V4架構

第四代技術架構以分佈式、雲化為核心,相比於前三代架構,它的核心特性如下:

1) 採用分佈式技術構建,所有的中間件都沒有單點,支持線性增長和彈性伸縮;

2) 以微服務架構為核心,打造電信領域的DevOps(結合華為PaaS平臺);

3) 由傳統的SOA Governance 向微服務治理和自治演進,提升服務治理效能;

4) 分佈式日誌採集 + 實時流式計算框架,更快的故障定界,提升大規模、分佈式系統中的運維效率;

5) 業務和數據的拆分,分而治之,通過分佈式中間件服務向業務屏蔽拆分細節;

6) 架構雲化帶來的巨大優勢:資源池提升硬件利用率、DevOps提升開發和運維效率、應用和服務的自動彈性伸縮、應用和服務故障自動恢復、高HA、自動化運維等。

1.3. 架構演進中的技術

隨著架構的演進,Java的版本也在不斷升級,技術堆棧不斷更新,EJB、Spring、RMI、MQ[消息隊列]、Node.js、NIO、Hadoop等。在眾多技術堆棧中,我印象最深的就是Java的NIO類庫以及業界成熟NIO框架的使用,它在華為軟件架構演進中發揮了重大作用,曾立下了汗馬功勞。現在,以Netty為代表的NIO框架已經在華為平臺產品和業務產品中得到了廣泛的應用。

作為華為軟件公司最早使用Java NIO技術進行平臺開發、2009年即在全球商用成功的親歷者和實踐者,我想跟大家分享下Java NIO框架在華為軟件以及電信領域的應用和實踐。

2. Java NIO 技術的引入

2.1. BIO帶給我們深深傷痛

在2008年的時候,我參與設計和開發的一個電信系統在月初出帳期,總是發生大量的連接超時和讀寫超時異常,業務的失敗率相比於平時高了很多,報表中的很多指標都差強人意。後來經過排查,發現問題的主要原因出現在下游網元的處理性能上,月初的時候BSS出帳,在出帳期間BSS系統運行緩慢,由於雙方採用了同步阻塞式的HTTP+XML進行通信(SOAP),導致任何一方處理緩慢都會影響對方的處理性能。按照故障隔離的設計原則,對方處理速度慢或者不回應答,不應該影響系統的其他功能模塊或者協議棧,但是在同步阻塞I/O通信模型下,這種故障傳播和相互影響是不可避免的,很難通過業務層面解決。

受限於當時Tomcat和Servlet的同步阻塞I/O模型,以及在Java領域異步HTTP協議棧的技術積累不足,當時我們並沒有辦法完全解決這個問題,只能通過調整線程池策略和HTTP超時時間來從業務層面做規避。由於我們的系統是一個全國級的一級系統,需要對接周邊各個網元,同時服務器資源十分有限,即便採用了高峰期間動態修改超時時間、優化線程池模型等多種措施,效果依然差強人意。

每當跟客戶開會的時候,客戶總會提起這個話題:別人響應慢,為啥會導致你的系統阻塞呢,可以返回處理其它消息啊?!我無法跟客戶解釋技術細節,因為同步阻塞I/O僅僅是Java I/O的一種實現,操作系統支持非阻塞I/O和異步I/O。

站在技術的角度,客戶的需求是合理並且也是可以實現的,當時受限於經驗以及其它技術原因,我們無法從根本上解決客戶提出的問題,團隊有種深深的挫敗感,Java BIO同步阻塞通信導致的各種問題給我留下了一些心理陰影,一直揮之不去。

2.2. BIO模型存在的問題

傳統同步阻塞通信面臨的主要問題如下:

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

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

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

傳統同步阻塞通信的處理模型圖如下:

Java NIO通信框架在電信領域的實踐

圖2-1 同步阻塞通信模型處理模型圖

從上圖我們可以看出,每當有一個新的客戶端接入,服務端就需要創建一個新的線程(或者重用線程池中的可用線程),每個客戶端鏈路對應一個線程。當客戶端處理緩慢或者網絡有擁塞時,服務端的鏈路線程就會被同步阻塞,也就是說所有的I/O操作都可能被掛住,這會導致線程利用率非常低,同時隨著客戶端接入數的不斷增加,服務端的I/O線程不斷膨脹,直到無法創建新的線程。

同步阻塞I/O導致的問題無法在業務層規避,必須改變I/O模型,才能從根本上解決這個問題。

2.3. 歷史性的引入Java NIO

2.3.1. Java NIO被冷落的原因

從2004年JDK1.4首次提供NIO 1.0類庫到現在,已經過去了整整10年。JSR 51的設計初衷就是讓Java能夠提供非阻塞、具有彈性伸縮能力的異步I/O類庫,從而結束Java在高性能服務器領域的不利地位。然而,在相當長的一段時間裡,Java的NIO編程並沒有流行起來,究其原因如下。

  1. 大多數高性能服務器,被C和C++語言盤踞,由於它們可以直接使用操作系統的異步I/O能力,所以對JDK的NIO並不關心;
  2. 移動互聯網尚未興起,基於Java的大規模分佈式系統極少,很多中小型應用服務對於異步I/O的訴求不是很強烈;
  3. 高性能、高可靠性領域,例如銀行、證券、電信等依然以C++為主導,Java充當打雜的角色,NIO暫時沒有用武之地;
  4. 當時主流的J2EE服務器,幾乎全部基於同步阻塞I/O構建,例如Servlet、Tomcat等,由於它們應用廣泛,如果這些容器不支持NIO,用戶很難具備獨立構建異步協議棧的能力;
  5. 異步NIO編程門檻比較高,開發和維護一款基於NIO的協議棧對很多中小型公司來說像是一場噩夢;
  6. 業界NIO框架不成熟,很難商用;
  7. 國內研發界對NIO的陌生和認識不足,沒有充分重視。

基於上述幾種原因,NIO編程的推廣和發展長期滯後,特別是國內,在2009年的時候,幾乎無法搜到國內企業成功使用NIO技術的案例。

2.3.2. 華為軟件引入Java NIO的原因

從2008年開始,華為軟件研發了Java版的業務網關,並迅速佔領國內外市場。隨著產品的推廣,在一些高併發、大業務量的局點相繼出現了幾起事故,質量回溯的結果都指向了Java BIO通信模型,包括Servlet 2.X的同步阻塞I/O、Tomcat 5.X(當時沒使用5.5)的同步I/O、以及其它的同步I/O協議棧。

問題根因已經很清楚,如果不改變同步I/O通信模型,問題會繼續發生,對於運營商而言,這是不可能接受的事情。自古華山一條路,即然業界沒有成熟的異步I/O協議棧,那我們就自研。

2009年初,由於對技術的熱愛,我作為業務骨幹被領導派去參加異步高性能網關平臺的研發工作,與兩位資深的架構師(其中一位工作20年,做華為交換機出身)一起合作。這是我第一次全面接觸異步I/O編程和高性能電信級協議棧的開發,眼界大開——異步高性能內部協議棧、異步HTTP、異步SOAP、異步SMPP……所有的協議棧都是異步非阻塞模式。

後來的性能測試表明:基於Reactor模型統一調度的長連接和短連接協議棧,無論是性能、可靠性還是可維護性,都可以“秒殺”傳統基於BIO開發的應用服務器和各種協議棧,這種指標差異本質上是一種技術代差。

2009年底,基於異步網關平臺研發的XX業務產品在海外某運營商成功上線,它的高性能、低時延和高HA令局方驚歎不已,原來準備的20多臺小機最後只使用了3臺,為客戶節省了一大把$。

2.3.3. 那些年我們踩過的NIO “坑”

在我從事異步NIO編程的2009年,業界還沒有成熟的NIO框架,那個時候Mina剛剛開始起步,功能和性能都達不到商用標準。最困難的是,國內Java領域的異步通信還沒有流行,整個業界的積累都非常少。那個時候資料匱乏,能夠交流和探討的圈內人很少,一旦踩住“地雷”,就需要夜以繼日地維護。在隨後2年多的時間裡,經歷了10多次的在通宵、凌晨被一線的運維人員電話吵醒等種種磨難之後,我們自研的NIO框架才逐漸穩定和成熟。期間,解決的BUG總計20~30個。

為了解決這些Bug,2年中我經歷了10幾個通宵,現在回想起來仍歷歷在目,特別是JDK epoll 空輪詢導致的 CPU 100%,更是坑中之坑(JDK NIO類庫的Bug),曾令多少產品中招,包括Mina、Netty、Jetty等著名開源框架。

2.4. 從Java 原生NIO到NIO框架

從2011年開始,華為軟件主要使用NIO框架Netty進行通信軟件的開發,為什麼不繼續使用原聲的Java NIO類庫,下面給出了我們切換的原因。

2.4.1. JAVA 原生NIO類庫的複雜性

在分析Java原生NIO類庫複雜性之前,我們首先看下最簡單的NIO服務端和客戶端創建流程。

最簡單的NIO服務端創建程序流程:

Java NIO通信框架在電信領域的實踐

圖2-2 Java NIO 服務端創建流程

最簡單的Java NIO 客戶端創建流程如下:

Java NIO通信框架在電信領域的實踐

圖2-3 Java NIO 客戶端創建流程

現在我們總結一下為什麼不建議開發者直接使用JDK的NIO類庫進行開發,具體原因如下:

(1)NIO的類庫和API繁雜,使用麻煩,你需要熟練掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等;

(2)需要具備其他的額外技能做鋪墊,例如熟悉Java多線程編程。這是因為NIO編程涉及到Reactor模式,你必須對多線程和網路編程非常熟悉,才能編寫出高質量的NIO程序;

(3)可靠性能力補齊,工作量和難度都非常大。例如客戶端面臨斷連重連、網絡閃斷、半包讀寫、失敗緩存、網絡擁塞和異常碼流的處理等問題,NIO編程的特點是功能開發相對容易,但是可靠性能力補齊的工作量和難度都非常大;

(4)JDK NIO的BUG,例如臭名昭著的epoll bug,它會導致Selector空輪詢,最終導致CPU 100%。官方聲稱在JDK1.6版本的update18修復了該問題,但是直到JDK1.7版本該問題仍舊存在,只不過該BUG發生概率降低了一些而已,它並沒有被根本解決。該BUG以及與該BUG相關的問題單可以參見以下鏈接內容:

◎ http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6403933

◎ http://bugs.java.com/bugdatabase/view_bug.do?bug_id=2147719

異常堆棧如下:

java.lang.Thread.State: RUNNABLE

at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)

at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:210)

at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:65)

at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:69)

- locked <0x0000000750928190> (a sun.nio.ch.Util$2)

- locked <0x00000007509281a8> (a java.util.Collections$ UnmodifiableSet)

- locked <0x0000000750946098> (a sun.nio.ch.EPollSelectorImpl)

at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:80)

at net.spy.memcached.MemcachedConnection.handleIO(Memcached Connection.java:217)

at net.spy.memcached.MemcachedConnection.run(MemcachedConnection. java:836)

2.4.2. 以Netty為代表的NIO框架已經成熟

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

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

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

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

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

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

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

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

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

正是因為這些優點,Netty逐漸成為Java NIO編程的首選框架,它也是華為公司首選的Java NIO通信框架,公司已經將其納入到公司級的優選開源第三方軟件庫中。

3. Netty在電信領域的實踐

電信行業軟件的幾個特點:

1) 高可靠性:5個9;

2) 高性能、低時延;

3) 大規模組網:例如中國移動、Telfonica 拉美十三國、沃達豐等,業務組網規模都非常大;

4) 複雜的網絡形態:對接不同設備提供商的網元和系統。

3.1. 高性能、低時延

3.1.1. 非阻塞I/O模型

在I/O編程過程中,當需要同時處理多個客戶端接入請求時,可以利用多線程或者I/O多路複用技術進行處理。I/O多路複用技術通過把多個I/O的阻塞複用到同一個select的阻塞上,從而使得系統在單線程的情況下可以同時處理多個客戶端請求。與傳統的多線程/多進程模型比,I/O多路複用的最大優勢是系統開銷小,系統不需要創建新的額外進程或者線程,也不需要維護這些進程和線程的運行,降低了系統的維護工作量,節省了系統資源。

我們採用Netty的NIO傳輸模式來提升I/O操作的效率,節省線程等其它資源開銷,它的模型如下所示:

Java NIO通信框架在電信領域的實踐

圖3-1 Netty的非阻塞I/O調度模型

3.1.2. 高性能的序列化框架

在華為軟件,對於序列化框架的選擇,我們遵循如下幾個原則:

1) 序列化後的碼流大小(網絡帶寬的佔用);

2) 序列化&反序列化的性能(CPU、內存等資源佔用);

3) 是否支持跨語言(異構系統的對接和開發語言切換);

4) 高併發調用時的性能,是否隨著線程併發數線性增長。

基於上述的指標,目前最常用的選擇是:Google的ProtoBuf和Apache的Thrift。

Netty原生提供了對ProtoBuf序列化框架的支持,它的優點如下:

1) 在谷歌內部長期使用,產品成熟度高;

2) 跨語言、支持多種語言,包括C++、Java和Python;

3) 編碼後的消息更小,更加有利於存儲和傳輸;

4) 編解碼的性能非常高;

5) 支持不同協議版本的前向兼容;

6) 支持定義可選和必選字段。

Netty ProtoBuf 服務端開發示例如下:

// 配置服務端的NIO線程組

EventLoopGroup bossGroup = new NioEventLoopGroup();

EventLoopGroup workerGroup = new NioEventLoopGroup();

try {

ServerBootstrap b = new ServerBootstrap();

b.group(bossGroup, workerGroup)

.channel(NioServerSocketChannel.class)

.option(ChannelOption.SO_BACKLOG, 100)

.handler(new LoggingHandler(LogLevel.INFO))

.childHandler(new ChannelInitializer() {

@Override

public void initChannel(SocketChannel ch) {

ch.pipeline().addLast(

new ProtobufVarint32FrameDecoder());

ch.pipeline().addLast(

new ProtobufDecoder(

SubscribeReqProto.SubscribeReq

.getDefaultInstance()));

ch.pipeline().addLast(

new ProtobufVarint32LengthFieldPrepender());

ch.pipeline().addLast(new ProtobufEncoder());

ch.pipeline().addLast(new SubReqServerHandler());

}

});

Thrift相對複雜一些,需要將編解碼框架從Thrift中剝離出來,然後利用Netty編解碼框架的擴展性定製實現,在此不再贅述。

3.1.3. 收斂的Reactor線程模型

Java線程採用搶佔的方式爭奪CPU等資源,當系統線程數增大到一定量級之後,性能不僅沒有提升,反而下降。

對於大型的電信應用,如果使用Tomcat等做Web容器,為了保證吞吐量和性能,HTTP線程池的最大線程數往往配置為1024。在系統運行期間我們Dump線程堆棧,發現大量的線程競爭,這不僅導致HTTP協議棧的性能下降,更影響其它業務處理線程的執行效率。

使用Netty之後,我們通過控制NioEventLoopGroup的NioEventLoop個數來收斂線程,防止線程膨脹。NioEventLoop聚合了一個多路複用器Selector,可以高效的處理N個Channel,它的線程模型如下:

Java NIO通信框架在電信領域的實踐

圖3-2 Netty Reactor線程模型

3.1.4. 其它優化

為了進一步提升性能,降低時延,我們還採用了其它一些優化措施,總結如下:

1) 使用Netty 4的內存池,減少業務高峰期ByteBuf頻繁創建和銷燬導致的GC頻率和時間;

2) 在程序中充分利用Netty提供的“零拷貝”特性,減少額外的內存拷貝,例如使用CompositeByteBuf而不是分別為Head和Body各創建一個ByteBuf對象;

3) TCP參數的優化,設置合理的Send和Receive Buffer,通常建議值為64K - 128K;

4) 軟中斷:如果Linux內核版本支持RPS(2.6.35以上版本),開啟RPS後可以實現軟中斷,提升網絡吞吐量;

5) 無鎖化串行開發理念:使用Netty 4.X版本,天生支持串行化處理;業務開發過程中,遵循Netty 4的線程模型優化理念,防止人為增加線程競爭。

3.2. 高HA

3.2.1. 內存保護

為了提升內存的利用率,Netty提供了內存池和對象池。但是,基於緩存池實現以後需要對內存的申請和釋放進行嚴格的管理,否則很容易導致內存洩漏。

如果不採用內存池技術實現,每次對象都是以方法的局部變量形式被創建,使用完成之後,只要不再繼續引用它,JVM會自動釋放。但是,一旦引入內存池機制,對象的生命週期將由內存池負責管理,這通常是個全局引用,如果不顯式釋放JVM是不會回收這部分內存的。

對於Netty的用戶而言,使用者的技術水平差異很大,一些對JVM內存模型和內存洩漏機制不瞭解的用戶,可能只記得申請內存,忘記主動釋放內存,特別是JAVA程序員。

為了防止因為用戶遺漏導致內存洩漏,Netty在Pipe line的尾Handler中自動對內存進行釋放。

緩衝區內存溢出保護:做過協議棧的讀者都知道,當我們對消息進行解碼的時候,需要創建緩衝區。緩衝區的創建方式通常有兩種:

1) 容量預分配,在實際讀寫過程中如果不夠再擴展;

2) 根據協議消息長度創建緩衝區。

在實際的商用環境中,如果遇到畸形碼流攻擊、協議消息編碼異常、消息丟包等問題時,可能會解析到一個超長的長度字段。筆者曾經遇到過類似問題,報文長度字段值竟然是2G多,由於代碼的一個分支沒有對長度上限做有效保護,結果導致內存溢出。系統重啟後幾秒內再次內存溢出,幸好及時定位出問題根因,險些釀成嚴重的事故。

Netty提供了編解碼框架,因此對於解碼緩衝區的上限保護就顯得非常重要。下面,我們看下Netty是如何對緩衝區進行上限保護的:

1) 在內存分配的時候指定緩衝區長度上限;

2) 在對緩衝區進行寫入操作的時候,如果緩衝區容量不足需要擴展,首先對最大容量進行判斷,如果擴展後的容量超過上限,則拒絕擴展;

3) 在解碼的時候,對消息長度進行判斷,如果超過最大容量上限,則拋出解碼異常,拒絕分配內存。

3.2.2. 流量整形

電信系統一般都有多個網元組成,例如參與短信互動,會涉及到手機、基站、短信中心、短信網關、SP/CP等網元。不同網元或者部件的處理性能不同。為了防止因為浪湧業務或者下游網元性能低導致下游網元被壓垮,有時候需要系統提供流量整形功能。

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

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

Java NIO通信框架在電信領域的實踐

圖3-3 Netty 流量整形原理圖

Netty內置兩種流量整形策略,可以方便的被用戶添加和使用:

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

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

3.2.3. 其它可靠性措施

其它比較重要的可靠性措施如下:

1) 客戶端連接超時控制策略;

2) 鏈路斷連重連策略;

3) 鏈路異常關閉資源釋放;

4) 解碼失敗的異常處理策略;

5) 鏈路異常的捕獲和處理;

6) I/O線程的釋放。

3.3. 華為軟件對Netty的優化

針對電信軟件的特點,結合華為軟件的實際業務需求,我們對Netty進行了優化,優化的策略如下:

1) 能夠通過Netty提供的擴展點實現的,通過擴展點實現,不自己造輪子;

2) 不允許修改Netty源碼,基於Netty提供的接口,開發華為自己的優化實現類;

3) 華為優化實現類獨立打包,對原Netty類庫是二進制依賴,不修改Netty原類庫;

4) 服務端和客戶端創建時,傳遞華為自己的實現類參數。

華為的主要優化點總結如下:

1) 安全性改造:滿足華為公司安全紅線、電信運營商的安全需求相關改造;

2) 可靠性增強:消息發送隊列的上限保護、鏈路中斷時緩存中待發送消息回調通知業務、增加錯誤碼、異常日誌打印抑制、I/O線程健康度檢測等;

3) 可定位性增強:單鏈路的網絡吞吐量、接收發送的速度、接收\發送的總字節數、畸形碼流檢測機制、解碼時延超大消息日誌打印等。

李林鋒,2007年畢業於東北大學,2008年進入華為公司從事高性能通信軟件的設計和開發工作,有7年NIO設計和開發經驗,精通Netty、Mina等NIO框架和平臺中間件,現任華為軟件平臺架構部架構師,《Netty權威指南》作者。目前從事華為下一代中間件和PaaS平臺的架構設計工作。


分享到:


相關文章: