自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

高性能調(diào)度系統(tǒng)設(shè)計(jì)總結(jié)

系統(tǒng)
本文詳細(xì)探討了調(diào)度模塊在多種系統(tǒng)中的應(yīng)用及其重要性,并深入分析了調(diào)度系統(tǒng)的通用流程,包括任務(wù)生成、任務(wù)存儲(chǔ)、定時(shí)掃描和路由實(shí)例等關(guān)鍵步驟。

作者 | akinwang

調(diào)度模塊在很多系統(tǒng)中都是常用的模塊,比如實(shí)習(xí)生的每天簽到郵件,預(yù)約銀行的業(yè)務(wù)短信,學(xué)習(xí)通的上課通知,騰訊視頻push中臺(tái)的任務(wù)下發(fā),調(diào)度系統(tǒng)在中間起到關(guān)鍵作用。

一、那么什么是調(diào)度?

本質(zhì)就是通過一些自定義策略,定時(shí)或者周期性的去觸發(fā)某些事件,比如去發(fā)起一次rpc調(diào)用,和下游進(jìn)行一次通信。

通用流程

調(diào)度行為可以抽象成以下幾步:

  • 任務(wù)生成。
  • 任務(wù)存儲(chǔ)。
  • 任務(wù)觸發(fā)。
  • 路由實(shí)例。

如果能做好這幾步,那么一個(gè)高性能的調(diào)度系統(tǒng)也就誕生了,而每一步的技術(shù)選型,都和未來系統(tǒng)想要達(dá)成的目標(biāo)(高精度,高可用),有著密不可分的關(guān)系,下面我會(huì)針對(duì)這幾步進(jìn)行分析。

后面會(huì)舉出一些實(shí)際的系統(tǒng)進(jìn)行說明。

二、任務(wù)生成

  • 單次任務(wù)生成:對(duì)于單次任務(wù),通常由管理臺(tái)直接發(fā)起請(qǐng)求,將任務(wù)信息寫入系統(tǒng)。
  • 周期性任務(wù)生成:周期性任務(wù)生成類似于打點(diǎn)計(jì)時(shí)器。每當(dāng)任務(wù)觸發(fā)時(shí),系統(tǒng)會(huì)計(jì)算出未來需要觸發(fā)的任務(wù)時(shí)間列表。例如,對(duì)于每小時(shí)執(zhí)行的任務(wù),系統(tǒng)會(huì)在第二天生成24個(gè)整點(diǎn)任務(wù)。
  • 推送系統(tǒng)任務(wù)生成:對(duì)于推送系統(tǒng)任務(wù),系統(tǒng)會(huì)根據(jù)用戶過去的行為畫像預(yù)測其最有可能點(diǎn)擊的時(shí)間區(qū)間。在第二天到來之前,系統(tǒng)會(huì)預(yù)先計(jì)算并生成第二天各個(gè)時(shí)間點(diǎn)的推送任務(wù)。

三、任務(wù)存儲(chǔ)

任務(wù)存儲(chǔ)的思考分為兩個(gè)方面,第一,是用什么數(shù)據(jù)結(jié)構(gòu)存。第二是用什么類型的db去存。

對(duì)于高性能調(diào)度系統(tǒng)而言,主要看重范圍查詢效率,查詢的qps,分布式鎖的表現(xiàn)。

至于這里為什么提到了分布式鎖,是因?yàn)樵诩耗J较拢囊慌_(tái)實(shí)例去執(zhí)行任務(wù)掃描這一過程依賴于分布式鎖的搶占。

1.數(shù)據(jù)結(jié)構(gòu)分析

列表:

  • 插入:O(1)
  • 查詢:O(logN)
  • 實(shí)現(xiàn):我們可以利用列表去存即將觸發(fā)的任務(wù)信息,通過遍歷的方式去取到大于當(dāng)前時(shí)間的任務(wù),并且觸發(fā)。
  • 優(yōu)點(diǎn):實(shí)現(xiàn)簡單
  • 缺點(diǎn):但需要對(duì)所有任務(wù)進(jìn)行遍歷,查出很多無效數(shù)據(jù),極其低效。

