1. 程式人生 > >Mina工作原理及業務流程分析

Mina工作原理及業務流程分析

Mina是Apache社群維護的一個開源的高效能IO框架, 在業界內久經考驗, 廣為使用. Mina與後來興起的高效能IO新貴Netty一樣, 都是韓國人Trustin Lee 的大作, 二者的設計理念是極為相似. 在作為一個強大的開發工具的同時, 這兩個人框架的優雅設計和不俗的表現, 有很多地方是值得學習和借鑑的.

一. 總體結構

Mina的底層依賴的主要是Java NIO庫, 上層提供的是基於事件的非同步介面. 其整體的結構如下:

image

IoService

最底層的是IoService, 負責具體的IO相關工作. 這一層的典型代表有IoSocketAcceptor和IoScoketChannel,分別對應TCP協議下的服務端和客戶端的IoService. IoService的意義在於隱藏底層IO的細節, 對上提供統一的基於事件的非同步IO介面. 每當有資料到達時, IoService會先呼叫底層IO介面讀取資料, 封裝成IOBuffer, 之後以事件的形式通知上層程式碼, 從而將Java NIO的同步IO介面轉化成了非同步IO, 所以從圖上看, 進來的low
-level IO經過IoService層後變成IOEvent.

具體的程式碼可以參考org.apache.mina.core.polling.AbstractPollingloProcessor的私有內部類Processor.

IoFilterChain

Mina的設計理念之一就是業務程式碼和資料包處理程式碼分離, 業務程式碼只專注於業務邏輯, 其他的邏輯如:資料包的解析,封裝,過濾等則交由IoFilterChain來處理. IoFilterChain可以看成是Mina處理流程的擴充套件點. 這樣的劃分似的結構更加清晰, 程式碼分工更明確. 開發者通過網Chain中新增IoFilter,來增強處理流程, 而不會影響後面的業務邏輯程式碼.

IoHandler

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

IoSession

IoSession是對底層連線的封裝, 一個IoSession對應於一個底層的IO連線(在Mina中UDP也被抽象成了連線). 通過IoSession, 可以獲取當前連線相關的上下文資訊, 以及向遠端peer傳送資料. 傳送資料其實也是個非同步的過程. 傳送的操作首先會逆向穿過IoFilterChain, 到達I噢Service. 但I噢Service上並不會直接呼叫底層IO介面來將資料傳送出去, 而是會將該次呼叫封裝成一個WriteRequest, 放入Session的writeRequestQueue中, 最後由IoProcessor執行緒統一排程flush出去. 所以傳送操作並不會引起上層呼叫執行緒的阻塞.

具體程式碼可參考rog.apache.mina.core.filterchain.DefaultloFilterChain的內部類HeadFilter的filterWrite的方法.

二. 工作流程

總體來講Mina框架分三層:

  • I/O Service : 負責處理I/O, 執行IO操作
  • I/O Filter Chain : 過濾鏈. 負責編碼處理, 位元組到資料結構或資料結構到位元組的轉換等, 即非業務邏輯的操作
  • I/O Handler : 負責處理業務邏輯

所以要建立一個基於MINA框架的應用程式,必須:

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

image

客戶端的只要邏輯思路如下:
  • 客戶端首先建立一個IOConnector用來和服務端通訊, 顧名思義這就是建立的一個連線物件
  • 在這個連線上建立一個session, 客戶端中的業務方法可以向session中寫入資料,資料經過Filter Chain的過濾後會傳送給服務端.
  • 從服務端發回的資料也會首先經過Filter Chain的過濾, 然後交給IOHandler做進一步的處理
服務端的主要邏輯思路如下:
  • IOAcceptor監聽網路資料包傳入的連線
  • 為每個新的連線(Connection)建立一個session, 同一個埠+ip的後續請求將通過session進行處理
  • 同一個session收到的所有資料, 通過過濾鏈進行過濾, 通過PacketEncoder/Decoder進行有效的編碼, 解碼處理(負責把底層傳輸的物件拼裝為更高一層的物件方便後續的處理, 租後傳輸的資料交給IOHandler)
  • 最後根據自己的業務需求完成Handler的業務邏輯處理

    1. 通過SocketAcceptor 同客戶端建立連線;
    2. 連線建立之後 I/O的讀寫交給了I/O Processor執行緒,I/O Processor是多執行緒的;
    3. 通過I/O Processor 讀取的資料經過IoFilterChain裡所有配置的IoFilter, IoFilter 進行訊息的過濾,格式的轉換,在這個層面可以制定一些自定義的協議;
    4. 最後 IoFilter 將資料交給 Handler 進行業務處理,完成了整個讀取的過程;

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

三. 工作原理

MINA裡面是怎麼使用Java NIO和進行執行緒排程? 這是提高IO處理效能的關鍵所在. MINA的執行緒排程原理主要如下圖所示:

image

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的執行緒模型主要有一下這幾種形式:

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 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的多個請求能被並行地處理,來進一步提高吞吐量。

引用地址