Golang如何對cron進行二次封裝實現(xiàn)指定時間執(zhí)行定時任務
背景
go中常用的定時任務庫:https://github.com/robfig/cron,不支持指定某個時間開始執(zhí)行一次任務,然后再以一定的時間間隔開始執(zhí)行任務。
在真實的業(yè)務場景中可能會有這樣的需求:進行某個操作時執(zhí)行一次任務,然后根據(jù)這個操作時間每隔一段時間執(zhí)行一次任務;再次操作時,立即執(zhí)行一次任務,然后根據(jù)此次操作的時間點每隔一段時間執(zhí)行一次任務,這時就需要停止上一次操作產(chǎn)生的定時間隔執(zhí)行任務。
下面就根據(jù)go中的cron庫進行封裝來滿足上述場景的功能。
cron庫下載
go get -u github.com/robfig/cron/v3
代碼示例
【1】結構體定義
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í)行的任務id
jobMutex sync.Mutex //防止并發(fā)問題
startTime time.Time //定時任務從此時間開始執(zhí)行
intervalTime uint32 //任務執(zhí)行間隔時間,不設置默認8小時
intervalType uint8 //任務執(zhí)行間隔類型,1-時,2.分,3-秒,不設置默認單位為時
f func() error //要執(zhí)行的任務
}
type OpOption func(*VarCron)
func NewVarCron(f func() error, opts ...OpOption) *VarCron {
vc := &VarCron{
c: cron.New(cron.WithSeconds()), //支持秒級調(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
}
}
上面定義一個新的結構體VarCron就是給原生的cron對象增加一些新的屬性字段。
| 字段 | 含義 |
|---|---|
| jobId | 定時執(zhí)行任務的id,只支持一個任務,目的是為了方便管理,如果想支持多個,可以多使用NewVarCron初始化多個對象分別執(zhí)行各自的定時任務 |
| jobMutex | 保證并發(fā)安全 |
| startTime | 首次執(zhí)行任務的時間 |
| intervalTime | 定時任務執(zhí)行間隔 |
| intervalType | 定時任務執(zhí)行類型 |
| f | 定時任務中執(zhí)行的函數(shù) |
【2】定時任務開啟
func (v *VarCron) Start(startTime time.Time) {
v.jobMutex.Lock() //保證同一時刻只有一個在執(zhí)行
defer v.jobMutex.Unlock()
logger.Info("start var cron", zap.Time("last_start_time", v.startTime), zap.Time("this_start_time", startTime))
//更新開始時間
v.startTime = startTime
//停止上一次的定時任務
v.stop()
now := time.Now()
if now.After(v.startTime) || now.Equal(v.startTime) { //定時任務指定的執(zhí)行時間在此刻之前就立即執(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í)行定時任務
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
//開始定時任務
v.c.Start()
} else {
logger.Info("now time before start time", zap.Time("now_time", now), zap.Time("start_time", v.startTime))
//到指定時間時間之后再開始執(zhí)行定時任務,精確到某月某天某時某分某秒就行
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() {
//停止上一次的定時任務
v.stop()
//這次是到指定時間之后的執(zhí)行
v.execJob()
//根據(jù)間隔開啟定時任務
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) //移除任務
v.c.Stop() //關閉定時任務
}
}
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ù)會有兩種場景:
- 場景1:startTime在當前時間之前,這個時候會立即執(zhí)行一次任務,然后按照間隔定時執(zhí)行任務。
- 場景2:startTime在當前時間之后,這個時候會等待時間到達startTime執(zhí)行一次,然后按照間隔定時執(zhí)行任務。
上面代碼中的stop函數(shù)目的就是為了停止上一次的任務,保證不會有沖突的定時任務執(zhí)行。
【3】使用示例
f := func() error {
logger.Info("exec task")
return nil
}
//每30秒打印一次時間
varCron := self_cron.NewVarCron(f, self_cron.WithIntervalType(self_cron.SECOND), self_cron.WithIntervalTime(30))
fmt.Println("===== 指定開始時間在當前時間之前,會立即執(zhí)行一次,然后定時執(zhí)行 ======")
varCron.Start(time.Now().AddDate(0, 0, -1)) //AddDate(0, 0, -1)的作用是將日期向前推一天
time.Sleep(3 * time.Minute) //第一種場景定時執(zhí)行任務跑三分鐘
fmt.Println("===== 指定開始時間在當前時間之后,到指定時間執(zhí)行一次,然后再開始定時執(zhí)行 ======")
varCron.Start(time.Now().Add(time.Minute)) //一分鐘后立即執(zhí)行一次,然后開始定時執(zhí)行
time.Sleep(3 * time.Minute) //第二種場景定時執(zhí)行任務跑三分鐘就退出
上面是一個簡單使用的例子:注冊一個每隔30秒打印字符串exec task的函數(shù);第1次指定開始時間為昨天的這個時刻,這時會立即打印exec task一次,然后每隔30s打印一次exec task;3分鐘后第2次指定開始時間為當前時間的1分鐘之后,這時上一次的任務已經(jīng)被取消,1分鐘內(nèi)不會再打印exec task,1分鐘后會打印exec task,然后每隔30s打印一次exec task。
【4】控制臺輸出
$ go run ./cron_demo/main.go
===== 指定開始時間在當前時間之前,會立即執(zhí)行一次,然后定時執(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í)行時間在當前時間之前,立即執(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
===== 指定開始時間在當前時間之后,到指定時間執(zhí)行一次,然后再開始定時執(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 //指定時間到了之后才執(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
總結
上面給出一個指定一個時間開始執(zhí)行任務,然后再以一定的時間間隔定時執(zhí)行任務的功能,可以根據(jù)業(yè)務場景的需求去修改上述功能,新增一些屬性,比如除了動態(tài)修改定時任務時間,還可以動態(tài)修改時間間隔或執(zhí)行函數(shù),或者新增一個tag字段打印在日志中來區(qū)分不同的業(yè)務。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

