Mina工作原理分析


  Mina是Apache社區維護的一個開源的高性能IO框架,在業界內久經考驗,廣為使用。Mina與后來興起的高性能IO新貴Netty一樣,都是韓國人Trustin Lee的大作,二者的設計理念是極為相似的。在作為一個強大的開發工具的同時,這兩個框架的優雅設計和不俗的表現,有很多地方是值得學習和借鑒的。本文將從Mina工作原理的角度出發,對其結構進行分析。

 一、總體結構

 Mina的底層依賴的主要是Java NIO庫,上層提供的是基於事件的異步接口。其整體的結構如下:

 

IoService

  最底層的是IOService,負責具體的IO相關工作。這一層的典型代表有IOSocketAcceptor和IOSocketChannel,分別對應TCP協議下的服務端和客戶端的IOService。IOService的意義在於隱藏底層IO的細節,對上提供統一的基於事件的異步IO接口。每當有數據到達時,IOService會先調用底層IO接口讀取數據,封裝成IoBuffer,之后以事件的形式通知上層代碼,從而將Java NIO的同步IO接口轉化成了異步IO。所以從圖上看,進來的low-level IO經過IOService層后變成IO Event。

  具體的代碼可以參考org.apache.mina.core.polling.AbstractPollingIoProcessor的私有內部類Processor。

IoFilterChain

  Mina的設計理念之一就是業務代碼和數據包處理代碼分離,業務代碼只專注於業務邏輯,其他的邏輯如:數據包的解析,封裝,過濾等則交由IoFilterChain來處理。IoFilterChain可以看成是Mina處理流程的擴展點。這樣的划分使得結構更加清晰,代碼分工更明確。開發者通過往Chain中添加IoFilter,來增強處理流程,而不會影響后面的業務邏輯代碼。

IoHandler

 IoHandler是實現業務邏輯的地方,需要有開發者自己來實現這個接口。IoHandler可以看成是Mina處理流程的終點,每個IoService都需要指定一個IoHandler。

IoSession

 IoSession是對底層連接的封裝,一個IoSession對應於一個底層的IO連接(在Mina中UDP也被抽象成了連接)。通過IoSession,可以獲取當前連接相關的上下文信息,以及向遠程peer發送數據。發送數據其實也是個異步的過程。發送的操作首先會逆向穿過IoFilterChain,到達IoService。但IoService上並不會直接調用底層IO接口來將數據發送出去,而是會將該次調用封裝成一個WriteRequest,放入session的writeRequestQueue中,最后由IoProcessor線程統一調度flush出去。所以發送操作並不會引起上層調用線程的阻塞。

具體代碼可以參考org.apache.mina.core.filterchain.DefaultIoFilterChain的內部類HeadFilter的filterWrite方法。

參見:http://www.cnblogs.com/xuekyo/archive/2013/03/06/2945826.html

          http://www.cnblogs.com/metoy/p/3593264.html

二、工作流程

總體來講Mina框架分3層:

  • I/O Service :負責處理I/O,執行IO操作;
  • I/O Filter Chain :過濾鏈。負責編碼處理,字節到數據結構或數據結構到字節的轉換等,即非業務邏輯的操作
  • I/O Handler :負責處理業務邏輯

所以要創建一個基於NIMA框架的應用程序,必須:

  1. Create an I/O service - 創建一個已經(*Acceptor)服務
  2. Create a Filter Chain - 創建一系列的Filters並加入到過濾鏈
  3. Create an I/O Handler - 實現自己的業務邏輯

客戶端的主要邏輯思路如下:

  •  客戶端首先創建一個IOConnector 用來和服務端通信,顧名思義這就是建立的一個連接對象;
  • 在這個連接上創建一個session, 客戶端中的業務方法可以向session中寫入數據,數據經過Filter Chain的過濾后會發送給服務端;
  • 從服務端發回的數據也會首先經過Filter Chain的過濾,然后交給IOHandler做進一步的處理

服務端的主要邏輯思路如下:

  • IOAcceptor 監聽網絡數據包傳入的連接;
  • 為每個新的連接(Connection)創建一個session,同一個端口+ip的后續請求將通過session進行處理;
  • 同一個session收到的所有數據,通過過濾鏈進行過濾.通過PacketEncoder/Decoder進行有效的編碼,解碼處理(負責把底層傳輸的對象拼裝為更高一層的對象方便后續的處理,最后傳輸的數據被交給IOHandler);
  • 最后根據自己的業務需求完成Handler的業務邏輯處理.

參見:http://www.cnblogs.com/quyongjin/archive/2013/06/08/3127222.html

     通過SocketAcceptor 同客戶端建立連接; 

     連接建立之后 I/O的讀寫交給了I/O Processor線程,I/O Processor是多線程的; 

     通過I/O Processor 讀取的數據經過IoFilterChain里所有配置的IoFilter, IoFilter  進行消息的過濾,格式的轉換,在這個層面可以制定一些自定義的協議; 

     最后  IoFilter 將數據交給 Handler  進行業務處理,完成了整個讀取的過程; 

     寫入過程也是類似,只是剛好倒過來,通過IoSession.write 寫出數據,然后Handler進行寫入的業務處理,處理完成后交給IoFilterChain,進行消息過濾和協議的轉換,最后通過 I/O Processor 將數據寫出到 socket 通道。

 三、工作原理

  Mina里面是怎么使用Java NIO和進行線程調度的呢?這是提高IO處理性能的關鍵所在。Mina的線程調度原理主要如下圖所示:

Acceptor與Connector線程

  在服務器端,bind一個端口后,會創建一個Acceptor線程來負責監聽工作。這個線程的工作只有一個:調用Java NIO接口在該端口上select connect事件,獲取新建的連接后,封裝成IoSession,交由后面的Processor線程處理。

 在客戶端,也有一個類似的,叫Connector的線程與之相對應。這兩類線程的數量只有1個,外界無法控制這兩類線程的數量。

 TCP實現的代碼可以參考org.apache.mina.core.polling.AbstractPollingIoAcceptor的內部類Acceptor和org.apache.mina.core.polling.AbstractPollingIoConnector的內部類Connector。

Processor線程

 Processor線程主要負責具體的IO讀寫操作和執行后面的IoFilterChain和IoHandler邏輯。Processor線程的數量N默認是CPU數量+1,可以通過配置參數來控制其數量。前面進來的IoSession會被分配到這N個Processor線程中。默認的SimpleIoProcessorPool的策略是session id絕對值對N取模來分配。

 每個Porcessor線程中都維護着一個selector,對它維護的IoSession集合進行select,然后對select的結果進行遍歷,逐一處理。像前面提到的,讀取數據,以事件的形式通知后面IoFilterChain;以及對寫請求隊列的flush操作,都是在這類線程中來做的。

 通過將session均分到多個Processor線程里進行處理,可以充分利用多核的處理能力,減輕select操作的壓力。默認的Processor的線程數量設置可以滿足大部分情況下的需求,但進一步的優化則需要根據實際環境進行測試。

四、線程模型

線程模型原理

  單一的Processor線程內部來看,IO請求的處理流程是單線程順序處理的。前面也提到過,當Process線程select了一批就緒的IO請求后,會在線程內部逐一對這些IO請求進行處理。處理的流程包括IoFilter和IoHandler里的邏輯。當前面的IO請求處理完畢后,才會取下一個IO請求進行處理。也就是說,如果IoFilter或IoHandler中有比較耗時的操作的話(如:讀取數據庫等),Processor線程將會被阻塞住,后續的請求將得不到處理。這樣的情況在高並發的服務器下顯然是不能容忍的。於是,Mina通過在處理流程中引入線程池來解決這個問題。

 那么線程池應該加在什么地方呢?正如前面所提到過的:IoFilterChain是Mina的擴展點。沒錯,Mina里是通過IoFilter的形式來為處理流程添加線程池的。Mina的線程模型主要有一下這幾種形式:

 

第一種模型是單線程模型,也是Mina默認線程模型。也就是Processor包辦了從底層IO到上層的IoHandler邏輯的所有執行工作。這種模型比較適合於處理邏輯簡單,能快速返回的情況。

第二種模型則是在IoFilterChain中加入了Thread Pool Filter。此時的處理流程變為Processor線程讀取完數據后,執行IoFilterChain的邏輯。當執行到Thread Pool Filter的時候,該Filter會將后續的處理流程封裝到一個Runnable對象中,並交由Filter自身的線程池來執行,而Processor線程則能立即返回來處理下一個IO請求。這樣如果后面的IoFilter或IoHandler中有阻塞操作,只會引起Filter線程池里的線程阻塞,而不會阻塞住Processor線程,從而提高了服務器的處理能力。Mina提供了Thread Pool Filter的一個實現:ExecutorFilter。

當然,也沒有限制說chain中只能添加一個ExecutorFilter,開發者也可以在chain中加入多個ExecutorFilter來構成第三種情況,但一般情況下可能沒有這個必要。

請求的處理順序

 在處理流程中加入線程池,可以較好的提高服務器的吞吐量,但也帶來了新的問題:請求的處理順序問題。在單線程的模型下,可以保證IO請求是挨個順序地處理的。加入線程池之后,同一個IoSession的多個IO請求可能被ExecutorFilter並行的處理,這對於一些對請求處理順序有要求的程序來說是不希望看到的。比如:數據庫服務器處理同一個會話里的prepare,execute,commit請求希望是能按順序逐一執行的。

 Mina里默認的實現是有保證同一個IoSession中IO請求的順序的。具體的實現是,ExecutorFilter默認采用了Mina提供的OrderedThreadPoolExecutor作為內置線程池。后者並不會立即執行加入進來的Runnable對象,而是會先從Runnable對象里獲取關聯的IoSession(這里有個down cast成IoEvent的操作),並將Runnable對象加入到session的任務列表中。OrderedThreadPoolExecutor會按session里任務列表的順序來處理請求,從而保證了請求的執行順序。

 對於沒有順序要請求的情況,可以為ExecutorFilter指定一個Executor來替換掉默認的OrderedThreadPoolExecutor,讓同一個session的多個請求能被並行地處理,來進一步提高吞吐量。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM