Golang如何對(duì)cron進(jìn)行二次封裝實(shí)現(xiàn)指定時(shí)間執(zhí)行定時(shí)任務(wù)
背景
go中常用的定時(shí)任務(wù)庫(kù):https://github.com/robfig/cron,不支持指定某個(gè)時(shí)間開始執(zhí)行一次任務(wù),然后再以一定的時(shí)間間隔開始執(zhí)行任務(wù)。
在真實(shí)的業(yè)務(wù)場(chǎng)景中可能會(huì)有這樣的需求:進(jìn)行某個(gè)操作時(shí)執(zhí)行一次任務(wù),然后根據(jù)這個(gè)操作時(shí)間每隔一段時(shí)間執(zhí)行一次任務(wù);再次操作時(shí),立即執(zhí)行一次任務(wù),然后根據(jù)此次操作的時(shí)間點(diǎn)每隔一段時(shí)間執(zhí)行一次任務(wù),這時(shí)就需要停止上一次操作產(chǎn)生的定時(shí)間隔執(zhí)行任務(wù)。
下面就根據(jù)go中的cron庫(kù)進(jìn)行封裝來(lái)滿足上述場(chǎng)景的功能。
cron庫(kù)下載
go get -u github.com/robfig/cron/v3
代碼示例
【1】結(jié)構(gòu)體定義
import (
"fmt"
"sync"
"time"
"GoTest/comm/logger"
"github.com/robfig/cron/v3"
"go.uber.org/zap"
)
const (
HOUR = 1
MINUTE = 2
SECOND = 3
)
const defaultIntervalTime = 8
type VarCron struct {
c *cron.Cron
jobId cron.EntryID //執(zhí)行的任務(wù)id
jobMutex sync.Mutex //防止并發(fā)問題
startTime time.Time //定時(shí)任務(wù)從此時(shí)間開始執(zhí)行
intervalTime uint32 //任務(wù)執(zhí)行間隔時(shí)間,不設(shè)置默認(rèn)8小時(shí)
intervalType uint8 //任務(wù)執(zhí)行間隔類型,1-時(shí),2.分,3-秒,不設(shè)置默認(rèn)單位為時(shí)
f func() error //要執(zhí)行的任務(wù)
}
type OpOption func(*VarCron)
func NewVarCron(f func() error, opts ...OpOption) *VarCron {
vc := &VarCron{
c: cron.New(cron.WithSeconds()), //支持秒級(jí)調(diào)度
f: f,
}
for _, opt := range opts {
opt(vc)
}
return vc
}
func WithIntervalTime(intervalTime uint32) OpOption {
return func(vc *VarCron) {
vc.intervalTime = intervalTime
}
}
func WithIntervalType(intervalType uint8) OpOption {
return func(vc *VarCron) {
vc.intervalType = intervalType
}
}
上面定義一個(gè)新的結(jié)構(gòu)體VarCron就是給原生的cron對(duì)象增加一些新的屬性字段。
| 字段 | 含義 |
|---|---|
| jobId | 定時(shí)執(zhí)行任務(wù)的id,只支持一個(gè)任務(wù),目的是為了方便管理,如果想支持多個(gè),可以多使用NewVarCron初始化多個(gè)對(duì)象分別執(zhí)行各自的定時(shí)任務(wù) |
| jobMutex | 保證并發(fā)安全 |
| startTime | 首次執(zhí)行任務(wù)的時(shí)間 |
| intervalTime | 定時(shí)任務(wù)執(zhí)行間隔 |
| intervalType | 定時(shí)任務(wù)執(zhí)行類型 |
| f | 定時(shí)任務(wù)中執(zhí)行的函數(shù) |
【2】定時(shí)任務(wù)開啟
func (v *VarCron) Start(startTime time.Time) {
v.jobMutex.Lock() //保證同一時(shí)刻只有一個(gè)在執(zhí)行
defer v.jobMutex.Unlock()
logger.Info("start var cron", zap.Time("last_start_time", v.startTime), zap.Time("this_start_time", startTime))
//更新開始時(shí)間
v.startTime = startTime
//停止上一次的定時(shí)任務(wù)
v.stop()
now := time.Now()
if now.After(v.startTime) || now.Equal(v.startTime) { //定時(shí)任務(wù)指定的執(zhí)行時(shí)間在此刻之前就立即執(zhí)行
logger.Info("now time after or equal start time", zap.Time("now_time", now), zap.Time("start_time", v.startTime))
//立即執(zhí)行一次
v.execJob()
//間隔執(zhí)行定時(shí)任務(wù)
jobId, err := v.c.AddFunc(v.getInterval(), func() {
v.execJob()
})
if err != nil {
logger.Error("add func error", zap.Error(err))
return
}
v.jobId = jobId
//開始定時(shí)任務(wù)
v.c.Start()
} else {
logger.Info("now time before start time", zap.Time("now_time", now), zap.Time("start_time", v.startTime))
//到指定時(shí)間時(shí)間之后再開始執(zhí)行定時(shí)任務(wù),精確到某月某天某時(shí)某分某秒就行
jobId, err := v.c.AddFunc(fmt.Sprintf("%d %d %d %d %d ?", v.startTime.Second(), v.startTime.Minute(), v.startTime.Hour(), v.startTime.Day(), v.startTime.Month()), func() {
//停止上一次的定時(shí)任務(wù)
v.stop()
//這次是到指定時(shí)間之后的執(zhí)行
v.execJob()
//根據(jù)間隔開啟定時(shí)任務(wù)
newJobId, err := v.c.AddFunc(v.getInterval(), func() {
v.execJob()
})
if err != nil {
logger.Error("add func error", zap.Error(err))
return
}
v.jobId = newJobId
v.c.Start()
})
if err != nil {
logger.Error("add func error", zap.Error(err))
return
}
v.jobId = jobId
v.c.Start()
}
}
func (v *VarCron) stop() {
if v.c != nil {
v.c.Remove(v.jobId) //移除任務(wù)
v.c.Stop() //關(guān)閉定時(shí)任務(wù)
}
}
func (v *VarCron) execJob() {
if err := v.f(); err != nil {
logger.Error("exec job error", zap.Error(err))
}
}
func (v *VarCron) getInterval() string {
if v.intervalTime == 0 {
v.intervalTime = defaultIntervalTime
}
switch v.intervalType {
case HOUR:
return fmt.Sprintf("@every %dh", v.intervalTime)
case MINUTE:
return fmt.Sprintf("@every %dm", v.intervalTime)
case SECOND:
return fmt.Sprintf("@every %ds", v.intervalTime)
}
return fmt.Sprintf("@every %dh", v.intervalTime)
}
調(diào)用Start(startTime time.Time)函數(shù)會(huì)有兩種場(chǎng)景:
- 場(chǎng)景1:startTime在當(dāng)前時(shí)間之前,這個(gè)時(shí)候會(huì)立即執(zhí)行一次任務(wù),然后按照間隔定時(shí)執(zhí)行任務(wù)。
- 場(chǎng)景2:startTime在當(dāng)前時(shí)間之后,這個(gè)時(shí)候會(huì)等待時(shí)間到達(dá)startTime執(zhí)行一次,然后按照間隔定時(shí)執(zhí)行任務(wù)。
上面代碼中的stop函數(shù)目的就是為了停止上一次的任務(wù),保證不會(huì)有沖突的定時(shí)任務(wù)執(zhí)行。
【3】使用示例
f := func() error {
logger.Info("exec task")
return nil
}
//每30秒打印一次時(shí)間
varCron := self_cron.NewVarCron(f, self_cron.WithIntervalType(self_cron.SECOND), self_cron.WithIntervalTime(30))
fmt.Println("===== 指定開始時(shí)間在當(dāng)前時(shí)間之前,會(huì)立即執(zhí)行一次,然后定時(shí)執(zhí)行 ======")
varCron.Start(time.Now().AddDate(0, 0, -1)) //AddDate(0, 0, -1)的作用是將日期向前推一天
time.Sleep(3 * time.Minute) //第一種場(chǎng)景定時(shí)執(zhí)行任務(wù)跑三分鐘
fmt.Println("===== 指定開始時(shí)間在當(dāng)前時(shí)間之后,到指定時(shí)間執(zhí)行一次,然后再開始定時(shí)執(zhí)行 ======")
varCron.Start(time.Now().Add(time.Minute)) //一分鐘后立即執(zhí)行一次,然后開始定時(shí)執(zhí)行
time.Sleep(3 * time.Minute) //第二種場(chǎng)景定時(shí)執(zhí)行任務(wù)跑三分鐘就退出
上面是一個(gè)簡(jiǎn)單使用的例子:注冊(cè)一個(gè)每隔30秒打印字符串exec task的函數(shù);第1次指定開始時(shí)間為昨天的這個(gè)時(shí)刻,這時(shí)會(huì)立即打印exec task一次,然后每隔30s打印一次exec task;3分鐘后第2次指定開始時(shí)間為當(dāng)前時(shí)間的1分鐘之后,這時(shí)上一次的任務(wù)已經(jīng)被取消,1分鐘內(nèi)不會(huì)再打印exec task,1分鐘后會(huì)打印exec task,然后每隔30s打印一次exec task。
【4】控制臺(tái)輸出
$ go run ./cron_demo/main.go
===== 指定開始時(shí)間在當(dāng)前時(shí)間之前,會(huì)立即執(zhí)行一次,然后定時(shí)執(zhí)行 ======
[2024-10-08 17:23:38.135] | INFO | Goroutine:1 | [self_cron/start_cron.go:63] | start var cron | {"last_start_time": "[0001-01-01 00:00:00.000]", "this_start_time": "[2024-10-07 17:23:38.096]"}
[2024-10-08 17:23:38.136] | INFO | Goroutine:1 | [self_cron/start_cron.go:73] | now time after or equal start time | {"now_time": "[2024-10-08 17:23:38.136]", "start_time": "[2024-10-07 17:23:38.096]"}
[2024-10-08 17:23:38.136] | INFO | Goroutine:1 | [cron_demo/main.go:159] | exec task //指定的執(zhí)行時(shí)間在當(dāng)前時(shí)間之前,立即執(zhí)行一次,之后每隔30s執(zhí)行
[2024-10-08 17:24:08.013] | INFO | Goroutine:34 | [cron_demo/main.go:159] | exec task
[2024-10-08 17:24:38.008] | INFO | Goroutine:35 | [cron_demo/main.go:159] | exec task
[2024-10-08 17:25:08.010] | INFO | Goroutine:36 | [cron_demo/main.go:159] | exec task
[2024-10-08 17:25:38.008] | INFO | Goroutine:37 | [cron_demo/main.go:159] | exec task
[2024-10-08 17:26:08.015] | INFO | Goroutine:38 | [cron_demo/main.go:159] | exec task
[2024-10-08 17:26:38.005] | INFO | Goroutine:39 | [cron_demo/main.go:159] | exec task
===== 指定開始時(shí)間在當(dāng)前時(shí)間之后,到指定時(shí)間執(zhí)行一次,然后再開始定時(shí)執(zhí)行 ======
[2024-10-08 17:26:38.144] | INFO | Goroutine:1 | [self_cron/start_cron.go:63] | start var cron | {"last_start_time": "[2024-10-07 17:23:38.096]", "this_start_time": "[2024-10-08 17:27:38.144]"}
[2024-10-08 17:26:38.145] | INFO | Goroutine:1 | [self_cron/start_cron.go:92] | now time before start time | {"now_time": "[2024-10-08 17:26:38.145]", "start_time": "[2024-10-08 17:27:38.144]"}
[2024-10-08 17:27:38.003] | INFO | Goroutine:5 | [cron_demo/main.go:159] | exec task //指定時(shí)間到了之后才執(zhí)行,之后每隔30s執(zhí)行
[2024-10-08 17:28:08.007] | INFO | Goroutine:42 | [cron_demo/main.go:159] | exec task
[2024-10-08 17:28:38.008] | INFO | Goroutine:43 | [cron_demo/main.go:159] | exec task
[2024-10-08 17:29:08.013] | INFO | Goroutine:44 | [cron_demo/main.go:159] | exec task
[2024-10-08 17:29:38.013] | INFO | Goroutine:45 | [cron_demo/main.go:159] | exec task
總結(jié)
上面給出一個(gè)指定一個(gè)時(shí)間開始執(zhí)行任務(wù),然后再以一定的時(shí)間間隔定時(shí)執(zhí)行任務(wù)的功能,可以根據(jù)業(yè)務(wù)場(chǎng)景的需求去修改上述功能,新增一些屬性,比如除了動(dòng)態(tài)修改定時(shí)任務(wù)時(shí)間,還可以動(dòng)態(tài)修改時(shí)間間隔或執(zhí)行函數(shù),或者新增一個(gè)tag字段打印在日志中來(lái)區(qū)分不同的業(yè)務(wù)。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Go語(yǔ)言實(shí)現(xiàn)動(dòng)態(tài)開點(diǎn)線段樹詳解
線段樹是一種用于高效處理區(qū)間查詢和區(qū)間更新的數(shù)據(jù)結(jié)構(gòu),下面我們就來(lái)看看如何使用Go實(shí)現(xiàn)動(dòng)態(tài)開點(diǎn)線段樹的方式,感興趣的可以了解下2025-02-02
Go中map數(shù)據(jù)類型的實(shí)現(xiàn)
本文介紹了Go語(yǔ)言中的map數(shù)據(jù)類型,用于高效地存儲(chǔ)和管理鍵值對(duì),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12
使用Go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單聊天系統(tǒng)
本文介紹了如何使用Go語(yǔ)言和WebSocket技術(shù)構(gòu)建一個(gè)簡(jiǎn)單的多人聊天室系統(tǒng),包括客戶端連接管理、消息廣播和并發(fā)處理,最后,通過編寫main.go、hub.go和client.go等核心代碼模塊,具有一定的參考價(jià)值,感興趣的可以了解一下2024-10-10
Golang 使用map需要注意的幾個(gè)點(diǎn)
這篇文章主要介紹了Golang 使用map需要注意的幾個(gè)點(diǎn),幫助大家更好的理解和學(xué)習(xí)golang,感興趣的朋友可以了解下2020-09-09
golang time包下定時(shí)器的實(shí)現(xiàn)方法
定時(shí)器的實(shí)現(xiàn)大家應(yīng)該都遇到過,最近在學(xué)習(xí)golang,所以下面這篇文章主要給大家介紹了關(guān)于golang time包下定時(shí)器的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-12-12

