Mina工作原理分析


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

總體結構

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

mina-flow

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方法。

最后附上一個簡單的echo server例子來作為本節結束吧。

EchoServer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public class EchoServer {  public static void main(String[] args) {  int PORT = 3333;  NioSocketAcceptor acceptor = new NioSocketAcceptor();  acceptor.setHandler(new EchoHandler());  try {  acceptor.bind(new InetSocketAddress(PORT));  System.out.println("Listening on " + PORT);  } catch (IOException e) {  e.printStackTrace();  }  } } 

EchoHandler.java

1
2
3
4
5
6
 public class EchoHandler extends IoHandlerAdapter {  @Override  public void messageReceived(IoSession session, Object message) throws Exception {  session.write(((IoBuffer)message).duplicate());  }  } 

工作原理

前面介紹了Mina總體的層次結構,那么在Mina里面是怎么使用Java NIO和進行線程調度的呢?這是提高IO處理性能的關鍵所在。Mina的線程調度原理主要如下圖所示:

mina-threads

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-thread-model

第一種模型是單線程模型,也是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