EventLoop 介面
Netty 是基於 Java NIO 的,因此 Channel 也有其生命週期,處理一個連線在其生命週期內發生的事件是所有網路框架的基本功能。通常來說,我們使用一個執行緒來處理一個連線,該連線的生命週期的某一事件就緒,則會呼叫對應的事件處理邏輯
在 Netty 中,一個 EventLoop 負責處理一個 Channel 的生命週期事件。在程式碼設計上,EventLoop 間接實現了 JUC 中的 ExecutorService 和 ScheduleExecutorService 兩個介面,因此其具有了執行緒池的特性。一個 EventLoop 將由一個永遠不會改變的 Thread 驅動,EventLoop 由一個 Selector 和 TaskQueue 組成,Selector 會選擇 Channel 生命週期中某一事件,並由 EventLoop 所在的執行緒選擇對應的 ChannelHandler 進行處理。除此之外,使用者還可以手動提交任務給 EventLoop,以立即執行或排程執行
任務排程
有時候,你希望在 Channel 中排程一個任務以立即執行、稍後執行或者週期性執行。然而,這些任務有可能會造成長時間的阻塞,如果寫在 ChannelHandler 裡面,則有可能會阻塞整個執行鏈。因此,我們可以把這些任務提交給 EventLoop,EventLoop 會把這些任務放入 TaskQueue 中,等待建立執行緒去執行,這個過程是非同步非阻塞的,不會影響到主執行鏈
Channel ch = ...;
ScheduleFuture<?> future = ch.eventLoop().schedule(
// 建立一個 Runnable 以供排程稍後執行
new Runnable() {
@Override
public void run() {
// 要執行的程式碼
System.out.println("60 second later");
}
}, 60, TimeUnit.SECONDS);
由此我們知道,EventLoop 所線上程負責一個 Channel 的整個生命週期內的所有事件。有時候,我們還會在別的執行緒去獲取一個 Channel,並向該 Channel 對應的 EventLoop 提交任務。這種非對應 Channel 所提交過來的任務,EventLoop 會把它放入任務佇列中,等待下次執行
EventLoop 的執行緒分配
服務於 Channel 的 EventLoop 包含在 EventLoopGroup 中,根據不同的傳輸實現,EventLoop 的建立和分配方式也不同
1. 非同步傳輸
非同步傳輸實現只使用了少量的 EventLoop,一個 EventLoop 可能會被多個 Channel 所共享,通過儘可能少的執行緒來支撐大量的 Channel,而不是每個 Channel 分配一個 Thread
需要注意的是,一個 EventLoop 被用於支撐多個 Channel,那麼對於所有相關聯的 Channel 來說,ThreadLocal 是一樣的,這使得它對於實現狀態追蹤等功能來說是個糟糕的選擇
2. 阻塞傳輸
每一個 Channel 都將分配給一個 EventLoop,每個 Channel 的 IO 事件都將只會被一個 Thread 處理