1.1
多線程模式
由於本項目使用的
Apache Mina
的架構進行網路通訊。當然其多線程模式也應該在
Mina
架構中體現出來。
為了理解多線程模式,首先要瞭解
Mina
的運作方式。
1.1.1
Mina
的通訊過程
此次便於解說,假設用戶端也是採用了
Mina
架構來進行,實際上本項目的用戶端只是簡單的使用了
windows
的
Socket
通訊。在當前假設下,其通訊過程如
16
所示。
圖
16 Apache Mina
的通訊過程
本項目只關注服務端,其服務端的通訊過程如下:
1
、通過
SocketAcceptor
同用戶端建立串連;
2
、連結建立之後
I/O
的讀寫交給了
I/O Processor
線程,
I/O Processor
是多線程的;
3
、通過
I/O Processor
讀取的資料經過
IoFilterChain
裡所有配置的
IoFilter
,
IoFilter
進行訊息的過濾,格式的轉換,在這個層面可以制定一些自訂的協議;
4
、最後
IoFilter
將資料交給
Handler
進行業務處理,完成了整個讀取的過程;
5
、寫入過程也是類似,只是剛好倒過來,通過
IoSession.write
寫出資料,然後
Handler
進行寫入的業務處理,處理完成後交給
IoFilterChain
,進行訊息過濾和協議的轉換,最後通過
I/O Processor
將資料寫出到
socket
通道。
1.1.2
Mina
的多級線程池
服務端的一個簡化過程如
17
所示:
圖
17
多級線程池
在基於
SocketAcceptor
的應用程式中,運行過程中
Mina
架構本身會有兩種類型的線程在運行,一種是在
SocketAcceptor
中建立的用於監聽並接收來自用戶端請求的線程,還有一類線程是處理用戶端與伺服器端
I/O
的線程,即
Processor
的處理線程。後面還有第三類,就是過濾器層的線程。這個多級的概念後面你會體會到,
Acceptor
是一級線程池,而
Processor
的線程池主要通過
ExecutorFileter
進行添加,當然可以添加多個層次的線程池。下面逐層進行講解。
1
、第一類線程池:
當調用
SocketAcceptor
的
bind
方法時,預設會建立一個名稱首碼為
SocketAcceptor
的線程,該線程負責監聽來自用戶端的請求,如果接收到用戶端的請求,它僅僅是為處理這個請求做好準備,而把具體處理請求以及
I/O
的任務代理給
SocketIoProcessor
,讓它去處理請求。這個準備過程主要是為接受到的請求建立一個
IoSession
,並構建出
IoFilter
鏈,然後把準備好的資料以及來自用戶端的請求交給
SocketIoProcessor
處理。通常
這類線程會針對每一次的
bind
調用建立一個新的線程。
請注意
“通常
”
二字,使用這兩個字說明上述的行為不是絕對的。的確這不是絕對的,這取決於
SocketAcceptor
的欄位
executor
的實現,可以通過構造方法來設定欄位
executor
的值,
executor
欄位的類型為
java.util.concurrent.Executor
。
mina
預設是用
org.apache.mina.util.NewThreadExecutor
,它會為每一個提交的任務建立一個新的線程。因為在
bind
方法中它會把實現監聽用戶端請求任務的
Runnable
提交到
executor
中去執行。注意千萬不要使用一個不建立新線程而是在原線程中執行的
Executor
,這會使程式無法監聽用戶端的請求,因為程式中的唯一線程會被
Selector.get()
方法所阻塞,詳情可以查看
SocketAcceptor
類的原始碼。
第二類線程池:
當
SocketAcceptor
收到了來自用戶端的請求,它就會把此請求丟給
SocketIoProcessor
去處理,這會建立名稱以
SocketAcceptorIoProcessor
為首碼的線程,
mina
架構在這類線程中處理
I/O
發布並處理事件。這一類線程的數量可以通過
SocketAcceptor
的建構函式來設定。具體的值可以根據應用的具體需求來決定。
作為
I/O
真正處理的線程,存在於伺服器端和用戶端,用來處理
I/O
的讀寫操作,線程的數量是可以配置的,預設最大數量是
CPU
個數
+1
。
在伺服器端中,在建立
SocketAcceptor
的時候指定
ProcessorCount
。
SocketAcceptor acceptor =
new SocketAcceptor(Runtime.getRuntime().availableProcessors() + 1, Executors.newCachedThreadPool());
NioProcessor
雖然是多線程,但是對與一個串連的時候業務處理只會使用一個線程進行處理(
Processor
線程對於一個用戶端串連只使用一個線程
NioProcessor-n
)如果
handler
的業務比較耗時,會導致
NioProcessor
線程堵塞
,在
2
個用戶端同時串連上來的時候會建立第
2
個(前提是第
1
個
NioProcessor
正在忙),建立的最大數量由
Acceptor
構造方法的時候指定。如果:一個用戶端串連同伺服器端有很多通訊,並且
I/O
的開銷不大,但是
Handler
處理的業務時間比較長,那麼需要採用獨立的線程模式,在
FilterChain
的最後增加一個
ExecutorFitler
,這個就是第三類線程池了。
第三類線程池:
上述的兩類線程是
mina
架構本身所建立的,如果你的應用每次處理請求的時間較長而又希望應用能夠有較好的響應性,那麼最好是把處理商務邏輯的任務放到一個新的線程中去執行,而不是在
mina
架構建立的線程中去執行。
mina
架構本身提供了一個過濾器
ExecutorFilter
來完成這樣的任務,它會把在此之後的過濾器以及
IoHandler
中處理商務邏輯的代碼放到一個新的線程中去執行。當
mina
架構中的第二類線程執行完此過濾器後就會立即返回,可以用於處理新的請求。如果不想使用此過濾器,還可以設定
mina
的執行緒模式來達到相同的效果,其實執行緒模式也是使用
ExecutorFilter
實現的。但需要注意的是,在
mina 2.0
版本中已經廢棄了執行緒模式。
使用類這三次線程池,效能可以得到保證了,在本項目中,主要配置了第二類線程池和第三類線程池。第二類線程池在建立
NioAcceptor
對象(以建立
TCP
監聽伺服器為例)時候,在其建構函式中體現,而這個數值需要多次測試來設定,其測試方法在國外網站有完整表述,請自行
Google
;第三類線程池設定在
Apache Mina
的過濾器層,一般而言只需要設定一層,設定在最消耗時間的業務前面,如比較複雜的解碼,或者是資料庫訪問模組。
關於共用線程池問題,
Apache Mina
有個官方說法:你可以想讓
IoServices
和
ExecutorFilters
共用一個線程池,而不是一家一個。這個是不禁止的,但是會出現很多問題,在這種情況下,除非你為
IoServices
建立一個緩衝線程池。
本人尚未考究。