經驗不在于年限,在于積累---專注互聯網軟件開發

          把工作當事業做,把項目當作品做!

            BlogJava :: 首頁 :: 聯系 :: 聚合  :: 管理
            55 Posts :: 0 Stories :: 66 Comments :: 0 Trackbacks

          最近的一個線上項目(認證服務器)老是出現服務延遲的情況。具體的問題描述:

          (1)客戶端發送一個請求A(長連接),在服務器端的業務層需要20秒以上才能接收到。

          (2)客戶端發送一個請求B(端連接),在服務器端的業務層可以迅速接收到。

          從現象大致知道問題出在服務器端的網絡接收層,大量通過長連接發送過來的請求都堵塞在網絡層得不到處理(在網絡層排隊,還沒到應用層)。

          (友情提示:本博文章歡迎轉載,但請注明出處:hankchen,http://www.aygfsteel.com/hankchen

           

          后來經過排查,發現是Netty中的OrderedMemoryAwareThreadPoolExecutor原因。相關代碼如下:

          MemoryAwareThreadPoolExecutor executor = new OrderedMemoryAwareThreadPoolExecutor(threadNums, maxChannelMemorySize,
                                                                                                                  maxTotalMemorySize, keepAliveTime,
                                                                                                                  TimeUnit.SECONDS);
          ExecutionHandler executionHandler = new ExecutionHandler(executor);

          public ChannelPipeline getPipeline() throws Exception
          {
                  ChannelPipeline pipeline = pipeline();
                  pipeline.addLast("decoder", new AuthDecoder());
                  pipeline.addLast("encoder", new AuthEncoder());
                  pipeline.addLast("executor", executionHandler);
                  pipeline.addLast("handler", new AuthServerHandler(commandFactory));
                  return pipeline;
          }

           

          先介紹下背景知識,再來分析問題。

          大家都知道,Netty是一個基于事件的NIO框架。在Netty中,一切網絡動作都是通過事件來傳播并處理的,例如:Channel讀、Channel寫等等?;貞浵翹etty的流處理模型:

          Boss線程(一個服務器端口對于一個)---接收到客戶端連接---生成Channel---交給Work線程池(多個Work線程)來處理。

          具體的Work線程---讀完已接收的數據到ChannelBuffer---觸發ChannelPipeline中的ChannelHandler鏈來處理業務邏輯。

          注意:執行ChannelHandler鏈的整個過程是同步的,如果業務邏輯的耗時較長,會將導致Work線程長時間被占用得不到釋放,從而影響了整個服務器的并發處理能力。

          所以,為了提高并發數,一般通過ExecutionHandler線程池來異步處理ChannelHandler鏈(worker線程在經過ExecutionHandler后就結束了,它會被ChannelFactory的worker線程池所回收)。在Netty中,只需要增加一行代碼:

          public ChannelPipeline getPipeline() {
                   return Channels.pipeline(
                           new DatabaseGatewayProtocolEncoder(),
                           new DatabaseGatewayProtocolDecoder(),
                           executionHandler, // Must be shared
                           new DatabaseQueryingHandler());
          }
          例如:
          ExecutionHandler executionHandler = new ExecutionHandler(
                       new OrderedMemoryAwareThreadPoolExecutor(16, 1048576, 1048576))

           

          對于ExecutionHandler需要的線程池模型,Netty提供了兩種可選:

          1) MemoryAwareThreadPoolExecutor 通過對線程池內存的使用控制,可控制Executor中待處理任務的上限(超過上限時,后續進來的任務將被阻塞),并可控制單個Channel待處理任務的上限,防止內存溢出錯誤;

          2) OrderedMemoryAwareThreadPoolExecutor 是 MemoryAwareThreadPoolExecutor 的子類。除了MemoryAwareThreadPoolExecutor 的功能之外,它還可以保證同一Channel中處理的事件流的順序性,這主要是控制事件在異步處理模式下可能出現的錯誤的事件順序,但它并不保證同一Channel中的事件都在一個線程中執行(通常也沒必要)。

          例如:

          Thread X: --- Channel A (Event A1) --.   .-- Channel B (Event B2) --- Channel B (Event B3) --->
                                                \ /
                                                 X
                                                / \
          Thread Y: --- Channel B (Event B1) --'   '-- Channel A (Event A2) --- Channel A (Event A3) --->

          上圖表達的意思有幾個:

          (1)對整個線程池而言,處理同一個Channel的事件,必須是按照順序來處理的。例如,必須先處理完Channel A (Event A1) ,再處理Channel A (Event A2)、Channel A (Event A3)

          (2)同一個Channel的多個事件,會分布到線程池的多個線程中去處理。

          (3)不同Channel的事件可以同時處理(分擔到多個線程),互不影響。  

          OrderedMemoryAwareThreadPoolExecutor 的這種事件處理有序性是有意義的,因為通常情況下,請求發送端希望服務器能夠按照順序處理自己的請求,特別是需要多次握手的應用層協議。例如:XMPP協議。

           

          現在回到具體業務上來,我們這里的認證服務也使用了OrderedMemoryAwareThreadPoolExecutor。認證服務的其中一個環節是使用長連接,不斷處理來自另外一個服務器的認證請求。通信的數據包都很小,一般都是200個字節以內。一般情況下,處理這個過程很快,所以沒有什么問題。但是,由于認證服務需要調用第三方的接口,如果第三方接口出現延遲,將導致這個過程變慢。一旦一個事件處理不完,由于要保持事件處理的有序性,其他事件就全部堵塞了!而短連接之所以沒有問題,是因為短連接一個Channel就一個請求數據包,處理完Channel就關閉了,根本不存在順序的問題,所以在業務層可以迅速收到請求,只是由于同樣的原因(第三方接口),處理時間會比較長。

          其實,認證過程都是獨立的請求數據包(單個帳號),每個請求數據包之間是沒有任何關系的,保持這樣的順序沒有意義!

           

          最后的改進措施:

          1、去掉OrderedMemoryAwareThreadPoolExecutor,改用MemoryAwareThreadPoolExecutor。

          2、減少調用第三方接口的超時時間,讓處理線程盡早回歸線程池。

          (友情提示:本博文章歡迎轉載,但請注明出處:hankchen,http://www.aygfsteel.com/hankchen

          posted on 2012-04-08 12:32 hankchen 閱讀(14356) 評論(0)  編輯  收藏 所屬分類: 網絡開發+Mina+Netty
          主站蜘蛛池模板: 体育| 呼玛县| 广安市| 门源| 西城区| 麟游县| 武功县| 璧山县| 九江市| 天祝| 陕西省| 龙江县| 仲巴县| 怀来县| 英德市| 台山市| 定安县| 康定县| 宁强县| 弥勒县| 阿勒泰市| 阜康市| 霍州市| 武川县| 绥中县| 肃北| 永靖县| 丹巴县| 旺苍县| 微山县| 长春市| 孟村| 湄潭县| 呼伦贝尔市| 图们市| 乌鲁木齐市| 思茅市| 陈巴尔虎旗| 任丘市| 南雄市| 和田县|