深入淺出Netty:NioEventLoop

佔小狼發表於2016-09-09

本系列:

上一章節中,我們分析了Netty服務的啟動過程,本章節分析Netty的eventLoop是如工作的。

深入淺出Netty:NioEventLoop

NioEventLoop中維護了一個執行緒,執行緒啟動時會呼叫NioEventLoop的run方法,執行I/O任務和非I/O任務。

  • I/O任務即selectionKey中ready的事件,如accept、connect、read、write等,由processSelectedKeysOptimized或processSelectedKeysPlain方法觸發。
  • 非IO任務則為新增到taskQueue中的任務,如register0、bind0等任務,由runAllTasks方法觸發。
  • 兩種任務的執行時間比由變數ioRatio控制,預設為50,則表示允許非IO任務執行的時間與IO任務的執行時間相等。

NioEventLoop.run 方法實現

hasTasks()方法判斷當前taskQueue是否有元素。
1、 如果taskQueue中有元素,執行 selectNow() 方法,最終執行selector.selectNow(),該方法會立即返回。

2、 如果taskQueue沒有元素,執行 select(oldWakenUp) 方法,程式碼如下:

這個方法解決了Nio中臭名昭著的bug:selector的select方法導致cpu100%。
1、delayNanos(currentTimeNanos):計算延遲任務佇列中第一個任務的到期執行時間(即最晚還能延遲多長時間執行),預設返回1s。每個SingleThreadEventExecutor都持有一個延遲執行任務的優先佇列PriorityQueue,啟動執行緒時,往佇列中加入一個任務。

2、如果延遲任務佇列中第一個任務的最晚還能延遲執行的時間小於500000納秒,且selectCnt == 0(selectCnt 用來記錄selector.select方法的執行次數和標識是否執行過selector.selectNow()),則執行selector.selectNow()方法並立即返回。
3、否則執行selector.select(timeoutMillis),這個方法已經在深入淺出NIO Socket分析過。
4、如果已經存在ready的selectionKey,或者selector被喚醒,或者taskQueue不為空,或則scheduledTaskQueue不為空,則退出迴圈。
5、如果 selectCnt 沒達到閾值SELECTOR_AUTO_REBUILD_THRESHOLD(預設512),則繼續進行for迴圈。其中 currentTimeNanos 在select操作之後會重新賦值當前時間,如果selector.select(timeoutMillis)行為真的阻塞了timeoutMillis,第二次的timeoutMillis肯定等於0,此時selectCnt 為1,所以會直接退出for迴圈。
6、如果觸發了epool cpu100%的bug,會發生什麼?
selector.select(timeoutMillis)操作會立即返回,不會阻塞timeoutMillis,導致 currentTimeNanos 幾乎不變,這種情況下,會反覆執行selector.select(timeoutMillis),變數selectCnt 會逐漸變大,當selectCnt 達到閾值,則執行rebuildSelector方法,進行selector重建,解決cpu佔用100%的bug。

rebuildSelector過程:
1、通過方法openSelector建立一個新的selector。
2、將old selector的selectionKey執行cancel。
3、將old selector的channel重新註冊到新的selector中。

對selector進行rebuild後,需要重新執行方法selectNow,檢查是否有已ready的selectionKey。

方法selectNow()或select(oldWakenUp)返回後,執行方法processSelectedKeys和runAllTasks。
1、processSelectedKeys 用來處理有事件發生的selectkey,這裡對優化過的方法processSelectedKeysOptimized進行分析:

在優化過的方法中,有事件發生的selectkey存放在陣列selectedKeys中,通過遍歷selectedKeys,處理每一個selectkey,具體處理過程,會在後續進行分析。

2、runAllTasks 處理非I/O任務。
如果 ioRatio 不為100時,方法runAllTasks的執行時間只能為ioTime * (100 – ioRatio) / ioRatio,其中ioTime 是方法processSelectedKeys的執行時間。

  1. 方法fetchFromScheduledTaskQueue把scheduledTaskQueue中已經超過延遲執行時間的任務移到taskQueue中等待被執行。
  2. 依次從taskQueue任務task執行,每執行64個任務,進行耗時檢查,如果已執行時間超過預先設定的執行時間,則停止執行非IO任務,避免非IO任務太多,影響IO任務的執行。

END。

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

深入淺出Netty:NioEventLoop

相關文章