大頂堆:

  • 刪除:O(logN)
  • 查詢:O(1)
  • 實(shí)現(xiàn):我們也可以利用大頂堆的性質(zhì),每次都取堆頂元素,如果堆頂元素大于當(dāng)前時(shí)間,那么就取最大元素。其余元素會(huì)利用大頂堆的性質(zhì),繼續(xù)浮出最大的元素,然后繼續(xù)比較。
  • 優(yōu)點(diǎn):查詢快,只會(huì)查到快到時(shí)間的任務(wù),實(shí)現(xiàn)簡單。
  • 缺點(diǎn):需要維護(hù)自身堆的性質(zhì),cpu壓力高,無法抗住高并發(fā)。

B+樹:

  • 查詢:O(logN)
  • B+樹(B-plus tree)是一種自平衡的樹數(shù)據(jù)結(jié)構(gòu),它能夠保持?jǐn)?shù)據(jù)有序,允許插入、刪除和查找操作在對(duì)數(shù)時(shí)間內(nèi)完成。B+樹特別適合于磁盤或其他直接存取輔助設(shè)備的存儲(chǔ)系統(tǒng),因?yàn)樗軌蜃畲蠡販p少I/O操作次數(shù)。

跳表:

  • 查詢:O(logN)
  • 跳表(Skip List)是一種基于有序鏈表的高效數(shù)據(jù)結(jié)構(gòu),它通過在鏈表的基礎(chǔ)上增加多級(jí)索引來實(shí)現(xiàn)快速的查找操作。跳表允許在對(duì)數(shù)時(shí)間內(nèi)完成搜索、插入和刪除操作,且插入和刪除操作不需要頻繁調(diào)整數(shù)據(jù)結(jié)構(gòu)。

小總結(jié)

總的來說,列表和大頂堆由于自身的性質(zhì),并不適合這樣的場景。對(duì)于掃表+觸發(fā)的模式,其實(shí)本質(zhì)是需要一個(gè)能高速范圍查詢的數(shù)據(jù)結(jié)構(gòu)。

B+樹和跳表都是高效的能范圍查詢數(shù)據(jù)結(jié)構(gòu),但它們各自適用于不同的場景。B+樹更適合于磁盤存儲(chǔ)和范圍查詢,而跳表則更適合于內(nèi)存中的快速查找和分布式環(huán)境。

2.數(shù)據(jù)庫分析

我們舉出基于內(nèi)存的數(shù)據(jù)庫的代表Redis和基于磁盤的數(shù)據(jù)庫進(jìn)行分析。

Redis VS MySQL:

  • Redis的底層是跳表,而MySQL的底層是B+樹。就范圍查詢而言,兩者不分伯仲。
  • 但Redis沒有事務(wù)概念,內(nèi)部實(shí)現(xiàn)是單線程,沒有鎖競爭,再加上IO多路復(fù)用的特性和極其高效的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn),就注定單機(jī)qps要遠(yuǎn)超過mysql。
  • mysql在這個(gè)場景下的優(yōu)勢則是有持久化能力,不容易丟數(shù)據(jù),redis可能在RDB和AOF的過程中有丟數(shù)據(jù)的可能性。

因此,mysql和redis都有可能是作為存儲(chǔ)任務(wù)的數(shù)據(jù)庫,需要區(qū)分場景。

3.分布式鎖的分析

在集群模式下,哪一臺(tái)實(shí)例去執(zhí)行任務(wù)掃描這一過程依賴于分布式鎖的搶占。

(1) 基于MySQL實(shí)現(xiàn)

select * from lock_table where lock_name = 'schedule_lock' for update

主要是利用了當(dāng)前讀,將這條數(shù)據(jù)加上了行鎖,其他線程在搶鎖的時(shí)候會(huì)阻塞。

(2) 基于Redis實(shí)現(xiàn)

加鎖:SET key value PX expireTime NX。

解鎖:del key。

然而,僅依靠這兩行命令作為分布式鎖的實(shí)現(xiàn),確實(shí)顯得過于簡單。在網(wǎng)絡(luò)波動(dòng)或垃圾回收(GC)的情況下,很有可能出現(xiàn)超時(shí)時(shí)間已過,但仍嘗試釋放鎖的情況,從而導(dǎo)致錯(cuò)誤地釋放了其他客戶端持有的鎖。

