天天看點

MINA工作流程

Mina 中的很多執行環節都使用了多線程機制,用于提高性能。Mina 中預設在三個地方使用了線程:

(1.) IoAcceptor:

這個地方用于接受用戶端的連接配接建立,每監聽一個端口(每調用一次bind()方法),都啟用一個線程,這個數字我們不能改變。這個線程監聽某個端口是否有請求到來,一旦發現,則建立一個IoSession 對象。因為這個動作很快,是以有一個線程就夠了。

(2.) IoConnector:

這個地方用于與服務端建立連接配接,每連接配接一個服務端(每調用一次connect()方法),就啟用一個線程,我們不能改變。同樣的,這個線程監聽是否有連接配接被建立,一旦發現,則建立一個IoSession 對象。因為這個動作很快,是以有一個線程就夠了。

(3.) IoProcessor:

這個地方用于執行真正的IO 操作,預設啟用的線程個數是CPU 的核數+1,譬如:單CPU 雙核的電腦,預設的IoProcessor 線程會建立3 個。這也就是說一個IoAcceptor 或者IoConnector 預設會關聯一個IoProcessor 池,這個池中有3 個IoProcessor。因為IO 操作耗費資源,是以這裡使用IoProcessor 池來完成資料的讀寫操作,有助于提高性能。這也就是前面說的IoAccetor、IoConnector 使用一個Selector,而IoProcessor 使用自己單獨的Selector 的原因。

那麼為什麼IoProcessor 池中的IoProcessor 數量隻比CPU 的核數大1 呢?因為IO 讀寫操作是耗費CPU 的操作,而每一核CPU 同時隻能運作一個線程,是以IoProcessor 池中的IoProcessor 的數量并不是越多越好。這個IoProcessor 的數量可以調整,如下所示:

IoAcceptor acceptor=new NioSocketAcceptor(5);

IoConnector connector=new NioSocketConnector(5);

這樣就會将IoProcessor 池中的數量變為5 個,也就是說可以同時處理5 個讀寫操作。還記得前面說過Mina 的解碼器要使用IoSession 儲存狀态變量,而不是Decoder 本身,這是因為Mina 不保證每次執行doDecode()方法的都是同一個IoProcessor 這句話嗎?其實這個問題的根本原因是IoProcessor 是一個池,每次IoSession 進入空閑狀态時(無讀些資料發生),IoProcessor 都會被回收到池中,以便其他的IoSession 使用,是以當IoSession從空閑狀态再次進入繁忙狀态時,IoProcessor 會再次配置設定給其一個IoProcessor 執行個體,而此時已經不能保證還是上一次繁忙狀态時的那個IoProcessor 了。你還會發現IoAcceptor 、IoConnector 還有一個構造方法, 你可以指定一個java.util.concurrent.Executor 類作為線程池對象,那麼這個線程池對象是做什麼用的呢?其實就是用于建立(1.)、(2.)中的用于監聽是否有TCP 連接配接建立的那個線程,預設情況下,使用Executors.newCachedThreadPool()方法建立Executor 執行個體,也就是一個無界的線程池(具體内容請參看JAVA 的并發庫)。大家不要試圖改變這個Executor 的執行個體,也就是使用内置的即可,否則可能會造成一些莫名其妙的問題,譬如:性能在某個通路量級别時,突然下降。因為無界線程池是有多少個Socket 建立,就配置設定多少個線程,如果你改為Executors 的其他建立線程池的方法,建立了一個有界線程池,那麼一些請求将無法得到及時響應,進而出現一些問題。

下面我們完整的綜述一下Mina 的工作流程:

(1.) 當 IoService 執行個體建立的時候,同時一個關聯在IoService 上的IoProcessor 池、線程池也被建立;

(2.) 當 IoService 建立套接字(IoAcceptor 的bind()或者是IoConnector 的connect()方法被調用)時,IoService 從線程池中取出一個線程,監聽套接字端口;

(3.) 當 IoService 監聽到套接字上有連接配接請求時,建立IoSession 對象,從IoProcessor池中取出一個IoProcessor 執行個體執行這個會話通道上的過濾器、IoHandler;

(4.) 當這條IoSession 通道進入空閑狀态或者關閉時,IoProcessor 被回收。

上面說的是Mina 預設的線程工作方式,那麼我們這裡要講的是如何配置IoProcessor 的多線程工作方式。因為一個IoProcessor 負責執行一個會話上的所有過濾器、IoHandler,也就是對于IO 讀寫操作來說,是單線程工作方式(就是按照順序逐個執行)。假如你想讓某個事件方法(譬如:sessionIdle()、sessionOpened()等)在單獨的線程中運作(也就是非IoProcessor 所在的線程),那麼這裡就需要用到一個ExecutorFilter 的過濾器。你可以看到IoProcessor 的構造方法中有一個參數是java.util.concurrent.Executor,也就是可以讓IoProcessor 調用的過濾器、IoHandler 中的某些事件方法線上程池中配置設定的線程上獨立運作,而不是運作在IoProcessor 所在的線程。