天天看點

面試Java後端卻問我時間輪算法,面試官沒想到我看過Dubbo源碼!(下)4 時間輪指針一次轉動的執行流程5 定時任務應用

HashedWheelTimer

Timer

接口的實作,通過時間輪算法實作了一個定時器。

職能

根據目前時間輪指針標明對應

HashedWheelBucket

槽,從連結清單頭部開始疊代,計算每個

HashedWheelTimeout

定時任務:

  • 屬于目前時鐘周期則取出運作
  • 不屬于則将其剩餘的時鐘周期數減一

核心域

workerState

時間輪目前所處狀态,三個可選值,由 

AtomicIntegerFieldUpdater

 實作其原子地修改。

面試Java後端卻問我時間輪算法,面試官沒想到我看過Dubbo源碼!(下)4 時間輪指針一次轉動的執行流程5 定時任務應用

startTime

目前時間輪的啟動時間,送出到該時間輪的定時任務的 deadline 字段值均以該時間戳為起點進行計算。

面試Java後端卻問我時間輪算法,面試官沒想到我看過Dubbo源碼!(下)4 時間輪指針一次轉動的執行流程5 定時任務應用

wheel

時間輪環形隊列,每個元素都是一個槽。當指定時間輪槽數為 n 時,會向上取最靠近 n 的 2 次幂值

面試Java後端卻問我時間輪算法,面試官沒想到我看過Dubbo源碼!(下)4 時間輪指針一次轉動的執行流程5 定時任務應用

timeouts、cancelledTimeouts

HashedWheelTimer 會在處理 HashedWheelBucket 的雙向連結清單前,先處理這倆隊列的資料:

timeouts 隊列

緩沖外部送出時間輪中的定時任務

cancelledTimeouts 隊列

暫存取消的定時任務

面試Java後端卻問我時間輪算法,面試官沒想到我看過Dubbo源碼!(下)4 時間輪指針一次轉動的執行流程5 定時任務應用

tick

位于 

HashedWheelTimer$Worker

 ,時間輪的指針,步長為 1 的單調遞增計數器

面試Java後端卻問我時間輪算法,面試官沒想到我看過Dubbo源碼!(下)4 時間輪指針一次轉動的執行流程5 定時任務應用

mask

掩碼, 

mask = wheel.length - 1

,執行 

ticks & mask

 便能定位到對應的時鐘槽

面試Java後端卻問我時間輪算法,面試官沒想到我看過Dubbo源碼!(下)4 時間輪指針一次轉動的執行流程5 定時任務應用

ticksDuration

時間指針每次加 1 所代表的實際時間,機關為納秒。

面試Java後端卻問我時間輪算法,面試官沒想到我看過Dubbo源碼!(下)4 時間輪指針一次轉動的執行流程5 定時任務應用

pendingTimeouts

目前時間輪剩餘的定時任務總數。

面試Java後端卻問我時間輪算法,面試官沒想到我看過Dubbo源碼!(下)4 時間輪指針一次轉動的執行流程5 定時任務應用

workerThread

時間輪内部真正執行定時任務的線程。

面試Java後端卻問我時間輪算法,面試官沒想到我看過Dubbo源碼!(下)4 時間輪指針一次轉動的執行流程5 定時任務應用

worker

真正執行定時任務的邏輯封裝這個 Runnable 對象中。

面試Java後端卻問我時間輪算法,面試官沒想到我看過Dubbo源碼!(下)4 時間輪指針一次轉動的執行流程5 定時任務應用

newTimeout()

送出定時任務,在定時任務進入到 timeouts 隊列之前會先調用 start() 方法啟動時間輪,其中會完成下面兩個關鍵步驟:

  1. 确定時間輪的 startTime 字段
  2. 啟動 workerThread 線程,開始執行 worker 任務。

之後根據 startTime 計算該定時任務的 deadline,最後才能将定時任務封裝成

HashedWheelTimeout

并添加到

timeouts

隊列。

4 時間輪指針一次轉動的執行流程

HashedWheelTimer$Worker.run()

  1. 時間輪指針轉動,時間輪周期開始
  2. 清理使用者主動取消的定時任務,這些定時任務在使用者取消時,記錄到 cancelledTimeouts 隊列中。在每次指針轉動的時候,時間輪都會清理該隊列
  3. 将緩存在 timeouts 隊列中的定時任務轉移到時間輪中對應的槽中
  4. 根據目前指針定位對應槽,處理該槽位的雙向連結清單中的定時任務
  5. 檢測時間輪的狀态。如果時間輪處于運作狀态,則循環執行上述步驟,不斷執行定時任務。如果時間輪處于停止狀态,則執行下面的步驟擷取到未被執行的定時任務并加入 

    unprocessedTimeouts

     隊列:周遊時間輪中每個槽位,并調用 

    clearTimeouts

    () 方法;對timeouts 隊列中未被加入槽中循環調用 poll()
  1. 最後再次清理 cancelledTimeouts 隊列中使用者主動取消的定時任務。

5 定時任務應用

并不直接用于周期性操作,而是隻向時間輪送出執行單次的定時任務,在上一次任務執行完成的時候,調用 newTimeout() 方法再次送出目前任務,這樣就會在下個周期執行該任務。即使在任務執行過程中出現了 GC、I/O 阻塞等情況,導緻任務延遲或卡住,也不會有同樣的任務源源不斷地送出進來,導緻任務堆積。

Dubbo 時間輪應用主要在如下方面:

失敗重試, 例如,Provider 向注冊中心進行注冊失敗時的重試操作,或是 Consumer 向注冊中心訂閱時的失敗重試等

周期性定時任務, 例如,定期發送心跳請求,請求逾時的處理,或是網絡連接配接斷開後的重連機制

參考

https://zhuanlan.zhihu.com/p/32906730