這種情況可能會(huì)引起任務(wù)的重復(fù)下發(fā),為了避免這一問題,下游系統(tǒng)不得不引入去重機(jī)制。

為確保安全,建議引入Lua腳本來優(yōu)化鎖的操作。在釋放鎖之前,Lua腳本可以檢查鎖的持有者是否為當(dāng)前客戶端,只有確認(rèn)是自身持有的鎖時(shí),才執(zhí)行釋放操作。這樣一來,GET和DEL兩個(gè)操作就能合并為一次原子操作,從而避免潛在的安全隱患。

總的而言,mysql的分布式鎖實(shí)現(xiàn)簡單,但性能低。redis實(shí)現(xiàn)稍微復(fù)雜,性能高,一般用redis的多一點(diǎn)。

四、任務(wù)觸發(fā)

在構(gòu)建高效、可靠的分布式任務(wù)調(diào)度系統(tǒng)時(shí),我們需要考慮多個(gè)方面,觸發(fā)包括定時(shí)掃描、狀態(tài)更新、任務(wù)重試等關(guān)鍵環(huán)節(jié)。

1.定時(shí)掃描

觸發(fā)的本質(zhì)就是將數(shù)據(jù)從db加載進(jìn)內(nèi)存中,那么我們可以通過定時(shí)任務(wù),按照一定時(shí)間間隔去加載。那么

  • 誰來掃描?
  • 掃描的時(shí)間間隔多少合理?

(1) 誰來掃描?

負(fù)責(zé)掃描的實(shí)例需將掃描到的任務(wù)進(jìn)行下發(fā),即發(fā)起RPC調(diào)用。

為確保實(shí)例能夠并發(fā)發(fā)起多條請(qǐng)求,其機(jī)器資源應(yīng)具備足夠的線程數(shù)。為實(shí)現(xiàn)掃描與下發(fā)任務(wù)的負(fù)載均衡,各實(shí)例可通過搶鎖機(jī)制競爭掃描權(quán)限。

獲得鎖的實(shí)例將負(fù)責(zé)執(zhí)行掃描及下發(fā)任務(wù)的職責(zé)。

如上圖,每個(gè)實(shí)例都有一個(gè)定時(shí)任務(wù),x秒執(zhí)行一次,去嘗試搶鎖。

(2) 掃描的時(shí)間間隔多少合理?

掃描時(shí)間間隔的設(shè)定對(duì)于確保系統(tǒng)性能和精度至關(guān)重要。這個(gè)間隔應(yīng)當(dāng)基于系統(tǒng)所需的實(shí)時(shí)精度以及單次掃描所生成的任務(wù)數(shù)量來合理確定。盲目降低掃描時(shí)間間隔并不總是能提高精度;相反,它可能會(huì)導(dǎo)致效率降低,甚至增加數(shù)據(jù)延遲。

舉個(gè)例子,如果系統(tǒng)每1秒執(zhí)行一次掃描,但每次掃描產(chǎn)生大量任務(wù),而RPC處理時(shí)間長達(dá)2秒,且在此期間無法解鎖以供其他實(shí)例掃描,那么第2秒的數(shù)據(jù)延遲將會(huì)顯著增加。也就是說,本該1s完成的事情,他拖到了第2s才完成,那么第2s的任務(wù)就會(huì)被連累到第3s才做完......

因此,在確定掃描時(shí)間間隔時(shí),應(yīng)考慮以下兩點(diǎn):

  • 對(duì)于精度要求不高且任務(wù)量較大的場景:可以適當(dāng)延長掃描時(shí)間間隔,以確保在單次掃描周期內(nèi)能夠完成所有任務(wù)的處理下發(fā)。這樣可以減輕系統(tǒng)負(fù)擔(dān),提高整體效率。
  • 對(duì)于精度要求高同時(shí)任務(wù)量也很大的場景:除了優(yōu)化RPC處理流程外,還可以考慮改進(jìn)數(shù)據(jù)存儲(chǔ)結(jié)構(gòu),將數(shù)據(jù)分片分桶處理。通過為每個(gè)數(shù)據(jù)分片分配獨(dú)立的掃描實(shí)例,可以實(shí)現(xiàn)并行處理,從而在保證高精度的同時(shí)提升系統(tǒng)響應(yīng)速度。

綜上所述,合理的掃描時(shí)間間隔應(yīng)當(dāng)根據(jù)具體應(yīng)用場景和系統(tǒng)需求進(jìn)行細(xì)致調(diào)整,以達(dá)到最佳的性能和精度平衡點(diǎn)。

2.狀態(tài)更新

為了讓我們的系統(tǒng)展現(xiàn)出卓越的性能和高精度,我們采用了異步方式來下發(fā)任務(wù)。異步處理的明顯優(yōu)勢在于它能夠使任務(wù)并發(fā)執(zhí)行,無需等待響應(yīng),從而顯著提升了系統(tǒng)的信息處理能力。然而,這也帶來了一個(gè)問題:我們無法確切知道下游系統(tǒng)是否真正收到了任務(wù)。即便上游系統(tǒng)竭盡全力發(fā)送任務(wù),如果下游系統(tǒng)接收不到,這些努力也將化為泡影。

因此,我們需要下游系統(tǒng)在成功接收到信息后,主動(dòng)發(fā)送一個(gè)確認(rèn)信號(hào)(ACK)。一旦系統(tǒng)接收到這個(gè)ACK,我們就能記錄下觸發(fā)時(shí)間和執(zhí)行時(shí)間等相關(guān)信息,以便后續(xù)的任務(wù)重試模塊進(jìn)行相應(yīng)的處理。

考慮到任務(wù)是并發(fā)下發(fā)的,返回的信息量可能會(huì)非常龐大,每條返回信息都可能觸發(fā)一次遠(yuǎn)程過程調(diào)用(RPC),這無疑會(huì)大量消耗連接資源。為了解決這個(gè)問題,我們引入了隊(duì)列機(jī)制。

隊(duì)列機(jī)制的工作原理如下:

通過引入ACK隊(duì)列,我們實(shí)現(xiàn)了以下幾個(gè)關(guān)鍵效果:

  • 當(dāng)任務(wù)隊(duì)列滿載時(shí),我們可以一次性取出所有元素,觸發(fā)一次RPC請(qǐng)求。這樣,原本需要多次請(qǐng)求的單個(gè)數(shù)據(jù)處理,現(xiàn)在可以合并為一次批量處理。
  • 即使任務(wù)隊(duì)列未滿,但如果自上次取元素以來經(jīng)過的時(shí)間超過了預(yù)設(shè)的閾值,我們也會(huì)將隊(duì)列中的所有數(shù)據(jù)一次性取出,觸發(fā)一次RPC請(qǐng)求。

通過這種方式,我們成功地實(shí)現(xiàn)了連接復(fù)用和即時(shí)響應(yīng)的雙重效果,這也是一個(gè)寫聚合的思想。

但是壞處也很明顯,就是我們這個(gè)隊(duì)列是基于內(nèi)存的,實(shí)例宕機(jī)有丟消息的可能性。時(shí)間的閾值也需要經(jīng)驗(yàn)去設(shè)置,如果設(shè)置短了,連接不會(huì)復(fù)用,設(shè)置長了,可能影響后續(xù)任務(wù)重試時(shí)的掃描,造成誤判。

這種思想源于Kafka提供的Micro-Batch的概念,他會(huì)將相同Topic和Partition的消息聚合成一個(gè)批次,然后一次性發(fā)送到Kafka集群

3.任務(wù)重試

上文我們分析了如何讓海量任務(wù)下發(fā),但仍然做不到能讓調(diào)度系統(tǒng)擁有可靠性。在分布式環(huán)境下,服務(wù)器可能因?yàn)榫W(wǎng)絡(luò)延遲,服務(wù)器故障,資源競爭等原因,任務(wù)執(zhí)行可能會(huì)失敗。那么如何處理這些失敗的任務(wù)呢?

其實(shí)這個(gè)問題可以拆解成幾個(gè)小任務(wù):

(1) 如何檢測到失敗的任務(wù)?

我們上個(gè)步驟的下發(fā)回流,就是為了收集任務(wù)的執(zhí)行上下文信息。有了這些信息,我們只需要去設(shè)置一個(gè)定時(shí)任務(wù),快速的掃描這個(gè)任務(wù)信息即可。

(2) 如何定義一個(gè)失敗的任務(wù)?

在下發(fā)一段較長的時(shí)間后,仍然沒有回流信息寫入。

回流信息寫入成功,但回流信息中的響應(yīng)code為失敗。

(3) 檢測到失敗任務(wù)以后的重試策略?

重試策略分為重試次數(shù)和重試間隔。

每次重試完成,我們需要去更新這個(gè)已經(jīng)重試次數(shù),并檢測他是否等于最大重試次數(shù),之所有有這個(gè)最大重試次數(shù),是為了防止他無限重試,造成重試風(fēng)暴,而超過這個(gè)最大重試次數(shù)的,我們可以把它塞入死信隊(duì)列中,讓負(fù)責(zé)這個(gè)任務(wù)的人手動(dòng)的去處理。

而重試間隔主要也分為幾種:

(1) 固定間隔重試

在這種策略中,每次重試之間都有一個(gè)固定的時(shí)間間隔。例如,如果操作失敗,系統(tǒng)會(huì)在1秒后重試,然后是2秒后重試,依此類推。

(2) 指數(shù)退避重試

指數(shù)退避重試策略是一種更復(fù)雜的重試策略,其中每次重試之間的時(shí)間間隔呈指數(shù)增長。例如,第一次重試可能在1秒后,第二次在2秒后,第三次在4秒后,以此類推。這種策略有助于減少對(duì)系統(tǒng)的沖擊,特別是在高負(fù)載或網(wǎng)絡(luò)擁塞的情況下。

之所以采用以上這兩種策略是因?yàn)閞pc接口調(diào)用在遇到服務(wù)質(zhì)量異常的錯(cuò)誤的時(shí)候,由于服務(wù)質(zhì)量異常是有一定時(shí)間的,因此有各種退避策略,一定程度上給足下游恢復(fù)的時(shí)間。

(3) 下游應(yīng)該如何處理重試的任務(wù)?

在掃描的過程中,如果因?yàn)榫W(wǎng)絡(luò)波動(dòng)的原因,導(dǎo)致回流消息的時(shí)間被拉長,而我們上游在掃描的時(shí)候誤認(rèn)為沒有下發(fā)成功,而實(shí)際上已經(jīng)下發(fā)成功了,我們依舊發(fā)起了重試,那么就會(huì)導(dǎo)致重復(fù)下發(fā)。

為了避免這一現(xiàn)象發(fā)生,下游有必要去做一次去重。我們可以給每次下發(fā)的任務(wù)都冠以一個(gè)唯一id,然后用位圖對(duì)當(dāng)日的下發(fā)進(jìn)行去重處理。

我們可以使用雪花算法去生成唯一id,也可以通過每次生成的業(yè)務(wù)id去拼接當(dāng)前下發(fā)的秒數(shù)去生成唯一id,這個(gè)方案很多,不多贅述。

五、路由實(shí)例

在經(jīng)過上述流程之后,我們需要做的是,選擇一個(gè)合適的實(shí)例進(jìn)行觸發(fā),往往通過線程池,協(xié)程池進(jìn)行rpc調(diào)度。

總結(jié)了市面上的開源中間件主要有以下幾種路由算法的實(shí)現(xiàn):

方法

描述

輪詢

依次遍歷執(zhí)行器列表

隨機(jī)數(shù)

random函數(shù)實(shí)現(xiàn)

一致性哈希

通過2^32 ring (md5散列的方式計(jì)算hash值),盡可能保證每輪觸發(fā)都均勻落到每個(gè)執(zhí)行器上。

LRU

最近最久未使用。

LFU

每個(gè)使用頻率最低的執(zhí)行器優(yōu)先被淘汰。

心跳

