本文是【位元組視覺化系列】Kafka專欄文章。
通過本文你將瞭解到時間輪演算法思想,層級時間輪,時間輪的升級和降級。
時間輪,是一種實現延遲功能(定時器)的巧妙演算法,在Netty,Zookeeper,Kafka等各種框架中,甚至Linux核心中都有用到。
本文將參考Kafka的時間輪作為例子講解。
0 設計源於生活
開始之前給大家看塊寶珀中華年曆表。
圖片來自寶珀官網
至於價格.....這個話題略過。
而時間輪,其設計正是來源於生活中的時鐘。
1 時間輪
如圖就是一個簡單的時間輪:
圖中大圓的圓心位置表示的是當前的時間,隨著時間推移, 圓心處的時間也會不斷跳動。
下面我們對著這個圖,來說說Kafka的時間輪TimingWheel。
Kafka時間輪的底層就是一個環形陣列,而陣列中每個元素都存放一個雙向連結串列TimerTaskList,連結串列中封裝了很多延時任務。
Kafka中一個時間輪TimingWheel是由20個時間格組成,wheelSize = 20;每格的時間跨度是1ms,tickMs = 1ms。參照Kafka,上圖中也用了20個灰邊小圓表示時間格,為了動畫演示可以看得清楚,我們這裡每個小圓的時間跨度是1s。
所以現在整個時間輪的時間跨度就是 tickMs * wheelSize ,也就是 20s。從0s到19s,我們都分別有一個灰邊小圓來承載。
Kafka的時間輪還有一個錶盤指標 currentTime,表示時間輪當前所處的時間。也就是圖中用黑色粗線表示的圓,隨著時間推移, 這個指標也會不斷前進;
新增定時任務
有了時間輪,現在可以往裡面新增定時任務了。我們用一個粉紅色的小圓來表示一個定時任務。
講清楚這些設定後,我們就開始新增定時任務吧。
初始的時候, 時間輪的指標定格在0。此時新增一個超時時間為2s的任務, 那麼這個任務將會插入到第二個時間格中。
當時間輪的指標到達第二個時間格時, 會處理該時間格上對應的任務。在動畫上就是讓紅色的小圓消失!
如果這個時候又插入一個延時時間為8s的任務進來, 這個任務的過期時間就是在當前時間2s的基礎上加8s, 也就是10s, 那麼這個任務將會插入到過期時間為10s的時間格中。
2 "動態"時間輪
複用時間格
為了解答上面的問題,我們先來點魔法, 讓時間輪上的時間都動起來!
其實呢,當指標定格在2s的位置時, 時間格0s, 1s和2s就已經是過期的時間格。
也就是說指標可以用來劃分過期的時間格[0,2]和未來的時間格 [3,19]。而過期的時間格可以繼續複用。比如過期的時間格0s就變成了20s, 存放過期時間為20s的任務。
3 時間輪升級
4 層級時間輪
如圖是一個兩層的時間輪:
新增定時任務
回到一開始的問題,在當前時間是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個時間格中去。