一張圖理解Kafka時間輪(TimingWheel),看不懂算我輸!

本文是【字節可視化系列】Kafka專欄文章。算法

經過本文你將瞭解到時間輪算法思想,層級時間輪,時間輪的升級和降級。


時間輪,是一種實現延遲功能(定時器)的巧妙算法,在Netty,Zookeeper,Kafka等各類框架中,甚至Linux內核中都有用到。數組


本文將參考Kafka的時間輪做爲例子講解。markdown


0 設計源於生活

開始以前給你們看塊寶珀中華年曆表。框架

圖片來自寶珀官網
oop


這款手錶的錶盤融合了中華曆法中各類博大精深的計時元素。

上方位置的小錶盤顯示時辰數字及字符,24小時一週期;年份視窗顯示當年所屬生肖,12年一週期;

左邊位置顯示農曆月,12個月一週期; 農曆日, 30天一週期;

右邊位置顯示五行元素和十天干,10年一週期;

下方的錶盤顯示月相盈虧。

至於價格.....這個話題略過。動畫


而時間輪,其設計正是來源於生活中的時鐘。spa


1 時間輪

如圖就是一個簡單的時間輪:
線程


圖中大圓的圓心位置表示的是當前的時間,隨着時間推移, 圓心處的時間也會不斷跳動。設計


下面咱們對着這個圖,來講說Kafka的時間輪TimingWheel。3d


Kafka時間輪的底層就是一個環形數組,而數組中每一個元素都存放一個雙向鏈表TimerTaskList,鏈表中封裝了不少延時任務。


Kafka中一個時間輪TimingWheel是由20個時間格組成,wheelSize = 20;每格的時間跨度是1ms,tickMs = 1ms。參照Kafka,上圖中也用了20個灰邊小圓表示時間格,爲了動畫演示能夠看得清楚,咱們這裏每一個小圓的時間跨度是1s。


因此如今整個時間輪的時間跨度就是 tickMs * wheelSize ,也就是 20s。從0s到19s,咱們都分別有一個灰邊小圓來承載。


Kafka的時間輪還有一個錶盤指針 currentTime,表示時間輪當前所處的時間。也就是圖中用黑色粗線表示的圓,隨着時間推移, 這個指針也會不斷前進;



添加定時任務

有了時間輪,如今能夠往裏面添加定時任務了。咱們用一個粉紅色的小圓來表示一個定時任務。


這裏先講一下設定,每個定時任務都有延時時間 delayTime,和過時時間 ExpiredTime
好比當前時間是10s,咱們添加了個延時時間爲2s的任務,那麼這個任務的過時時間就是12s,也就是當前時間10s再走兩秒,變成了12s的時候,就到了觸發這個定時任務的時間。

而時間輪上表明時間格的灰邊小圓上顯示的數字,能夠理解爲任務的過時時間。


講清楚這些設定後,咱們就開始添加定時任務吧。


初始的時候, 時間輪的指針定格在0。此時添加一個超時時間爲2s的任務, 那麼這個任務將會插入到第二個時間格中。



當時間輪的指針到達第二個時間格時, 會處理該時間格上對應的任務。在動畫上就是讓紅色的小圓消失!


若是這個時候又插入一個延時時間爲8s的任務進來, 這個任務的過時時間就是在當前時間2s的基礎上加8s, 也就是10s, 那麼這個任務將會插入到過時時間爲10s的時間格中。




2 "動態"時間輪

到目前爲止,一切都很好理解。

那麼若是在當前時間是2s的時候, 插入一個延時時間爲19s的任務時, 這個任務的過時時間就是在當前時間2s的基礎上加19s, 也就是21s。

請看下圖,當前的時間輪是沒有過時時間爲21s的時間格。這個任務將會插入到過時時間爲1s的時間格中,這是怎麼回事呢?

複用時間格


爲了解答上面的問題,咱們先來點魔法, 讓時間輪上的時間都動起來!

其實呢,當指針定格在2s的位置時, 時間格0s, 1s和2s就已是過時的時間格。


也就是說指針能夠用來劃分過時的時間格[0,2]和將來的時間格 [3,19]。而過時的時間格能夠繼續複用。好比過時的時間格0s就變成了20s, 存放過時時間爲20s的任務。


理解了時間格的複用以後,再看回剛剛的例子,當前時間是2s時,添加延時時間爲19s的任務,那麼這個任務就會插入到過時時間爲21s的時間格中。

3 時間輪升級

下面,新的問題來了,請坐好扶穩。

若是在當前時間是2s的時候, 插入一個延時時間爲22s的任務, 這個任務的過時時間就是在2s的基礎上加22s,也就是24s。