遍歷每個(gè)執(zhí)行器,向每個(gè)執(zhí)行器發(fā)起請(qǐng)求,如果哪個(gè)執(zhí)行器最快發(fā)回心跳包,說明他最閑,那么就選擇他。

這些路由算法都是為了能讓不同執(zhí)行器的負(fù)載變得均衡,需要根據(jù)場景選擇合適的路由算法。

六、優(yōu)秀系統(tǒng)的設(shè)計(jì)

1.xxl-job的實(shí)現(xiàn)

XXL-JOB是一款知名的分布式任務(wù)調(diào)度框架,它采用內(nèi)存中的時(shí)間輪算法結(jié)合MySQL作為持久化存儲(chǔ)來管理調(diào)度任務(wù),其調(diào)度粒度精準(zhǔn)至秒級(jí)。

以下是XXL-JOB的核心工作流程:

  • 調(diào)度線程預(yù)讀與更新:調(diào)度線程負(fù)責(zé)提前讀取未來5秒內(nèi)即將執(zhí)行的任務(wù),將這些任務(wù)載入內(nèi)存中的時(shí)間輪,并同步更新它們的下一次觸發(fā)時(shí)刻。
  • 時(shí)間輪線程輪詢執(zhí)行:時(shí)間輪線程每隔一秒從等待狀態(tài)激活為可運(yùn)行狀態(tài)。它會(huì)捕獲當(dāng)前時(shí)間的秒級(jí)信息,并在時(shí)間輪中定位到對(duì)應(yīng)的任務(wù)。一旦找到,時(shí)間輪線程便會(huì)利用預(yù)先配置的線程池發(fā)起RPC調(diào)用,觸發(fā)任務(wù)執(zhí)行。

下面是簡易版的偽代碼(筆者憑借之前的印象寫的,可能并不完整):

// 時(shí)間輪 秒數(shù)為key,task列表為value
Map<Integer,List<Task>> ringData = new HashMap<>();
// rpc線程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor();

// 調(diào)度線程
Thread schedulerThread = new Thread(() -> {
    while(true){
        1. 從數(shù)據(jù)庫預(yù)讀未來5s的任務(wù)
        List<Task> tasks = mapper.loadTask(now() + 5000)  
        for(task in tasks){
            long time = task.getTriggerTime();
            // 根據(jù)觸發(fā)時(shí)間計(jì)算秒數(shù)。比如觸發(fā)時(shí)間是 21:00:01 那么秒數(shù)就是 1
            Integer second = time.calculateSecond();
            ringData.put(task.getSecond(), task)
            // 更新下一次觸發(fā)事件,運(yùn)行狀態(tài)等信息,
            updateTask(task)
        }
        Thread.sleep(5000)
    }
})
schedulerThread.start();

// 時(shí)間輪線程
Thread ringThread = new Thread(() -> {
    while(true){
       //秒數(shù)對(duì)齊,整秒數(shù)激活為可運(yùn)行狀態(tài)
       Thread.sleep(1000 - System.currentTimeMillis() % 1000);
       //獲取蘇醒時(shí)候的秒數(shù)
       Integer currentSecond = now().getSecond();
       List<Task> tasks = ringData.get(currentSecond);
       //放入線程池發(fā)起rpc調(diào)度
       threadPool.trigger(tasks)
       // help gc
       tasks.clear();
    }
})
ringThread.start();

這里的時(shí)間輪就是一個(gè)key為秒數(shù),value為即將要執(zhí)行的任務(wù)id列表。

時(shí)間輪分為單級(jí)時(shí)間輪和多級(jí)時(shí)間輪。xxl-job并沒有像kafka那樣采用多級(jí)時(shí)間輪,主要是因?yàn)樵O(shè)計(jì)理念的不同,他為了簡化設(shè)計(jì),并且單級(jí)時(shí)間輪已經(jīng)滿足大部分任務(wù)調(diào)度的需求。

