Mina工作原理分析

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

總體結構

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

IoService

最底層的是IoService,負責具體的IO相關工作。這一層的典型代表有IoSocketAcceptor和IoSocketChannel,分別對應TCP協議下的服務端和客戶端的Ioservice。IoService的意義在於隱藏底層的IO的細節,對上提供統一的基於事件的異步IO接口。每當有數據到達時,IOService會先調用底層IO接口讀取數據,封裝成IoBuffer,之所以事件的形式通知上層代碼,從而將Java Nio的同步IO接口轉化成了異步IO。所以從圖中看,進來的loe-level IO經過IoService層後變成了IO Event。
具體的代碼可以參考
org.apache.mina.core.polling.AbstractPollingIoProcessor的私有內部類Processor。

IoFilterChain

Mina的設計理念之一就是業務代碼和數據包處理代碼分離,業務代碼只專注於業務邏輯,其它的邏輯如:數據包的解析、封裝、過濾等則交由IoFilter Chain來處理。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

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

public class EchoHandler extends IoHandlerAdaptor{
    @Override
    public void messageReceived(IoSession session,Object message) throws Exception{
        session.write(((IoBuffer)message).duplicate());
    }
}

工作原理

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

image

Acceptor與Connector線程

在服務器端,bind一個端口後,會創建一個Acceptor線程來負責監聽工作。這個線程的工作只有一個,調用Java Nio接口在該端口上select connect事件,獲取新的連接後,封裝成IoSession,交由後面的Processor線程處理。在客戶端,也有一個類似的,叫Connector的線程與之相對應。這兩類線程的數量只有一個,外界無法控制這兩類線程的數量。

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取模來分配。

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

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

線程模型

線程模型原理

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

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

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

第二種模型則是在IoFilterChain中加入了Thread Pool Filter。此時的處理流程變爲Processor線程讀取完數據後,執行IOFilterChain的邏輯。當執行到Thread Pool Filter的時候,該Filter會將後續的處理流程封裝到一個Runnable對象中,並交由Filter自身的線程池來執行,而Processor線程則能立即返回處理下一個IO請求。這樣如果後面的額IOFIlter或IoHandler有阻塞操作,只會引起Filter線程池裏的線程阻塞,而不會阻塞住Processor線程,從而提高了服務器的處理能力。Mina提供了Thread PoolFilter的一個實現: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的多個請求能被並行地處理,來進一步提高吞吐量。

轉載:http://www.cnblogs.com/metoy/p/3593264.html?utm_source=tuicool&utm_medium=referral

發佈了104 篇原創文章 · 獲贊 56 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章