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

InfoQ - 李林鋒發表於2015-05-19

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

1.1. 電信軟體

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

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

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

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

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

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

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

圖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,它的邏輯架構如下所示:

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

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

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

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

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

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

圖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和基礎設施雲化,下面讓我們一起看下華為軟體雲化後的邏輯架構:

圖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進行通訊,導致任何一方處理緩慢都會影響對方的處理效能。按照故障隔離的設計原則,對方處理速度慢或者不回應答,不應該影響系統的其他功能模組或者協議棧,但是在同步阻塞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執行緒數無法有效控制、資源無法有效共享(多執行緒併發問題),系統可維護性差

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

圖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服務端建立程式流程:

圖2-2 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操作的效率,節省執行緒等其它資源開銷,它的模型如下所示:

圖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<SocketChannel>() {
				@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,它的執行緒模型如下:

圖3-1 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)。當令牌桶有足夠的令牌時,再均勻的向外傳送這些被快取的報文。流量整形與流量監管的另一區別是,整形可能會增加延遲,而監管幾乎不引入額外的延遲。

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

圖3-2 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) 可定位性增強:單鏈路的網路吞吐量、接收傳送的速度、接收\傳送的總位元組數、畸形碼流檢測機制、解碼時延超大訊息日誌列印等。

4. 作者簡介

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

聯絡方式:新浪微博 Nettying  微信:Nettying 微信公眾號:Netty之家

對於Netty學習中遇到的問題,或者認為有價值的Netty或者NIO相關案例,可以通過上述幾種方式聯絡我。

相關文章