優(yōu)點(diǎn):

  • 高效檢索與持久化:借助MySQL B+樹的特性,搜索操作的時(shí)間復(fù)雜度降至O(logN),同時(shí)提供數(shù)據(jù)的持久化存儲(chǔ),確保數(shù)據(jù)安全不易丟失。
  • 高精度調(diào)度:通過將任務(wù)提前預(yù)讀至內(nèi)存,提升了任務(wù)調(diào)度的精度和響應(yīng)速度。
  • 生產(chǎn)者-消費(fèi)者模型:調(diào)度線程與時(shí)間輪線程的協(xié)同工作構(gòu)成了經(jīng)典的生產(chǎn)者-消費(fèi)者模型。時(shí)間輪作為任務(wù)緩沖區(qū),有效實(shí)現(xiàn)流量削峰,減輕系統(tǒng)瞬時(shí)負(fù)載壓力。

缺點(diǎn):

  • 調(diào)度器集群性能瓶頸:現(xiàn)有架構(gòu)依賴調(diào)度器集群間的鎖機(jī)制來執(zhí)行任務(wù),這可能導(dǎo)致單個(gè)調(diào)度器承受過大壓力,限制了系統(tǒng)的并發(fā)處理能力(QPS)。在任務(wù)量激增時(shí),難以通過水平擴(kuò)展提升調(diào)度能力,從而可能導(dǎo)致部分任務(wù)延遲過高。
  • 磁盤存儲(chǔ)的性能局限:雖然MySQL提供了可靠的數(shù)據(jù)存儲(chǔ),但作為基于磁盤的數(shù)據(jù)庫,在高并發(fā)場景下的性能可能不如內(nèi)存型數(shù)據(jù)庫如Redis。

總體而言,XXL-JOB采用內(nèi)存結(jié)合MySQL的部署方式簡單易行,無需額外引入中間件。這種設(shè)計(jì)在追求調(diào)度精度的同時(shí)犧牲了一定的水平擴(kuò)展性。對(duì)于任務(wù)量適中的場景而言,它仍然是一個(gè)值得考慮的優(yōu)秀調(diào)度框架選項(xiàng)。

2.騰訊視頻push中臺(tái)的實(shí)現(xiàn)

騰訊視頻push中臺(tái)為了應(yīng)對(duì)海量的并發(fā),犧牲了調(diào)度的精度,以redis作為db,ZSet(跳表)作為底層數(shù)據(jù)結(jié)構(gòu)來支持任務(wù)的范圍查詢。

主要流程:將任務(wù)id作為key,時(shí)間戳作為value進(jìn)行任務(wù)的新增,利用Zrange 命令獲取要觸發(fā)的任務(wù),并判斷是否需要觸發(fā)。而任務(wù)拉取任務(wù)依賴不同實(shí)例去搶分布式鎖,然后執(zhí)行。

優(yōu)點(diǎn):

  • 基于redis,查詢快,qps是mysql的好幾倍,實(shí)現(xiàn)簡單,易于維護(hù)。
  • redis的分布式鎖性能優(yōu)秀,加鎖解鎖快。
  • 無需依賴其他中間件,成本小。
  • 讓搜素的時(shí)間復(fù)雜度降低到O(logN),查詢快,無需遍歷額外數(shù)據(jù)。

缺點(diǎn):精度無法保證。

3.Redis的高精度版本實(shí)現(xiàn)

(1) 分片

為了實(shí)現(xiàn)更高精度的Redis調(diào)度,我們需要確保跳表中的數(shù)據(jù)量保持在合理范圍內(nèi)。過多的數(shù)據(jù)可能導(dǎo)致內(nèi)存占用過高、成本不足以及讀寫響應(yīng)時(shí)間變長等問題(大Key問題)。因此,為了降低Redis訪問的響應(yīng)時(shí)間(即提高精度),我們對(duì)數(shù)據(jù)進(jìn)行分片處理,使調(diào)度器每次只需掃描一個(gè)分片的數(shù)據(jù)。

如下圖:

我們可以把一天的數(shù)據(jù)分為多個(gè)分鐘級(jí)別的數(shù)據(jù),雖然搜索的時(shí)間復(fù)雜度仍為O(logN),但由于N大大減小,搜索效率得到提高,響應(yīng)速度更快。

然而,這仍然無法解決一個(gè)問題:如果某個(gè)實(shí)例通過搶鎖方式獲得某一分鐘分片的掃描權(quán)限,但該分鐘內(nèi)的數(shù)據(jù)量仍然很大,可能會(huì)導(dǎo)致實(shí)例的線程數(shù)不足,無法實(shí)現(xiàn)并發(fā)處理。

(2) 分桶

為了解決這個(gè)問題,我們可以采用分桶策略,將這一分鐘的數(shù)據(jù)劃分為多個(gè)bucket。

在集群模式的調(diào)度器下,每個(gè)實(shí)例競爭的是各個(gè)bucket的鎖,獲得鎖后,只需掃描相應(yīng)分桶的數(shù)據(jù)。這種方法可以實(shí)現(xiàn)每分鐘級(jí)別的tasklist調(diào)度,多臺(tái)機(jī)器可以同時(shí)掃描和下發(fā),避免了單個(gè)實(shí)例線程不足的問題。

如下圖:

若即使分成三個(gè)桶,數(shù)據(jù)量仍然過大,我們可以引入一個(gè)決策服務(wù)來監(jiān)控任務(wù)的延時(shí)情況。如果任務(wù)的延時(shí)率持續(xù)較高,可以根據(jù)實(shí)際情況動(dòng)態(tài)調(diào)整分桶數(shù)量,從而更好地滿足實(shí)際需求。

總結(jié)

本文詳細(xì)探討了調(diào)度模塊在多種系統(tǒng)中的應(yīng)用及其重要性,并深入分析了調(diào)度系統(tǒng)的通用流程,包括任務(wù)生成、任務(wù)存儲(chǔ)、定時(shí)掃描和路由實(shí)例等關(guān)鍵步驟。

文章針對(duì)每個(gè)步驟的技術(shù)選型進(jìn)行了探討,并結(jié)合實(shí)際系統(tǒng)(如XXL-JOB和騰訊視頻push中臺(tái))進(jìn)行了案例分析。此外,還討論了各種路由算法的實(shí)現(xiàn)及其適用場景。

總的來說,一個(gè)高性能的調(diào)度系統(tǒng)需要綜合考慮任務(wù)生成策略、存儲(chǔ)數(shù)據(jù)結(jié)構(gòu)的選擇、數(shù)據(jù)庫選型、分布式鎖的實(shí)現(xiàn)以及定時(shí)掃描的機(jī)制等多個(gè)方面。通過合理的技術(shù)選型和系統(tǒng)設(shè)計(jì),可以實(shí)現(xiàn)高精度和高可用的調(diào)度目標(biāo)。同時(shí),根據(jù)具體的應(yīng)用場景和需求,靈活調(diào)整調(diào)度策略和路由算法,以達(dá)到最佳的性能和效率平衡點(diǎn)。

責(zé)任編輯:趙寧寧 來源: 騰訊技術(shù)工程
相關(guān)推薦

2020-07-16 08:06:53

網(wǎng)關(guān)高性能計(jì)

2021-05-24 09:28:41

軟件開發(fā) 技術(shù)

2012-01-16 09:00:18

云計(jì)算高性能計(jì)算

2011-04-22 16:23:16

ASP.NET動(dòng)態(tài)應(yīng)用系統(tǒng)

2024-10-15 16:31:30

2024-07-12 08:42:58

Redis高性能架構(gòu)

2019-06-27 09:50:49

高性能秒殺系統(tǒng)

2023-02-02 08:18:41

2024-11-19 16:31:23

2024-11-12 08:13:09

2017-04-24 14:09:13

深度學(xué)習(xí)TensorFlow

2019-07-12 08:49:04

MySQ數(shù)據(jù)庫Redis

2015-12-09 09:51:03

Java高性能

2018-09-18 17:20:14

MySQL優(yōu)化數(shù)據(jù)庫

2020-11-10 09:43:32

NginxLinux服務(wù)器

2021-08-30 09:30:29

Kafka高性能設(shè)計(jì)

2024-07-05 09:41:42

2021-10-18 08:28:03

Kafka架構(gòu)主從架構(gòu)

2024-11-20 19:56:36

2010-04-02 09:57:34

云計(jì)算
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)