Kafka解惑之時間輪 (TimingWheel)

来源:https://www.cnblogs.com/xuexiqun784789432/archive/2018/06/06/9145197.html
-Advertisement-
Play Games

Kafka中存在大量的延遲操作,比如延遲生產、延遲拉取以及延遲刪除等。Kafka並沒有使用JDK自帶的Timer或者DelayQueue來實現延遲的功能,而是基於時間輪自定義了一個用於實現延遲功能的定時器(SystemTimer)。JDK的Timer和DelayQueue插入和刪除操作的平均時間複雜 ...


 

 

Kafka解惑之時間輪 (TimingWheel)

Kafka中存在大量的延遲操作,比如延遲生產、延遲拉取以及延遲刪除等。Kafka並沒有使用JDK自帶的Timer或者DelayQueue來實現延遲的功能,而是基於時間輪自定義了一個用於實現延遲功能的定時器(SystemTimer)。JDK的Timer和DelayQueue插入和刪除操作的平均時間複雜度為O(nlog(n)),並不能滿足Kafka的高性能要求,而基於時間輪可以將插入和刪除操作的時間複雜度都降為O(1)。時間輪的應用並非Kafka獨有,其應用場景還有很多,在Netty、Akka、Quartz、Zookeeper等組件中都存在時間輪的蹤影。

如果你想瞭解大數據的學習路線,想學習大數據知識以及需要免費的學習資料可以加群:784789432.歡迎你的加入。

參考下圖,Kafka中的時間輪(TimingWheel)是一個存儲定時任務的環形隊列,底層採用數組實現,數組中的每個元素可以存放一個定時任務列表(TimerTaskList)。TimerTaskList是一個環形的雙向鏈表,鏈表中的每一項表示的都是定時任務項(TimerTaskEntry),其中封裝了真正的定時任務TimerTask。

Kafka解惑之時間輪 (TimingWheel)

時間輪由多個時間格組成,每個時間格代表當前時間輪的基本時間跨度(tickMs)。時間輪的時間格個數是固定的,可用wheelSize來表示,那麼整個時間輪的總體時間跨度(interval)可以通過公式 tickMs × wheelSize計算得出。時間輪還有一個表盤指針(currentTime),用來表示時間輪當前所處的時間,currentTime是tickMs的整數倍。currentTime可以將整個時間輪劃分為到期部分和未到期部分,currentTime當前指向的時間格也屬於到期部分,表示剛好到期,需要處理此時間格所對應的TimerTaskList的所有任務。

若時間輪的tickMs=1ms,wheelSize=20,那麼可以計算得出interval為20ms。初始情況下表盤指針currentTime指向時間格0,此時有一個定時為2ms的任務插入進來會存放到時間格為2的TimerTaskList中。隨著時間的不斷推移,指針currentTime不斷向前推進,過了2ms之後,當到達時間格2時,就需要將時間格2所對應的TimeTaskList中的任務做相應的到期操作。

此時若又有一個定時為8ms的任務插入進來,則會存放到時間格10中,currentTime再過8ms後會指向時間格10。如果同時有一個定時為19ms的任務插入進來怎麼辦?新來的TimerTaskEntry會復用原來的TimerTaskList,所以它會插入到原本已經到期的時間格1中。總之,整個時間輪的總體跨度是不變的,隨著指針currentTime的不斷推進,當前時間輪所能處理的時間段也在不斷後移,總體時間範圍在currentTime和currentTime+interval之間。

如果此時有個定時為350ms的任務該如何處理?直接擴充wheelSize的大小麽?Kafka中不乏幾萬甚至幾十萬毫秒的定時任務,這個wheelSize的擴充沒有底線,就算將所有的定時任務的到期時間都設定一個上限,比如100萬毫秒,那麼這個wheelSize為100萬毫秒的時間輪不僅占用很大的記憶體空間,而且效率也會拉低。Kafka為此引入了層級時間輪的概念,當任務的到期時間超過了當前時間輪所表示的時間範圍時,就會嘗試添加到上層時間輪中。

參考上圖,復用之前的案例,第一層的時間輪tickMs=1ms, wheelSize=20, interval=20ms。第二層的時間輪的tickMs為第一層時間輪的interval,即為20ms。每一層時間輪的wheelSize是固定的,都是20,那麼第二層的時間輪的總體時間跨度interval為400ms。以此類推,這個400ms也是第三層的tickMs的大小,第三層的時間輪的總體時間跨度為8000ms。

對於之前所說的350ms的定時任務,顯然第一層時間輪不能滿足條件,所以就升級到第二層時間輪中,最終被插入到第二層時間輪中時間格17所對應的TimerTaskList中。如果此時又有一個定時為450ms的任務,那麼顯然第二層時間輪也無法滿足條件,所以又升級到第三層時間輪中,最終被插入到第三層時間輪中時間格1的TimerTaskList中。註意到在到期時間在[400ms,800ms)區間的多個任務(比如446ms、455ms以及473ms的定時任務)都會被放入到第三層時間輪的時間格1中,時間格1對應的TimerTaskList的超時時間為400ms。

隨著時間的流逝,當次TimerTaskList到期之時,原本定時為450ms的任務還剩下50ms的時間,還不能執行這個任務的到期操作。這裡就有一個時間輪降級的操作,會將這個剩餘時間為50ms的定時任務重新提交到層級時間輪中,此時第一層時間輪的總體時間跨度不夠,而第二層足夠,所以該任務被放到第二層時間輪到期時間為[40ms,60ms)的時間格中。再經歷了40ms之後,此時這個任務又被“察覺”到,不過還剩餘10ms,還是不能立即執行到期操作。所以還要再有一次時間輪的降級,此任務被添加到第一層時間輪到期時間為[10ms,11ms)的時間格中,之後再經歷10ms後,此任務真正到期,最終執行相應的到期操作。

設計,其本源於生活。我們常見的鐘錶就是一種具有三層結構的時間輪,第一層時間輪tickMs=1ms, wheelSize=60,interval=1min,此為秒鐘;第二層tickMs=1min,wheelSize=60,interval=1hour,此為分鐘;第三層tickMs=1hour,wheelSize為12,interval為12hours,此為時鐘。

在Kafka中第一層時間輪的參數同上面的案例一樣:tickMs=1ms, wheelSize=20, interval=20ms,各個層級的wheelSize也固定為20,所以各個層級的tickMs和interval也可以相應的推算出來。Kafka在具體實現時間輪TimingWheel時還有一些小細節:

  1. TimingWheel在創建的時候以當前系統時間為第一層時間輪的起始時間(startMs),這裡的當前系統時間並沒有簡單的調用System.currentTimeMillis(),而是調用了Time.SYSTEM.hiResClockMs,這是因為currentTimeMillis()方法的時間精度依賴於操作系統的具體實現,有些操作系統下並不能達到毫秒級的精度,而Time.SYSTEM.hiResClockMs實質上是採用了System.nanoTime()/1_000_000來將精度調整到毫秒級。也有其他的某些騷操作可以實現毫秒級的精度,但是筆者並不推薦,System.nanoTime()/1_000_000是最有效的方法。(如對此有想法,可在留言區探討。)
  2. TimingWheel中的每個雙向環形鏈表TimerTaskList都會有一個哨兵節點(sentinel),引入哨兵節點可以簡化邊界條件。哨兵節點也稱為啞元節點(dummy node),它是一個附加的鏈表節點,該節點作為第一個節點,它的值域中並不存儲任何東西,只是為了操作的方便而引入的。如果一個鏈表有哨兵節點的話,那麼線性表的第一個元素應該是鏈表的第二個節點。
  3. 除了第一層時間輪,其餘高層時間輪的起始時間(startMs)都設置為創建此層時間輪時前面第一輪的currentTime。每一層的currentTime都必須是tickMs的整數倍,如果不滿足則會將currentTime修剪為tickMs的整數倍,以此與時間輪中的時間格的到期時間範圍對應起來。修剪方法為:currentTime = startMs - (startMs % tickMs)。currentTime會隨著時間推移而推薦,但是不會改變為tickMs的整數倍的既定事實。若某一時刻的時間為timeMs,那麼此時時間輪的currentTime = timeMs - (timeMs % tickMs),時間每推進一次,每個層級的時間輪的currentTime都會依據此公式推進。
  4. Kafka中的定時器只需持有TimingWheel的第一層時間輪的引用,並不會直接持有其他高層的時間輪,但是每一層時間輪都會有一個引用(overflowWheel)指向更高一層的應用,以此層級調用而可以實現定時器間接持有各個層級時間輪的引用。

關於時間輪的細節就描述到這裡,各個組件中時間輪的實現大同小異。讀者讀到這裡是否會好奇文中一直描述的一個情景——“隨著時間的流逝”或者“隨著時間的推移”,那麼在Kafka中到底是怎麼推進時間的呢?類似採用JDK中的scheduleAtFixedRate來每秒推進時間輪?顯然這樣並不合理,TimingWheel也失去了大部分意義。

Kafka中的定時器藉助了JDK中的DelayQueue來協助推進時間輪。具體做法是對於每個使用到的TimerTaskList都會加入到DelayQueue中,“每個使用到的TimerTaskList”特指有非哨兵節點的定時任務項TimerTaskEntry的TimerTaskList。DelayQueue會根據TimerTaskList對應的超時時間expiration來排序,最短expiration的TimerTaskList會被排在DelayQueue的隊頭。Kafka中會有一個線程來獲取DelayQueue中的到期的任務列表,有意思的是這個線程所對應的名稱叫做“ExpiredOperationReaper”,可以直譯為“過期操作收割機”,和“SkimpyOffsetMap”有的一拼。當“收割機”線程獲取到DelayQueue中的超時的任務列表TimerTaskList之後,既可以根據TimerTaskList的expiration來推進時間輪的時間,也可以就獲取到的TimerTaskList執行相應的操作,對立面的TimerTaskEntry該執行過期操作的就執行過期操作,該降級時間輪的就降級時間輪。

讀者讀到這裡或許又非常的困惑,文章開頭明確指明的DelayQueue不適合Kafka這種高性能要求的定時任務,為何這裡還要引入DelayQueue呢?註意對於定時任務項TimerTaskEntry插入和刪除操作而言,TimingWheel時間複雜度為O(1),性能高出DelayQueue很多,如果直接將TimerTaskEntry插入DelayQueue中,那麼性能顯然難以支撐。就算我們根據一定的規則將若幹TimerTaskEntry劃分到TimerTaskList這個組中,然後再將TimerTaskList插入到DelayQueue中,試想下如果這個TimerTaskList中又要多添加一個TimerTaskEntry該如何處理?對於DelayQueue而言,這類操作顯然變得力不從心。

分析到這裡可以發現,Kafka中的TimingWheel專門用來執行插入和刪除TimerTaskEntry的操作,而DelayQueue專門負責時間推進的任務。再試想一下,DelayQueue中的第一個超時任務列表的expiration為200ms,第二個超時任務為840ms,這裡獲取DelayQueue的隊頭只需要O(1)的時間複雜度。如果採用每秒定時推進,那麼獲取到第一個超時的任務列表時執行的200次推進中有199次屬於“空推進”,而獲取到第二個超時任務時有需要執行639次“空推進”,這樣會無故空耗機器的性能資源,這裡採用DelayQueue來輔助以少量空間換時間,從而做到了“精準推進”。Kafka中的定時器真可謂是“知人善用”,用TimingWheel做最擅長的任務添加和刪除操作,而用DelayQueue做最擅長的時間推進工作,相輔相成。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 索引類似大學圖書館建書目索引,可以提高數據檢索的效率,降低資料庫的IO成本。MySQL在300萬條記錄左右性能開始逐漸下降,雖然官方文檔說500~800w記錄,所以大數據量建立索引是非常有必要的。MySQL提供了Explain,用於顯示SQL執行的詳細信息,可以進行索引的優化。 一、導致SQL執行慢 ...
  • Redis主要數據結構:簡單動態字元串(SDS)、雙端鏈表、字典、跳躍表、整數集合、壓縮列表和快速列表; 一、簡單動態字元串(SDS): Redis沒有直接使用C語言中的傳統的位元組數組保存字元串,而是自行構建了簡單動態字元串(SDS),C字元串只是作為簡單動態字元串(SDS)的字面量,用於在無需對字 ...
  • 1、資料庫簡介-》解決的問題:持久化存儲,優化讀寫,保證數據的有效性-》關係型資料庫: 基於E-R模型(數據關係模型) 使用sql語言進行操作-》分類:文檔型sqlite,服務型-》資料庫設計 三範式:列不可拆分,唯一標識,引用主鍵 關係及存儲: 1對1:1個對象A對應著1個對象B,1個對象B對應著 ...
  • 原本以為 正則表達式裡面的特殊\d匹配數字放到sql語句裡面也是適用的,沒想到一直不匹配。但是放到編程語言java或者js裡面又匹配。看了一下原來sql對正則的支持沒有那麼全面。一定要用[0-9]代表數字。原因的話我猜是sql是一門查詢語言,設計原則不應該有和編程語言靠近的東西 ...
  • 第1章 初探大數據 本章將介紹為什麼要學習大數據、如何學好大數據、如何快速轉型大數據崗位、本項目實戰課程的內容安排、本項目實戰課程的前置內容介紹、開發環境介紹。同時為大家介紹項目中涉及的Hadoop、Hive相關的知識 第2章 Spark及其生態圈概述 Spark作為近幾年最火爆的大數據處理技術,是 ...
  • 實驗案例一:驗證索引的作用 1、首先創建一個數據量大的表,名稱為“學生表”,分別有三列,學號,姓名和班級,如下圖所示,學號為自動編號,班級為預設值“一班”。 2、向表中插入大量數據,數據越多,驗證索引的效果越好。 使用語句完成:While 1>0 Insert into 學生表(姓名) values ...
  • 數據中心是智慧保護區的信息倉庫,為整個信息化平臺的高效運營提供豐富的數據源,全面支撐保護區各項應用。數據中心主要是通過保護區基礎資料庫建設工程的實施,通過規範生物多樣性信息分類、採集存儲、處理、交換和服務的標準,建成基礎資料庫。按照統一標準、共建共用、互聯互通的原則,以高端、集約、安全為目標,加強林 ...
  • 佛曰:“不可說,說既是錯”,所以本篇也是錯! 技術人的世界是一塊凈土,也許世界並不該這麼複雜。 ——KK 這篇感悟也許帶著些許悲涼、無奈,也許又帶著激情滿滿,也許還透著辛酸。 技術男 很多人眼裡的技術宅是[傻傻的] [情商低的] [不愛說話的][邋遢的]....當然也有一些好詞 [踏實] [誠實] ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...