顯然當前時間輪是沒法找到過時時間格爲24秒的時間格,由於當前過時時間最大的時間格纔到21s。並且咱們也沒辦法像前面那樣再複用時間格,由於除了過時時間爲2s的時間格,其餘的時間格都還沒過時呢。當前時間輪沒法承載這個定時任務, 那麼應該怎麼辦呢?

固然咱們能夠選擇擴展時間輪上的時間格, 可是這樣一來,時間輪就失去了意義。

是時候要升級時間輪了!

咱們先來理解下多層時間輪之間的聯繫。


4 層級時間輪

如圖是一個兩層的時間輪:

第二層時間輪也是由20個時間格組成, 每一個時間格的跨度是20s。

圖中展現了每一個時間格對應的過時時間範圍, 咱們能夠清晰地看到, 第二層時間輪的第0個時間格的過時時間範圍是 [0,19]。也就是說, 第二層時間輪的一個時間格就能夠表示第一層時間輪的全部(20個)時間格;

爲了進一步理清第一層時間輪和第二層時間輪的關係, 咱們拉着時間的小手, 一塊兒觀看下面的動圖:

能夠看到,第二層時間輪一樣也有本身的指針, 每當第一層時間輪走完一個週期,第二層時間輪的指針就會推動一格。


添加定時任務

回到一開始的問題,在當前時間是2s的時候, 插入一個延時時間爲22s的任務,該任務過時時間爲24s。

當第一層時間輪容納不下時,進入第二層時間輪,並插入到過時時間爲[20,39]的時間格中。


咱們再來個例子,若是在當前時間是2s的時候, 插入一個延時時間爲350s的任務, 這個任務的過時時間就是在2s的基礎上加350s,也就是352s。

從圖中能夠看到,該任務插入到第二層時間輪過時時間爲[340,359]s的時間格中,也就是第17格的位置。



5 "動態"層級時間輪

一般來講, 第二層時間輪的第0個時間格是用來表示第一層時間輪的, 這一格是存放不了任務的, 由於超時時間0-20s的任務, 第一層時間輪就能夠處理了。


可是! 事情每每沒這麼簡單, 咱們時間輪上的時間格都是能夠複用的! 那麼這在第二層時間輪上又是怎麼體現的呢?


下面是魔法時間, 咱們讓時間輪上的過時時間都動起來!


從圖中能夠看到,當第一層時間輪的指針定格在1s時,超時時間0s的時間格就過時了。而這個時候,第二層時間輪第0個時間格的時間範圍就從[0,19]分爲了過時的[0],和未過時的[1,19]。而過時的[0]就會被新的過時時間[400]複用。


第二層時間輪第0個時間格的過時時間範圍演變以下:

[0-19]

[400][1,19]

[400,401][2,19]

......

[400,419]


因此,若是在當前時間是2s的時候, 插入一個延時時間爲399s的任務, 這個任務的過時時間就是在2s的基礎上加399s,也就是401s。如圖,這個任務仍是會插到第二層時間輪第0個時間格中去。


6 時間輪降級

仍是用回這個你們都已經耳熟能詳的例子,在當前時間是2s的時候, 插入一個延時時間爲22s的任務,該任務過時時間爲24s。最後進入第二層時間輪,並插入到過時時間爲[20,39]的時間格中。

當二層時間輪上的定時任務到期後,時間輪是怎麼作的呢?


從圖中能夠看到,隨着當前時間從2s繼續往前推動,一直到20s的時候,總共通過了18s。此時第二層時間輪中,超時時間爲[20-39s]的時間格上的任務到期。

本來超時時間爲24s的任務會被取出來,從新加入時間輪。此時該定時任務的延時時間從本來的22s,到如今還剩下4s(22s-18s)。最後停留在第一層時間輪超時時間爲24s的時間格,也就是第4個時間格。

隨着當前時間繼續推動,再通過4s後,該定時任務到期被執行。


從這裏能夠看出時間輪的巧妙之處,兩層時間輪只用了40個數組元素,卻能夠承載[0-399s]的定時任務。而三層時間輪用60個數組元素,就能夠承載[0-7999s]的定時任務!


7 時間輪的推動

從動畫中能夠注意到, 隨着時間推動, 時間輪的指針循環往復地定格在每個時間格上, 每一次都要判斷當前定格的時間格里是否是有任務存在;

其中有不少時間格都是沒有任務的, 指針定格在這種空的時間格中, 就是一次"空推動";

好比說, 插入一個延時時間400s的任務, 指針就要執行399次"空推動", 這是一種浪費!

那麼Kafka是怎麼解決這個問題的呢?這就要從延遲隊列DelayQueue開始講起了!
時間輪搭配延遲隊列DelayQueue,會發生什麼化學反應呢?請關注公衆號【字節武裝】後續更新。


往期回顧



相關文章
相關標籤/搜索