Netty事件循環(huán)EventLoop

2018-08-08 11:03 更新

事件循環(huán)的意思就是:它運(yùn)行在一個循環(huán)中,直到它停止。網(wǎng)絡(luò)框架需要需要在一個循環(huán)中為一個特定的連接運(yùn)行事件,所以這符合網(wǎng)絡(luò)框架的設(shè)計(jì)。在Netty之前,已經(jīng)有其他框架和實(shí)現(xiàn)這么做了。

下面的清單顯示了典型的 EventLoop 邏輯。請注意這是為了更好的說明這個想法而不是單單展示 Netty 實(shí)現(xiàn)本身。

Listing 14.1 Execute task in EventLoop

while (!terminated) {
    List<Runnable> readyEvents = blockUntilEventsReady(); //1
    for (Runnable ev: readyEvents) {
        ev.run(); //2
    }
}
  1. 阻塞直到事件可以運(yùn)行
  2. 循環(huán)所有事件,并運(yùn)行他們

在 Netty 中使用 EventLoop 接口代表事件循環(huán),EventLoop 是從EventExecutor 和 ScheduledExecutorService 擴(kuò)展而來,所以可以將任務(wù)直接交給 EventLoop 執(zhí)行。類關(guān)系圖如下:

Figure%2015

Figure 15.2 EventLoop class hierarchy

EventLoop 是完全由一個 Thread,從未改變。為了更合理利用資源,根據(jù)配置和可用的內(nèi)核, Netty 可以使用多個 EventLoop。

事件/任務(wù)執(zhí)行順序

一個重要的細(xì)節(jié)關(guān)于事件和任務(wù)的執(zhí)行順序是,事件/任務(wù)執(zhí)行順序按照FIFO(先進(jìn)先出)。這是必要的,因?yàn)榉駝t事件不能按順序處理,所處理的字節(jié)將不能保證正確的順序。這將導(dǎo)致問題,所以這個不是所允許的設(shè)計(jì)。

Netty 4 中的 I/O 和事件處理

Netty 使用 I/O 事件,b被各種 I/O 操作運(yùn)輸本身所觸發(fā)。 這些 I/O 操作,例如網(wǎng)絡(luò) API 的一部分,由Java 和底層操作系統(tǒng)提供。

一個區(qū)別在于,一些操作(或者事件)是由 Netty 的本身的傳輸實(shí)現(xiàn)觸發(fā)的,一些是由用戶自己。例如讀事件通常是由傳輸本身在讀取一些數(shù)據(jù)時觸發(fā)。相比之下,寫事件通常是由用戶本身,例如,當(dāng)調(diào)用 Channel.write(…)。

究竟需要做一次處理一個事件取決于事件的性質(zhì)。經(jīng)常會讀網(wǎng)絡(luò)棧的數(shù)據(jù)轉(zhuǎn)移到您的應(yīng)用程序。有時它會在另一個方向做同樣的事情,例如,把數(shù)據(jù)從應(yīng)用程序到網(wǎng)絡(luò)堆棧(內(nèi)核)發(fā)送到它的遠(yuǎn)端。但不限于這種類型的事務(wù);重要的是,所使用的邏輯是通用的,靈活地處理各種各樣的用例。

I/O 和事件處理的一個重要的事情在 Netty 4,是每一個 I/O 操作和事件總是由 EventLoop 本身處理,以及分配給 EventLoop 的 Thread。

我們應(yīng)該注意,Netty 不總是使用我們描述的線程模型(通過 EventLoop 抽象)。在下一節(jié)中,你會了解 Netty 3 中使用的線程模型。這將幫助你理解為什么現(xiàn)在用新的線程模型以及為什么使用取代了 Netty 3 中仍然使用的舊模式。

Netty 3 中的 I/O 操作

在以前的版本中,線程模型是不同的。Netty 保證只將入站(以前稱為 upstream)事件在執(zhí)行 I/O Thread 執(zhí)行 (I/O Thread 現(xiàn)在在 Netty 4 叫 EventLoop )。所有的出站(以前稱為 downstream)事件被調(diào)用Thread 處理,這可能是 I/O Thread 也可以能是其他 Thread。 這聽起來像一個好主意,但原來是容易出錯,因?yàn)樘幚?ChannelHandler需要小心的出站事件同步,因?yàn)樗鼪]有保證只有一個線程運(yùn)行在同一時間。這可能會發(fā)生如果你觸發(fā) downstream 事件同時在一個管道時;例如,您 調(diào)用 Channel.write(..) 在不同的線程。

除了需要負(fù)擔(dān)同步 ChannelHandler,這個線程模型的另一個問題是你可能需要去掉一個入站事件作為一個出站事件的結(jié)果,例如 Channel.write(..) 操作導(dǎo)致異常。在這種情況下,exceptionCaught 必須生成并拋出去。乍看之下這不像是一個問題,但我們知道, exceptionCaught 由入站事件涉及,會讓你知道問題出在哪里。問題是,事實(shí)上,你現(xiàn)在的情況是在調(diào)用 Thread 上執(zhí)行,但 exceptionCaught 事件必須交給工作線程來執(zhí)行,這樣上下文切換是必須的。

相比之下,Netty 4 新線程模型根本沒有這些問題,因?yàn)橐磺卸荚谕粋€EventLoop 在同一 Thread 中 執(zhí)行。這消除了需要同步ChannelHandler ,并且使它更容易為用戶理解執(zhí)行。

現(xiàn)在你知道 EventLoop 如何執(zhí)行任務(wù),它的時間來快速瀏覽下 Netty 的各種內(nèi)部功能。

Netty 線程模型的內(nèi)部

Netty 的內(nèi)部實(shí)現(xiàn)使其線程模型表現(xiàn)優(yōu)異,它會檢查正在執(zhí)行的 Thread 是否是已分配給實(shí)際 Channel (和 EventLoop),在 Channel 的生命周期內(nèi),EventLoop 負(fù)責(zé)處理所有的事件。

如果 Thread 是相同的 EventLoop 中的一個,討論的代碼塊被執(zhí)行;如果線程不同,它安排一個任務(wù)并在一個內(nèi)部隊(duì)列后執(zhí)行。通常是通過EventLoop 的 Channel 只執(zhí)行一次下一個事件,這允許直接從任何線程與通道交互,同時還確保所有的 ChannelHandler 是線程安全,不需要擔(dān)心并發(fā)訪問問題。

下圖顯示在 EventLoop 中調(diào)度任務(wù)執(zhí)行邏輯,這適合 Netty 的線程模型:

Figure%2015

  1. 應(yīng)在 EventLoop 中執(zhí)行的任務(wù)
  2. 任務(wù)傳遞到執(zhí)行方法后,執(zhí)行檢查來檢測調(diào)用線程是否是與分配給 EventLoop 是一樣的
  3. 線程是一樣的,說明你在 EventLoop 里,這意味著可以直接執(zhí)行的任務(wù)
  4. 線程與 EventLoop 分配的不一樣。當(dāng) EventLoop 事件執(zhí)行時,隊(duì)列的任務(wù)再次執(zhí)行一次

15.5 EventLoop execution logic/flow

設(shè)計(jì)是非常重要的,以確保不要把任何長時間運(yùn)行的任務(wù)放在執(zhí)行隊(duì)列中,因?yàn)殚L時間運(yùn)行的任務(wù)會阻止其他在相同線程上執(zhí)行的任務(wù)。這多少會影響整個系統(tǒng)依賴于 EventLoop 實(shí)現(xiàn)用于特殊傳輸?shù)膶?shí)現(xiàn)。

傳輸之間的切換在你的代碼庫中可能沒有任何改變,重要的是:切勿阻塞 I/O 線程。如果你必須做阻塞調(diào)用(或執(zhí)行需要長時間才能完成的任務(wù)),使用 EventExecutor。

下一節(jié)將講解一個在應(yīng)用程序中經(jīng)常使用的功能,就是調(diào)度執(zhí)行任務(wù)(定期執(zhí)行)。Java對這個需求提供了解決方案,但 Netty 提供了幾個更好的方案


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號