W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
原文鏈接:https://chai2010.cn/advanced-go-programming-book/ch6-cloud/ch6-03-delay-job.html
我們在做系統(tǒng)時,很多時候是處理實時的任務(wù),請求來了馬上就處理,然后立刻給用戶以反饋。但有時也會遇到非實時的任務(wù),比如確定的時間點發(fā)布重要公告?;蛘咝枰谟脩糇隽艘患虑榈?X 分鐘 / Y 小時后,對其特定動作,比如通知、發(fā)券等等。
如果業(yè)務(wù)規(guī)模比較小,有時我們也可以通過數(shù)據(jù)庫配合輪詢來對這種任務(wù)進行簡單處理,但上了規(guī)模的公司,自然會尋找更為普適的解決方案來解決這一類問題。
一般有兩種思路來解決這個問題:
兩種思路進而衍生出了一些不同的系統(tǒng),但其本質(zhì)是差不多的。都是需要實現(xiàn)一個定時器(timer)。在單機的場景下定時器其實并不少見,例如我們在和網(wǎng)絡(luò)庫打交道的時候經(jīng)常會調(diào)用 SetReadDeadline()
函數(shù),就是在本地創(chuàng)建了一個定時器,在到達(dá)指定的時間后,我們會收到定時器的通知,告訴我們時間已到。這時候如果讀取還沒有完成的話,就可以認(rèn)為發(fā)生了網(wǎng)絡(luò)問題,從而中斷讀取。
下面我們從定時器開始,探究延時任務(wù)系統(tǒng)的實現(xiàn)。
定時器(timer)的實現(xiàn)在工業(yè)界已經(jīng)是有解的問題了。常見的就是時間堆和時間輪。
最常見的時間堆一般用小頂堆實現(xiàn),小頂堆其實就是一種特殊的二叉樹,見 圖 6-4
圖 6-4 二叉堆結(jié)構(gòu)
小頂堆的好處是什么呢?對于定時器來說,如果堆頂元素比當(dāng)前的時間還要大,那么說明堆內(nèi)所有元素都比當(dāng)前時間大。進而說明這個時刻我們還沒有必要對時間堆進行任何處理。定時檢查的時間復(fù)雜度是 O(1)
。
當(dāng)我們發(fā)現(xiàn)堆頂?shù)脑匦∮诋?dāng)前時間時,那么說明可能已經(jīng)有一批事件已經(jīng)開始過期了,這時進行正常的彈出和堆調(diào)整操作就好。每一次堆調(diào)整的時間復(fù)雜度都是 O(LgN)
。
Go 自身的內(nèi)置定時器就是用時間堆來實現(xiàn)的,不過并沒有使用二叉堆,而是使用了扁平一些的四叉堆。在最近的版本中,還加了一些優(yōu)化,我們先不說優(yōu)化,先來看看四叉的小頂堆長什么樣:
圖 6-5 四叉堆結(jié)構(gòu)
小頂堆的性質(zhì),父節(jié)點比其 4 個子節(jié)點都小,子節(jié)點之間沒有特別的大小關(guān)系要求。
四叉堆中元素超時和堆調(diào)整與二叉堆沒有什么本質(zhì)區(qū)別。
圖 6-6 時間輪
用時間輪來實現(xiàn)定時器時,我們需要定義每一個格子的 “刻度”,可以將時間輪想像成一個時鐘,中心有秒針順時針轉(zhuǎn)動。每次轉(zhuǎn)動到一個刻度時,我們就需要去查看該刻度掛載的任務(wù)列表是否有已經(jīng)到期的任務(wù)。
從結(jié)構(gòu)上來講,時間輪和哈希表很相似,如果我們把哈希算法定義為:觸發(fā)時間 % 時間輪元素大小。那么這就是一個簡單的哈希表。在哈希沖突時,采用鏈表掛載哈希沖突的定時器。
除了這種單層時間輪,業(yè)界也有一些時間輪采用多層實現(xiàn),這里就不再贅述了。
有了基本的定時器實現(xiàn)方案,如果我們開發(fā)的是單機系統(tǒng),那么就可以擼起袖子開干了,不過本章我們討論的是分布式,距離 “分布式” 還稍微有一些距離。
我們還需要把這些 “定時” 或是“延時”(本質(zhì)也是定時)任務(wù)分發(fā)出去。下面是一種思路:
圖 6-7 分布式任務(wù)分發(fā)
每一個實例每隔一小時,會去數(shù)據(jù)庫里把下一個小時需要處理的定時任務(wù)撈出來,撈取的時候只要取那些 task_id % shard_count = shard_id
的那些任務(wù)即可。
當(dāng)這些定時任務(wù)被觸發(fā)之后需要通知用戶側(cè),有兩種思路:
兩種方案各有優(yōu)缺點,如果采用 1,那么如果消息隊列出故障會導(dǎo)致整個系統(tǒng)不可用,當(dāng)然,現(xiàn)在的消息隊列一般也會有自身的高可用方案,大多數(shù)時候我們不用擔(dān)心這個問題。其次一般業(yè)務(wù)流程中間走消息隊列的話會導(dǎo)致延時增加,定時任務(wù)若必須在觸發(fā)后的幾十毫秒到幾百毫秒內(nèi)完成,那么采用消息隊列就會有一定的風(fēng)險。如果采用 2,會加重定時任務(wù)系統(tǒng)的負(fù)擔(dān)。我們知道,單機的定時器執(zhí)行時最害怕的就是回調(diào)函數(shù)執(zhí)行時間過長,這樣會阻塞后續(xù)的任務(wù)執(zhí)行。在分布式場景下,這種憂慮依然是適用的。一個不負(fù)責(zé)任的業(yè)務(wù)回調(diào)可能就會直接拖垮整個定時任務(wù)系統(tǒng)。所以我們還要考慮在回調(diào)的基礎(chǔ)上增加經(jīng)過測試的超時時間設(shè)置,并且對由用戶填入的超時時間做慎重的審核。
當(dāng)我們的任務(wù)執(zhí)行集群有機器故障時,需要對任務(wù)進行重新分配。按照之前的求模策略,對這臺機器還沒有處理的任務(wù)進行重新分配就比較麻煩了。如果是實際運行的線上系統(tǒng),還要在故障時的任務(wù)平衡方面花更多的心思。
下面給出一種思路:
我們可以參考 Elasticsearch 的數(shù)據(jù)分布設(shè)計,每份任務(wù)數(shù)據(jù)都有多個副本,這里假設(shè)兩副本,如 圖 6-8 所示:
圖 6-8 任務(wù)數(shù)據(jù)分布
一份數(shù)據(jù)雖然有兩個持有者,但持有者持有的副本會進行區(qū)分,比如持有的是主副本還是非主副本,主副本在圖中為摸黑部分,非主副本為正常線條。
一個任務(wù)只會在持有主副本的節(jié)點上被執(zhí)行。
當(dāng)有機器故障時,任務(wù)數(shù)據(jù)需要進行數(shù)據(jù)再平衡的工作,比如節(jié)點 1 掛了,見 圖 6-9。
圖 6-9 故障時數(shù)據(jù)分布
節(jié)點 1 的數(shù)據(jù)會被遷移到節(jié)點 2 和節(jié)點 3 上。
當(dāng)然,也可以用稍微復(fù)雜一些的思路,比如對集群中的節(jié)點進行角色劃分,由協(xié)調(diào)節(jié)點來做這種故障時的任務(wù)重新分配工作,考慮到高可用,協(xié)調(diào)節(jié)點可能也需要有 1 至 2 個備用節(jié)點以防不測。
之前提到我們會用消息隊列觸發(fā)對用戶的通知,在使用消息隊列時,很多隊列是不支持 exactly once
的語義的,這種情況下我們需要讓用戶自己來負(fù)責(zé)消息的去重或者消費的冪等處理。
![]() | ![]() |
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: