Mina工做原理分析

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

 1、整體結構數據庫

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

 

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,來加強處理流程,而不會影響後面的業務邏輯代碼。session

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

參見:http://www.cnblogs.com/xuekyo/archive/2013/03/06/2945826.html框架

          http://www.cnblogs.com/metoy/p/3593264.html

2、工做流程

整體來說Mina框架分3層:

  • I/O Service :負責處理I/O,執行IO操做;
  • I/O Filter Chain :過濾鏈。負責編碼處理,字節到數據結構或數據結構到字節的轉換等,即非業務邏輯的操做
  • I/O Handler :負責處理業務邏輯

因此要建立一個基於NIMA框架的應用程序,必須:

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

客戶端的主要邏輯思路以下:

  •  客戶端首先建立一個IOConnector 用來和服務端通訊,顧名思義這就是創建的一個鏈接對象;
  • 在這個鏈接上建立一個session, 客戶端中的業務方法能夠向session中寫入數據,數據通過Filter Chain的過濾後會發送給服務端;
  • 從服務端發回的數據也會首先通過Filter Chain的過濾,而後交給IOHandler作進一步的處理

服務端的主要邏輯思路以下:

  • IOAcceptor 監聽網絡數據包傳入的鏈接;
  • 爲每一個新的鏈接(Connection)建立一個session,同一個端口+ip的後續請求將經過session進行處理;
  • 同一個session收到的全部數據,經過過濾鏈進行過濾.經過PacketEncoder/Decoder進行有效的編碼,解碼處理(負責把底層傳輸的對象拼裝爲更高一層的對象方便後續的處理,最後傳輸的數據被交給IOHandler);
  • 最後根據本身的業務需求完成Handler的業務邏輯處理.

參見:http://www.cnblogs.com/quyongjin/archive/2013/06/08/3127222.html

     經過SocketAcceptor 同客戶端創建鏈接; 

     鏈接創建以後 I/O的讀寫交給了I/O Processor線程,I/O Processor是多線程的; 

     經過I/O Processor 讀取的數據通過IoFilterChain裏全部配置的IoFilter, IoFilter  進行消息的過濾,格式的轉換,在這個層面能夠制定一些自定義的協議; 

     最後  IoFilter 將數據交給 Handler  進行業務處理,完成了整個讀取的過程; 

     寫入過程也是相似,只是恰好倒過來,經過IoSession.write 寫出數據,而後Handler進行寫入的業務處理,處理完成後交給IoFilterChain,進行消息過濾和協議的轉換,最後經過 I/O Processor 將數據寫出到 socket 通道。

 3、工做原理

  Mina裏面是怎麼使用Java NIO和進行線程調度的呢?這是提升IO處理性能的關鍵所在。Mina的線程調度原理主要以下圖所示:

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的線程數量設置能夠知足大部分狀況下的需求,但進一步的優化則須要根據實際環境進行測試。

4、線程模型

線程模型原理

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

相關文章
相關標籤/搜索