cron需要進(jìn)行初始化,在gin的main中進(jìn)行,然后后面的定時(shí)任務(wù),使用addjob,addjob會(huì)返回一個(gè)jobid,后面關(guān)閉時(shí),可以使用這個(gè)id去關(guān)閉。
成都創(chuàng)新互聯(lián)公司-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比合江網(wǎng)站開(kāi)發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫(kù),直接使用。一站式合江網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋合江地區(qū)。費(fèi)用合理售后完善,十年實(shí)體公司更值得信賴。
1.main.go中初始化cron
2.InitCron,返回一個(gè)Cron類型
3.使用cj進(jìn)行添加任務(wù),任務(wù)會(huì)返回一個(gè)id,因?yàn)檎{(diào)用的時(shí)候會(huì)使用協(xié)程,將id通過(guò)channel返回
4.將id回收,已被刪除定時(shí)任務(wù)時(shí)使用。另外實(shí)現(xiàn)mqttJob需要實(shí)現(xiàn)Run接口,addjob才能運(yùn)行
5.刪除定時(shí)任務(wù),cj為gin初始化的new cron
在linux下實(shí)現(xiàn)定時(shí)器主要有如下方式
在這當(dāng)中 基于時(shí)間輪方式實(shí)現(xiàn)的定時(shí)器 時(shí)間復(fù)雜度最小,效率最高,然而我們可以通過(guò) 優(yōu)先隊(duì)列 實(shí)現(xiàn)時(shí)間輪定時(shí)器。
優(yōu)先隊(duì)列的實(shí)現(xiàn)可以使用最大堆和最小堆,因此在隊(duì)列中所有的數(shù)據(jù)都可以定義排序規(guī)則自動(dòng)排序。我們直接通過(guò)隊(duì)列中 pop 函數(shù)獲取數(shù)據(jù),就是我們按照自定義排序規(guī)則想要的數(shù)據(jù)。
在 Golang 中實(shí)現(xiàn)一個(gè)優(yōu)先隊(duì)列異常簡(jiǎn)單,在 container/head 包中已經(jīng)幫我們封裝了,實(shí)現(xiàn)的細(xì)節(jié),我們只需要實(shí)現(xiàn)特定的接口就可以。
下面是官方提供的例子
因?yàn)閮?yōu)先隊(duì)列底層數(shù)據(jù)結(jié)構(gòu)是由二叉樹(shù)構(gòu)建的,所以我們可以通過(guò)數(shù)組來(lái)保存二叉樹(shù)上的每一個(gè)節(jié)點(diǎn)。
改數(shù)組需要實(shí)現(xiàn) Go 預(yù)先定義的接口 Len , Less , Swap , Push , Pop 和 update 。
timerType結(jié)構(gòu)是定時(shí)任務(wù)抽象結(jié)構(gòu)
首先的 start 函數(shù),當(dāng)創(chuàng)建一個(gè) TimeingWheel 時(shí),通過(guò)一個(gè) goroutine 來(lái)執(zhí)行 start ,在start中for循環(huán)和select來(lái)監(jiān)控不同的channel的狀態(tài)
通過(guò)for循環(huán)從隊(duì)列中取數(shù)據(jù),直到該隊(duì)列為空或者是遇見(jiàn)第一個(gè)當(dāng)前時(shí)間比任務(wù)開(kāi)始時(shí)間大的任務(wù), append 到 expired 中。因?yàn)閮?yōu)先隊(duì)列中是根據(jù) expiration 來(lái)排序的,
所以當(dāng)取到第一個(gè)定時(shí)任務(wù)未到的任務(wù)時(shí),表示該定時(shí)任務(wù)以后的任務(wù)都未到時(shí)間。
當(dāng) getExpired 函數(shù)取出隊(duì)列中要執(zhí)行的任務(wù)時(shí),當(dāng)有的定時(shí)任務(wù)需要不斷執(zhí)行,所以就需要判斷是否該定時(shí)任務(wù)需要重新放回優(yōu)先隊(duì)列中。 isRepeat 是通過(guò)判斷任務(wù)中 interval 是否大于 0 判斷,
如果大于0 則,表示永久就生效。
防止外部濫用,阻塞定時(shí)器協(xié)程,框架又一次封裝了timer這個(gè)包,名為 timer_wapper 這個(gè)包,它提供了兩種調(diào)用方式。
參數(shù)和上面的參數(shù)一樣,只是在第三個(gè)參數(shù)中使用了任務(wù)池,將定時(shí)任務(wù)放入了任務(wù)池中。定時(shí)任務(wù)的本身執(zhí)行就是一個(gè) put 操作。
至于put以后,那就是 workers 這個(gè)包管理的了。在 worker 包中, 也就是維護(hù)了一個(gè)任務(wù)池,任務(wù)池中的任務(wù)會(huì)有序的執(zhí)行,方便管理。
目前APP業(yè)務(wù)中啟用的定時(shí)任務(wù)已達(dá)到400+,目前管理比較混亂,很多任務(wù)運(yùn)行時(shí)占用服務(wù)器資源巨大,其中不乏一些非緊急的任務(wù),平時(shí)并不會(huì)有太大影響,但是當(dāng)流量高峰來(lái)臨時(shí),這些定時(shí)任務(wù)可能會(huì)成為壓死駱駝的最后一根稻草。為了避免出現(xiàn)這樣的問(wèn)題,我們通常會(huì)在高流量來(lái)之前去調(diào)整一些定時(shí)任務(wù)的執(zhí)行間隔時(shí)間或者暫停一些不影響服務(wù)的定時(shí)任務(wù)。這樣做的弊端是工作量很大,同時(shí)難免會(huì)有遺漏。由此衍生除了對(duì)任務(wù)分級(jí)的訴求。對(duì)任務(wù)分級(jí)后,高峰流量時(shí),可視情況降級(jí)相關(guān)等級(jí)的定時(shí)任務(wù)。
? PS:設(shè)計(jì)核心流程的任務(wù)等,如支付回調(diào)
? PS:任務(wù)中設(shè)計(jì)到事務(wù)等
基于gocron的任務(wù)節(jié)點(diǎn)做任務(wù)分級(jí),不同級(jí)別的任務(wù)對(duì)應(yīng)不同的gocron節(jié)點(diǎn)。如下圖:
把三級(jí)任務(wù)放在三級(jí)節(jié)點(diǎn)上跑,如下圖:
以此類推,不同級(jí)別的任務(wù)跑在對(duì)應(yīng)級(jí)別的節(jié)點(diǎn)上。
當(dāng)流量高峰來(lái)臨時(shí),我們想通過(guò)停掉所有三級(jí)任務(wù)來(lái)實(shí)現(xiàn)快速降級(jí),而這個(gè)操作僅僅需要關(guān)閉對(duì)應(yīng)節(jié)點(diǎn)的連接即可。如下圖
PS:這個(gè)操作同時(shí)會(huì)停止所有正在運(yùn)行的任務(wù)
舉個(gè)例子:目前我的三級(jí)任務(wù)節(jié)點(diǎn)上運(yùn)行了一個(gè)同步數(shù)據(jù)的任務(wù)(預(yù)計(jì)5分鐘左右能執(zhí)行完),當(dāng)我把三級(jí)任務(wù)節(jié)點(diǎn)關(guān)閉時(shí),這個(gè)任務(wù)會(huì)直接失敗,在節(jié)點(diǎn)對(duì)應(yīng)的機(jī)器上我們可以看到所有進(jìn)程也被直接kill掉了,即使我的任務(wù)是多進(jìn)程在跑,相應(yīng)的子進(jìn)程也會(huì)被kill掉。如下:
當(dāng)前正在服務(wù)的三級(jí)節(jié)點(diǎn)-asgard三級(jí)定時(shí)任務(wù)
當(dāng)前正在節(jié)點(diǎn)-asgard三級(jí)定時(shí)任務(wù)上運(yùn)行的任務(wù)-商品數(shù)據(jù)整合同步搜索個(gè)推庫(kù)
節(jié)點(diǎn)服務(wù)器上正在運(yùn)行的進(jìn)程
這時(shí)候我們關(guān)閉asgard三級(jí)定時(shí)任務(wù)這個(gè)節(jié)點(diǎn)
可以看到任務(wù)直接執(zhí)行失敗了
同時(shí),節(jié)點(diǎn)服務(wù)器上的進(jìn)程也被kill掉了
由于二級(jí)任務(wù)可能涉及到事務(wù)等操作,非萬(wàn)分緊急情況下不能直接終止,以免導(dǎo)致臟數(shù)據(jù)的產(chǎn)生。對(duì)于這種任務(wù)的降級(jí)我們不能直接通過(guò)節(jié)點(diǎn)的方式停止任務(wù)??梢酝ㄟ^(guò)關(guān)閉任務(wù)的方式停止。如下:
PS:關(guān)閉任務(wù)的操作會(huì)等當(dāng)前的任務(wù)執(zhí)行完成再關(guān)閉,不會(huì)對(duì)當(dāng)前任務(wù)產(chǎn)生任何影響
舉個(gè)例子:
還拿asgard三級(jí)定時(shí)任務(wù)這個(gè)節(jié)點(diǎn)來(lái)看,目前這個(gè)節(jié)點(diǎn)在鏈接狀態(tài)
這個(gè)節(jié)點(diǎn)下跑了一個(gè)任務(wù)
同樣的,節(jié)點(diǎn)服務(wù)器上有對(duì)應(yīng)的進(jìn)程在跑著
這時(shí)候,我們關(guān)閉這個(gè)任務(wù)
我們可以看到,關(guān)閉這個(gè)任務(wù),不會(huì)影響正在執(zhí)行的任務(wù)
節(jié)點(diǎn)對(duì)應(yīng)的服務(wù)器上的任務(wù)也正常在跑
PS:這個(gè)關(guān)閉任務(wù)對(duì)應(yīng)的是,完成當(dāng)前任務(wù)后不再執(zhí)行新的任務(wù)。
1、基于gocron的任務(wù)節(jié)點(diǎn)對(duì)任務(wù)做分級(jí)處理
2、一、二、三級(jí)任務(wù)的劃分
3、服務(wù)降級(jí)的兩種方式:關(guān)閉節(jié)點(diǎn)關(guān)閉任務(wù)
利用 Etcd 的Lease租約特性來(lái)實(shí)現(xiàn)定時(shí)功能,同時(shí)通過(guò)Watch機(jī)制來(lái)實(shí)現(xiàn)多節(jié)點(diǎn)情況下只有一個(gè)節(jié)點(diǎn)執(zhí)行該任務(wù)。通過(guò)定時(shí)任務(wù)庫(kù) Cron 的時(shí)間字符串解析器Parser來(lái)解析任務(wù)執(zhí)行時(shí)間。
Etcd
Cron
源碼鏈接
不過(guò),業(yè)務(wù)中 ① 總會(huì)存在對(duì)中止比較敏感的接口(比如支付相關(guān)),并且 ② 總會(huì)存在一些帶狀態(tài)的服務(wù),此時(shí)優(yōu)雅中止就顯得比較重要了。
本文通過(guò)一個(gè)Go 定時(shí)任務(wù)示例來(lái)簡(jiǎn)單介紹 Go 技術(shù)棧中優(yōu)雅中止的處理思路。
入門——初級(jí)√——中級(jí)——高級(jí);本文適應(yīng)初級(jí)及以上。
所謂“優(yōu)雅中止”,是指應(yīng)用接收到特定的中止信號(hào)(比如 INT、TERM)后,不再接受外部的新請(qǐng)求,也不再創(chuàng)建內(nèi)部的新任務(wù),保持應(yīng)用進(jìn)程運(yùn)行直到舊需求和舊任務(wù)執(zhí)行完成后再終止退出。
作為高可靠的服務(wù)平臺(tái),k8s 定義了終止 Pod (業(yè)務(wù)進(jìn)程在 Pod 中運(yùn)行)的基本步驟:當(dāng)主動(dòng)刪除 pod 時(shí),系統(tǒng)會(huì)在強(qiáng)制終止 Pod 之前將 TERM 信號(hào)發(fā)送到每個(gè)容器中的主進(jìn)程,過(guò)一段時(shí)間后(默認(rèn)為 30 秒),再把 KILL 信號(hào)發(fā)送到這些進(jìn)程。除此之外, k8s 還通過(guò)鉤子方法提供了對(duì) 容器生命周期 的管理能力,允許用戶通過(guò)自定義的方式配置容器啟動(dòng)后或終止前執(zhí)行的操作。
當(dāng)打包進(jìn)鏡像的應(yīng)用運(yùn)行在 k8s 中的時(shí)候,如果應(yīng)用實(shí)現(xiàn)了優(yōu)雅中止的機(jī)制,就可以充分利用上面提到的 k8s 的能力,在升級(jí)應(yīng)用(發(fā)新版本)和管理 Pod (宿主機(jī)維護(hù)時(shí)把 Pod 漂移到另一個(gè)宿主機(jī),或者在閑時(shí)動(dòng)態(tài)地收縮 Pod 數(shù)量從而把資源省出來(lái)另作他用)的過(guò)程中實(shí)現(xiàn)服務(wù)的零中斷。
下面的代碼定義了兩個(gè)定時(shí)任務(wù):mySecondJobs 每秒鐘會(huì)觸發(fā)一次,每次持續(xù)約 1 秒鐘;myMinuteJobs 每分鐘會(huì)觸發(fā)一次,每次持續(xù)約 2 秒鐘。具體地可以閱讀下面的代碼(可以直接復(fù)制下面的代碼到自己的環(huán)境中運(yùn)行):
代碼中采用了 go mySecondJobs() 和 go myMinuteJobs() 異步任務(wù)的方式;如果采用同步的方式將無(wú)法捕獲信號(hào),因?yàn)榇藭r(shí)主線程在處理業(yè)務(wù)邏輯,沒(méi)有空閑處理信號(hào)捕獲邏輯。
源碼中偷懶地采取簡(jiǎn)單等待的方式來(lái)保證異步任務(wù)正常結(jié)束,非普適方法,實(shí)際開(kāi)發(fā)中需要根據(jù)情況做定制。
time.Ticker 的使用是有注意事項(xiàng)的,當(dāng) select 語(yǔ)句中同一時(shí)刻有多個(gè)分支滿足條件時(shí)會(huì)隨機(jī)取一個(gè)執(zhí)行,從而導(dǎo)致信息丟失(參考文獻(xiàn)中最后一篇有講到),不過(guò)本文的代碼不會(huì)觸發(fā)這個(gè)問(wèn)題,大家可以思考一下原因。
默認(rèn)情況下,Go 應(yīng)用在接收到 TERM 信號(hào)后直接退出主進(jìn)程,如果此時(shí)有過(guò)程沒(méi)處理完(比如 接收到外部請(qǐng)求后尚未返回響應(yīng),或者內(nèi)部的異步任務(wù)尚未結(jié)束),則會(huì)導(dǎo)致過(guò)程的異常中斷,影響服務(wù)質(zhì)量。通過(guò)在代碼中 顯式 地捕獲 TERM 信號(hào)及其他信號(hào),感知操作系統(tǒng)對(duì)進(jìn)程的處理,可以主動(dòng)采取措施優(yōu)雅地結(jié)束應(yīng)用進(jìn)程。
隨著 k8s 的普及,考慮到其對(duì)進(jìn)程生命周期的規(guī)范化管理,應(yīng)用支持代碼級(jí)的優(yōu)雅中止(尤其是容器化的應(yīng)用)有必要成為一種開(kāi)發(fā)規(guī)范,值得引起每一位開(kāi)發(fā)者的注意。
著作權(quán)歸作者所有。
原文:
func?startTimer(f?func())?{
go?func()?{
for?{
f()
now?:=?time.Now()
//?計(jì)算下一個(gè)零點(diǎn)
next?:=?now.Add(time.Hour?*?24)
next?=?time.Date(next.Year(),?next.Month(),?next.Day(),?0,?0,?0,?0,?next.Location())
t?:=?time.NewTimer(next.Sub(now))
-t.C
}
}()
}