放翁(文初)的一畝三分地

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            210 隨筆 :: 1 文章 :: 320 評論 :: 0 Trackbacks

          引問:NIO在服務端的應用已經(jīng)被廣為熟悉,但是在客戶端的使用,其實給予的指導并不多。同時在我看來,NIO在客戶端使用就是原來的長連接模式加上事件驅(qū)動的框架,而相對于短連接池模式來說,性能是否真的在任何環(huán)境都那么突出,其實不然。

           最近正好要優(yōu)化TB的Cache客戶端,原始代碼是用NIO寫的,但是效率不高,性能也一般,因此反而拖累了服務端的表現(xiàn),在整個優(yōu)化過程中,看了NIO2,也就是JDK7中比較突出的AIO,同時也經(jīng)過反復優(yōu)化和測試,其中對于NIO應用到客戶端來談一下自己的一些收獲。

          傳統(tǒng)IO操作和NIO操作的區(qū)別

           簡單來說:1.對于數(shù)據(jù)處理由Stream方式轉(zhuǎn)變稱為了Block方式。2.事件驅(qū)動機制替換了傳統(tǒng)的一個線程處理到底的模式。

           第一種轉(zhuǎn)變不適合需要對于字節(jié)流做處理的場景。(需要對字節(jié)充分作處理,例如我在另一個優(yōu)化中對于字節(jié)流采用lazy Analysis,通過邊解析邊交驗的方式,提前過濾無效請求,降低由于分析大數(shù)據(jù)包無效請求帶來的性能消耗)。但是Block傳輸和處理這種轉(zhuǎn)變符合操作系統(tǒng)的真實模式,使Java可以充分利用各個操作系統(tǒng)的實現(xiàn)來優(yōu)化性能,同時管道的思想也符合操作系統(tǒng)的真實實現(xiàn)(原來Java都是將雙向通道拆分為In,Out的)。事件驅(qū)動使得完整的處理流程被拆分成為流水線作業(yè),最大程度上利用了資源,防止后端處理成為了前端請求的瓶頸,降低了服務器的吞吐量,同時最大限度的給開發(fā)者優(yōu)化流程,縮短關鍵路徑的機會。

           下面表格大致列舉了傳統(tǒng)IO和NIO在客戶端使用的需求和各自的優(yōu)勢(兩者都需要的就不列入其中了,例如容錯恢復等)

            需求 優(yōu)勢
          IO(連接池) 1. 連接池的管理。2. 高并發(fā)大壓力下Socket數(shù)量龐大,對于文件句柄消耗也大 1. 數(shù)據(jù)發(fā)送接受處理簡單,單線程模式。2.可以對字節(jié)流逐一解析,避免內(nèi)存過分無謂消耗
          NIO 1. 交互的協(xié)議需要支持會話。(也可以不支持,這就會使得處理模式退化,效率下降,后續(xù)會談到)2. 對于接收和發(fā)送需要支持多線程,提高效率3. 需要對Channel和Block有所熟悉 1. 資源利用率最大化,性能提升(消息通道的充分利用,操作系統(tǒng)IO優(yōu)化的使用)2. 充分靈活的將處理分割為多個工作項,流水線作業(yè),減小業(yè)務處理對服務器服務請求接收吞吐量的影響。

          NIO在客戶端

          1

          上圖描述的是NIO實現(xiàn)的客戶端和服務端之間的數(shù)據(jù)交互場景,可以看到由于要最大限度利用消息通道,NIO客戶端需要具備以下幾點:
          1.消息會話支持。
          2.多線程訪問控制。
          3.消息過濾容錯。
          4.超時控制。
          5…..
           消息會話支持,指的是消息的傳輸與接收需要通過通信協(xié)議方式來實現(xiàn)會話,NIO在服務端可以很容易使用就是因為,NIO Server自身維護了服務處理會話,而對于客戶端來說,首先上圖可以看到,不同的線程使用NIO Client的時候,發(fā)送消息后得到回復的順序并不一定和消息發(fā)送的順序一致,因此需要通過在協(xié)議中內(nèi)嵌會話碼,這樣才能夠在結(jié)果返回并解析以后通知消息接收者。

           多線程訪問控制,對于消息的發(fā)送和接收,可以通過單線程處理,但是這將成為高并發(fā)下的處理瓶頸(后續(xù)的優(yōu)化過程會詳細說明)。如果允許多線程發(fā)送和接收消息,那么對于發(fā)送隊列和接收緩存都將存在著并發(fā)訪問控制的需求,同時對于發(fā)送和接收在數(shù)據(jù)需要切分的時候,需要通過小事務來保證數(shù)據(jù)的一致性。

           消息過濾容錯,由于共享一個數(shù)據(jù)通道,當返回數(shù)據(jù)出現(xiàn)問題的時候,如何過濾出錯的數(shù)據(jù)防止解析異常和死鎖很重要,也是多線程處理不相互影響的保證。

           超時控制,由于共享數(shù)據(jù)通道是異步服務接收模式,因此請求隊列會成為在高并發(fā)下的消耗資源,必要的將隊列中的超時請求刪除,是在網(wǎng)絡異?;蛘叻斩水惓5臅r候,不會被壓垮的保證。

           其他其實還有很多設計細節(jié)和要點,這里就不一一列出了。

           歸結(jié)起來,如何高效的協(xié)調(diào)好多線程數(shù)據(jù)發(fā)送和接收及分析是共享數(shù)據(jù)通道的NIO客戶端最重要的實現(xiàn)關注點。

          NIO客戶端優(yōu)化分析
           在優(yōu)化前,客戶端的結(jié)構(gòu)就是最早的傳統(tǒng)的NIO模式,以下兩張圖就可以說明大致的結(jié)構(gòu)。

          2

          3

          第一張圖是NIO的概念模型,第二張圖則是從實際使用的角度去描述了NIO框架中各個角色在實際模型中所處的地位。具體的NIO角色工作介紹就不詳細說了,這里描述一下問題,在高并發(fā)下,發(fā)現(xiàn)等待消息返回的線程池內(nèi)的線程不斷堆積,同時處理性能也不斷下降,呈現(xiàn)為惡性循環(huán)的狀態(tài)。

          4

          傳統(tǒng)模式下,事件處理是單線程串行處理的,例如如果ReadEvent在WriteEvent之前,當ReadEvent出現(xiàn)服務超時或者本身就比較耗時,那么導致其他事件無法得到處理或者處理比較慢。

           初步認定原因:串行化事件處理,導致事件之間處理效率將會相互影響。
           考慮的解決方式:事件處理線程化,將事件觸發(fā)與事件處理分離,提高不同事件處理能力。

          5

          從上圖中看到Dispatcher調(diào)用Handler處理事件和事件監(jiān)聽已經(jīng)剝離開來,這樣應該看起來會很好的解決當前的問題。

           三下五除二,在Dispatcher中新增加了一個Executor(沒有采用Cache的,使用的是fix的,防止資源由于暴漲導致產(chǎn)生大量線程搞垮應用),然后將attachment中的Channel代理類作了線程安全改造。

           測試結(jié)果讓我大跌眼鏡,在高并發(fā)下不僅沒有提高效率,反而效率降低。仔細觀察后發(fā)現(xiàn),由于對于連接,讀,寫三種事件都做了注冊,在高并發(fā)下,讀寫的事件頻度很高,因此會產(chǎn)生大量的線程,同時線程池是fix的(如果是Cache就直接OOM了),在創(chuàng)建線程中消耗的遠大于原來認為是瓶頸的順序執(zhí)行,加上對于Channel代理的線程安全改造(資源鎖等安全優(yōu)化),導致最終性能還不抵原來的初始架構(gòu)。

           看來在Dispatcher中通過線程池方式來剝離框架和業(yè)務邏輯并不合適,因此考慮從其他角度入手。從另一方面來考慮,其實如果能夠做到將Read事件和Write事件內(nèi)部處理作的足夠輕量,就算是順序處理也會有很好的效果,因此就有了下面的設計:
           6

          LightWeightHander是一個輕量級的消息處理Handler,對于原來的業(yè)務數(shù)據(jù)處理做了拆分,達到就算是串行事件處理也能夠防止事件之間相互影響。

           工作拆分實際上成了優(yōu)化最重要的部分,下面描述了工作拆分的分析點(也是不斷通過測試結(jié)果得出的最后的策略)
           7

          上圖是長連接模式中最常見的方式,系統(tǒng)中存在著發(fā)送和接收的緩沖區(qū),但是對于發(fā)送沒有采用多線程處理,而僅僅采用批量處理(設定一定的閥值,在高并發(fā)下緩沖區(qū)內(nèi)容突然增長則采用批量Flush的方式提高效率),寫出不采用多線程其實也是經(jīng)過測試發(fā)現(xiàn)性能在線程創(chuàng)建過程中會有損耗,而且寫的動作消耗時間有限,不需要做此優(yōu)化。但是對于讀部分,則存在著很多數(shù)據(jù)分析,拷貝,對象反序列化等操作,因此需要切割工作,支持并行處理,提高反饋速度。這里主要將讀取分析過程切分成三部分,前兩部分都是單線程完成操作,最后一步交由多線程池來執(zhí)行。

          第一步是由LightWeightHandler通過讀取事件了解有數(shù)據(jù)需要讀入,然后將數(shù)據(jù)包分別讀入掛接在讀入隊列中。此處不使用多線程有兩個原因,第一個原因也是線程創(chuàng)建消耗問題,第二個原因是當數(shù)據(jù)包由于過大需要分包接收,此時采用多線程接收,則會導致消息順序錯亂(消息包根據(jù)設定的接收窗口大小讀入,因此沒有編排)。第二步是將讀入的數(shù)據(jù)包按照報文協(xié)議規(guī)則劃分邏輯單元(此處需要采用ByteBuffer的各種特性,盡量避免創(chuàng)建新的數(shù)據(jù)塊和相互拷貝,提高效率)。這部分采用單線程原因是因為此處無法實行流水線作業(yè),本身就是串行的工作,關鍵路徑無法縮短。第三步,將邏輯切分后的數(shù)據(jù)報文分配給每一個線程執(zhí)行最耗時的解析和回調(diào)工作。

           最后測試效果還是不錯的,總結(jié)出來的幾點經(jīng)驗:

          1.多線程消耗有時候超過你的瓶頸,同時還需要防范在高并發(fā)下資源申請的問題。
          2.謹慎分割的任務,將任務處理串行和并行結(jié)合起來,找到最短路徑。
          3.NIO處理中充分利用ByteBuffer等Buffer,數(shù)據(jù)邏輯分段和隔離,盡量少用拷貝和新建方式,提高4.處理速度。(需要注意的是Buffer的相對操作函數(shù)使用方式,避免出現(xiàn)Buffer復用導致異常)
          批量處理需要做好測試,找到合理的閥值,防止批量帶來的延時效應和峰值效應。
          5.多線程調(diào)試盡量多用打印輸出運行期狀態(tài)了解性能瓶頸。(調(diào)試就不必了,基本無法確切了解內(nèi)部高速運轉(zhuǎn)細節(jié))

          NIO2
           最近也關注了JDK7中的很多信特性,當然NIO2不得不說,這里還是提一下和本文比較相關的AIO。在NIO2中正式的出現(xiàn)了異步IO的接口及實現(xiàn),對于這種異步模式,框架提供了兩種方式獲取結(jié)果:
          采用Future的方式來獲取。
          采用Callback的方式(CompletionHandler)來獲取。

           在上面優(yōu)化的框架中也有兩種機制的實現(xiàn),第二種就直接將方法注冊到key的attachment中。第一種的實現(xiàn)如下圖:

          8

          就是通過會話碼及對象的Wait和Notify來實現(xiàn)的。
           
           下圖就是AIO的結(jié)構(gòu)圖:

          9

            其實AIO是在NIO的幾個版本結(jié)構(gòu)更新以后增加了更好的異步回調(diào)封裝,異步回調(diào)封裝就不多說了,這里就談談NIO迭代的幾個版本中新增加的幾個角色。

           ChannelFacade其實就和優(yōu)化過程中使用長連接最通用的輸入輸出緩存設計一樣,對Channel外部在作了一層封裝,提供優(yōu)化和攔截處理的入口。InputHandler也就是負責對于輸入內(nèi)容的一個邏輯分析,判斷是否需要Flush,這和優(yōu)化中的批量Flush也十分類似。

          后話
           總的看來其實NIO在客戶端的應用和我起初的想法還是比較一致,就是長連接模式加上事件驅(qū)動框架,同時如何處理好多線程并發(fā)控制及任務切分才是體現(xiàn)出NIO信道復用的關鍵,否則還是簡單的用客戶端連接池來的省心。

          posted on 2009-09-24 08:57 岑文初 閱讀(3375) 評論(7)  編輯  收藏

          評論

          # re: 客戶端NIO實踐分析[未登錄] 2009-09-24 10:00 dennis
          aio不僅僅是異步回調(diào)的封裝,aio在大量連接的情況下能有很高的效率,不過這對客戶端通常來說沒多大意義。非阻塞IO也是可以模擬aio的。  回復  更多評論
            

          # re: 客戶端NIO實踐分析 2009-10-06 22:01 xingbo.xu
          羨慕 有 這樣的 機會 實踐??!

          像我們這樣的小公司,感覺做技術沒有前途;

          你給他簡單 把ibatis封裝哈 人家還不愛用!

          看來需要轉(zhuǎn)行!  回復  更多評論
            

          # re: 客戶端NIO實踐分析 2010-06-24 19:53 david.hew
          寫的很好,但是圖片好像看不到  回復  更多評論
            

          # re: 客戶端NIO實踐分析 2010-07-01 13:57 jaedong
          大蝦,你的圖圖還是看不到,繼續(xù)郁悶..  回復  更多評論
            

          # re: 客戶端NIO實踐分析 2011-02-17 15:55 游客
          圖還是看不到呀。  回復  更多評論
            

          # re: 客戶端NIO實踐分析[未登錄] 2011-03-02 14:33 littleJava
          么有圖片???!  回復  更多評論
            

          # re: 客戶端NIO實踐分析 2011-03-05 17:20 國畫
          羨慕有這樣的機會實踐??!圖還是看不到呀  回復  更多評論
            


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導航:
           
          主站蜘蛛池模板: 绿春县| 珠海市| 常州市| 黄冈市| 运城市| 绩溪县| 台州市| 灯塔市| 抚宁县| 玛沁县| 汝城县| 莒南县| 河西区| 南岸区| 二连浩特市| 巫溪县| 金川县| 肇源县| 景东| 闽清县| 海晏县| 嘉荫县| 湛江市| 三都| 梅河口市| 崇阳县| 中江县| 外汇| 榆社县| 合作市| 安化县| 孟州市| 龙里县| 榆中县| 左云县| 建湖县| 张北县| 萨迦县| 淮北市| 故城县| 德钦县|