写在前面
记录 Go 的一些关于定时任务的处理
内容
基础库
for + sleep
func main() {
for {
time.Sleep(5 * time.Second)
fmt.Println("every 5s")
}
}
Ticker
func main() {
ticker := time.NewTicker(5 * time.Second)
for range ticker.C {
fmt.Println("every 5s")
}
}
第三方库
cron
前面两个都是基础库里的东西,可以实现一些基本的定时功能,但对于一些稍显复杂的定时任务需求,就需要我们自己去做更多的内容来支持,比如说需求需要在每个月的 1 号,或是每周一之类的来进行任务,就需要我们自己去写代码来判断了。而 cron 这个第三方库,则能很好地支撑我们这类的需求。
安装
go get github.com/robfig/cron/v3@v3.0.0
cron 对于定时的设置有几种写法,都能达到同样的目的。
@表达式
func descriptors() {
c := cron.New()
// @every
// 支持秒级别,会在启动 6s 后执行
_, errEveryS := c.AddFunc("@every 6s", func() {
fmt.Println("every 6s")
})
if errEveryS != nil {
fmt.Printf("errEveryS: %v\n", errEveryS)
}
// 0.1 min = 6s
_, errEveryM := c.AddFunc("@every 0.1m", func() {
fmt.Println("every 0.1m")
})
if errEveryM != nil {
fmt.Printf("errEveryM: %v\n", errEveryM)
}
// @yearly @annually 每年执行一次,即 1 月 1 日的时候开始执行
_, errYearly := c.AddFunc("@yearly", func() {
fmt.Println("yearly")
})
if errYearly != nil {
fmt.Printf("errYearly: %v\n", errYearly)
}
_, errAnnually := c.AddFunc("@annually", func() {
fmt.Println("annually")
})
if errAnnually != nil {
fmt.Printf("errAnnually: %v\n", errAnnually)
}
// @monthly 每个月的第一天的零点执行
_, errMonthly := c.AddFunc("@monthly", func() {
fmt.Println("monthly")
})
if errMonthly != nil {
fmt.Printf("errMonthly: %v\n", errMonthly)
}
// @weekly 每周的第一天零点执行,这个第一天可能是周六,也可能是周日
_, errWeekly := c.AddFunc("@weekly", func() {
fmt.Println("weekly")
})
if errWeekly != nil {
fmt.Printf("errWeekly: %v\n", errWeekly)
}
// @hourly 每小时执行一次,启动后一小时执行
_, errHourly := c.AddFunc("@hourly", func() {
fmt.Println("hourly")
})
if errHourly != nil {
fmt.Printf("errHourly: %v\n", errHourly)
}
// @daily @midnight,每天执行一次,在每天的零点执行
_, errDaily := c.AddFunc("@daily", func() {
fmt.Println("daily")
})
if errDaily != nil {
fmt.Printf("errDaily: %v\n", errDaily)
}
_, errMidnight := c.AddFunc("@midnight", func() {
fmt.Println("midnight")
})
if errMidnight != nil {
fmt.Printf("errMidnight: %v\n", errMidnight)
}
c.Start()
defer c.Stop()
select {}
}
linux cron 表达式
如果我们想要更灵活的一些处理,就需要学习下 Linux 的 cron 表达式,参照如下:
* * * * *
- - - - -
| | | | |
| | | | +----- day of week (0 - 7) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
| | | +---------- month (1 - 12) OR jan,feb,mar,apr ...
| | +--------------- day of month (1 - 31)
| +-------------------- hour (0 - 23)
+------------------------- minute (0 - 59)
只要我们学会了 Linux 的 cron 表达式的使用,也就可以直接用在 cron 库上,我们可以看到 Linux cron 表达式的标准实现里,最小的时间单位是支持到分钟级别,秒级别的话我们可以用上面的方案处理。
func linux() {
c := cron.New()
// 每1分钟执行,等同于 */1 * * * * 和 @every 1m
_, err := c.AddFunc("* * * * *", func() {
fmt.Println("* * * * *")
})
if err != nil {
fmt.Printf("err: %v\n", err)
}
// 每 5 分钟执行,等同于 @every 5m
// / 表示频率
_, err1 := c.AddFunc("*/5 * * * *", func() {
fmt.Println("* * * * *")
})
if err1 != nil {
fmt.Printf("err1: %v\n", err1)
}
// 每个小时的 01 分执行
_, err2 := c.AddFunc("1 * * * *", func() {
fmt.Println("1 * * * *")
})
if err2 != nil {
fmt.Printf("err2: %v\n", err2)
}
// 每个月的第一天的 0 点 0 分运行,相当于 @monthly
_, err3 := c.AddFunc("0 0 1 * *", func() {
fmt.Println("0 0 1 * *")
})
if err3 != nil {
fmt.Printf("err3: %v\n", err3)
}
// 每周的第一天(周六或周日)的 0 点 0 分运行,相当于 @weekly
// 像一些发奖需求,在每周一进行发奖,就可以写成 0 0 * * 1
_, err4 := c.AddFunc("0 0 * * 0", func() {
fmt.Println("0 0 * * 0")
})
if err4 != nil {
fmt.Printf("err4: %v\n", err4)
}
// 每天的 0 点 0 分运行一次,相当于 @daily 和 @midnight
_, err5 := c.AddFunc("0 0 * * *", func() {
fmt.Println("0 0 * * *")
})
if err5 != nil {
fmt.Printf("err5: %v\n", err5)
}
// 每年运行一次,在 1 月 1 日 0 点 0 分,相当于 @yearly @annually
_, err6 := c.AddFunc("0 0 1 1 *", func() {
fmt.Println("0 0 1 1 *")
})
if err6 != nil {
fmt.Printf("err6: %v\n", err6)
}
// 每小时运行一次,相当于 @hourly
_, err7 := c.AddFunc("0 * * * *", func() {
fmt.Println("0 * * * *")
})
if err7 != nil {
fmt.Printf("err7: %v\n", err7)
}
// - 表示范围 , 表示列表
// 这里的意思是,在每天的 3 点到 6 点和 20 点到 23 点这两个范围内,每个 30 分执行一次
// 也就是 3 点 30,4 点 30,5 点 30 这样
_, err8 := c.AddFunc("30 3-6,20-23 * * *", func() {
fmt.Println("30 3-6,20-23 * * *")
})
if err8 != nil {
fmt.Printf("err8: %v\n", err8)
}
c.Start()
defer c.Stop()
select {}
}
更进一步的,假如我们在做海外的应用,有时需要根据时区来进行定时任务,那么可以这么写:
func timezone() {
c := cron.New()
// 在东京时区,每天的 4 点 30 分运行
_, err := c.AddFunc("CRON_TZ=Asia/Tokyo 30 04 * * *", func() {
fmt.Println("CRON_TZ=Asia/Tokyo 30 04 * * *")
})
if err != nil {
fmt.Printf("err: %v\n", err)
}
c.Start()
defer c.Stop()
// 或者在 cron 初始化的时候指定时区
tc, _ := time.LoadLocation("Asia/Tokyo")
c1 := cron.New(cron.WithLocation(tc))
_, err1 := c1.AddFunc("30 04 * * *", func() {
fmt.Println("30 04 * * *")
})
if err1 != nil {
fmt.Printf("err1: %v\n", err1)
}
c1.Start()
defer c1.Stop()
select {}
}
job
在工作中,为了做到封装的效果,我们可以实现 Job 接口,把任务给封装起来:
func job() {
c := cron.New()
j1 := MyJob{name: "1"}
_, errJob1 := c.AddJob("@every 5s", &j1)
if errJob1 != nil {
fmt.Printf("errJob1: %v", errJob1)
}
j2 := MyJob{name: "2"}
_, errJob2 := c.AddJob("@every 6s", &j2)
if errJob2 != nil {
fmt.Printf("errJob2: %v", errJob2)
}
c.Start()
select {}
}
// MyJob 对定时任务进行包装
type MyJob struct {
name string
}
// Run 实现 Job 接口里的 Run 方法
func (j *MyJob) Run() {
fmt.Printf("myjob : %s\n", j.name)
}
schedule
如果我们想动态的调整任务的下一次运行的时间,就可以通过实现 Schedule 接口来处理:
func schedule() {
c := cron.New()
// 为了方便,这里用 FuncJob,就不去创建 Job 结构体了
c.Schedule(&MySchedule{}, cron.FuncJob(func() {
fmt.Println("hello")
}))
c.Start()
select {}
}
type MySchedule struct {
}
// Next 实现 Schedule 接口的 Next 方法
// Start 的时候,会触发 Next 方法,我们可以在这个的基础上,返回下一次的运行时间
// 之后每次触发都会调用 Next,我们就可以在这里做文章,动态修改下一次的触发时间
func (s *MySchedule) Next(t time.Time) time.Time {
fmt.Printf("now time: %v\n", t)
result := t.Add(5 * time.Second)
return result
}
参考
crontab执行时间计算
cron github