Mina是Apache社區維護的一個開源的高性能IO框架,在業界內久經考驗,廣爲使用。Mina與後來興起的高性能IO新貴Netty同樣,都是韓國人Trustin Lee的大做,兩者的設計理念是極爲類似的。在做爲一個強大的開發工具的同時,這兩個框架的優雅設計和不俗的表現,有不少地方是值得學習和借鑑的。本文將從Mina工做原理的角度出發,對其結構進行分析。java
Mina的底層依賴的主要是Java NIO庫,上層提供的是基於事件的異步接口。其總體的結構以下:數據庫
最底層的是IOService,負責具體的IO相關工做。這一層的典型表明有IOSocketAcceptor和IOSocketChannel,分別對應TCP協議下的服務端和客戶端的IOService。IOService的意義在於隱藏底層IO的細節,對上提供統一的基於事件的異步IO接口。每當有數據到達時,IOService會先調用底層IO接口讀取數據,封裝成IoBuffer,以後以事件的形式通知上層代碼,從而將Java NIO的同步IO接口轉化成了異步IO。因此從圖上看,進來的low-level IO通過IOService層後變成IO Event。apache
具體的代碼能夠參考org.apache.mina.core.polling.AbstractPollingIoProcessor的私有內部類Processor。服務器
Mina的設計理念之一就是業務代碼和數據包處理代碼分離,業務代碼只專一於業務邏輯,其餘的邏輯如:數據包的解析,封裝,過濾等則交由IoFilterChain來處理。IoFilterChain能夠當作是Mina處理流程的擴展點。這樣的劃分使得結構更加清晰,代碼分工更明確。開發者經過往Chain中添加IoFilter,來加強處理流程,而不會影響後面的業務邏輯代碼。session
IoHandler是實現業務邏輯的地方,須要有開發者本身來實現這個接口。IoHandler能夠當作是Mina處理流程的終點,每一個IoService都須要指定一個IoHandler。併發
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例子來做爲本節結束吧。ide
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的線程調度原理主要以下圖所示:
在服務器端,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線程主要負責具體的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的多個請求能被並行地處理,來進一步提升吞吐量。