实际业务中有一种需求,需要一个时刻或者时间段需要执行某种业务。最简单的一种方式就是使用定时器来检测时间,当定时器触发事件时,获取当前的时间,判断当前时间与任务执行时间是否匹配,匹配的情况下开始执行调度任务。除此之外,在linux平台下,系统提供了定期执行任务的命令crontab
。在go中有一个第三方包cron
也是能够提供类似的功能,下面我们就这两个大的方面记录一下。
1.定时器调度任务
1.1. 匹配字符串
获取当前的时间,将时间格式化成特定的格式,再通过字符串匹配的方式判断当前的时间是否是需要的时间点。如下:
// 判断是否是特定时间点
func example1() {
now := time.Now()
strNow := now.Format(layout)
strStamp := fmt.Sprintf("%04d-%02d-%02d %s", now.Year(), now.Month(), now.Day(), tStamp)
if strStamp == strNow {
log.Printf("整点时间[%v],开始执行调度任务", strNow)
}
}
1.2.匹配时间戳
先获取到今天凌晨的时间戳,然后结合目的时间字符串计算出目的时间在今天的时间戳。在定时任务中检测当前时间是否与目的时间戳相等,从而判断是否满足时间的匹配。示例:
// 判断是否到了凌晨时间点
func example2(stamp int64) {
nowStamp := time.Now().Unix()
if nowStamp == stamp {
date_time := time.Unix(nowStamp, 0)
log.Printf("到了时间:[%v],开始执行调度任务", date_time.Format(layout))
}
}
计算目的时间的时间戳:
//目的时刻
const tStamp = "15:40:00"
//目的时刻今天的时间戳
stamp := time.Date(now.Year(), now.Month(), now.Day(), getInt(ts[0]), getInt(ts[1]), getInt(ts[2]), 0, time.Local)
//string转int函数
func getInt(s string) int {
i, _ := strconv.ParseInt(s, 10, 32)
return int(i)
}
1.3.时间操作拓展
1.3.1 判断时间整点
判断当前的时间是否整点,类似xx:00:00
的时刻。示例:
// 判断是否到了凌晨时间点
func example2(stamp int64) {
nowStamp := time.Now().Unix()
if nowStamp == stamp {
date_time := time.Unix(nowStamp, 0)
log.Printf("到了时间:[%v],开始执行调度任务", date_time.Format(layout))
}
}
1.3.2 获取凌晨时间戳
获取今天的凌晨时间戳,获取明天凌晨的时间戳示例:
// 获取今天,明天的凌晨时间戳
func getMidnightStamp() {
now := time.Now()
todayMidNightStamp := now.Unix() - int64(now.Second()) - int64((60 * now.Minute())) - int64((60 * 60 * now.Hour()))
tomMidnighttamp := now.Unix() + int64((23-now.Hour())*60*60) + int64((59-now.Minute())*60) + int64(60-now.Second())
log.Printf("当前的时间戳:[%v]", now.Unix())
log.Printf("今天的零点时间戳:[%v]", todayMidNightStamp)
log.Printf("明天的零点时间戳:[%v]", tomMidnighttamp)
log.Printf("明天和今天的零点时间戳差:[%v]", tomMidnighttamp-todayMidNightStamp)
}
2.cron定时调度任务的基本使用
在这个示例中使用的是"github.com/robfig/cron.v3"
版本,此处拉去v3版本,v3版本完全适配了crotab的语法,取消了秒级别的定时调度任务。但是为了兼容秒级别的调度任务需要自定义一个解析器来解析调度时间字符串。以下的示例中为了测试方便,最大的时间跨度设置在分钟级别,主要以秒来说明cron时间的含义。
2.1. 默认的定期调度任务
要求在每个小时的第1分钟执行某一个调度任务,用打印一行字符串来模拟调度任务。示例:
// 分 时 日 月 周
spec0 := "1 * * * *"
cDefault := cron.New()
_, err = cDefault.AddFunc(spec0, func() {
log.Printf("默认调度任务")
})
if err != nil {
panic(err)
}
cDefault.Start()
上述代码定义了一个名为cDefault
的cron对象,调度时间"1 * * * *"
表示在每小时的第1分钟执行调度函数,调度函数打印一行字符。我们在crontab.guru
上查看cron的含义,如下图:
2.2 在v3版本下使用秒级的调度任务
上面介绍的crontab.guru
在线检测调度时间的函数不支持秒级别的,需要自己判断了。但是基本上和分钟级别的一致。v3版本默认的使用的是最小分钟级别的调度,但是也兼容了之前版本中秒级的任务。需要自定义一个秒级的调度器对象,再传递秒级的调度时间就能够实现。下面示例是实现了一个函数,该能够返回一个秒级的调度对象。
func newWithSeconds() *cron.Cron {
secondParser := cron.NewParser(cron.Second | cron.Minute |
cron.Hour | cron.Dom | cron.Month | cron.DowOptional | cron.Descriptor)
return cron.New(cron.WithParser(secondParser), cron.WithChain())
}
2.2.1 每分钟第1秒执行调度任务
秒级调度任务实现每分钟的第1秒钟执行调度任务。示例:
spec1 := "1 * * * * ?"
cSecond := newWithSeconds()
_, err = cSecond.AddFunc(spec1, func() {
log.Printf("秒定时器执行")
})
if err != nil {
panic(err)
}
cSecond.Start()
运行结果如下,每分钟的01秒执行调度任务:
2.2.2 每隔1秒执行调度任务
将2.2.1中的spec1
中的调度时间换成spec1 := "*/1 * * * * ?"
即可。运行结果:
2.3 cron调度时间的解释
在cron的v3版本中默认使用标准的格式,官方文档描述:
Standard cron spec parsing by default (first field is “minute”)
从左到右依次为:分 时 日 月 周
,中间用空格间隔。格式表示的含义:
字段名称 | 必填? | 允许的值 | 允许的特殊字符
---------- | ---------- | -------------- | --------------------------
Minutes | Yes | 0-59 | * / , -
Hours | Yes | 0-23 | * / , -
Day of month | Yes | 1-31 | * / , - ?
Month | Yes | 1-12 or JAN-DEC | * / , -
Day of week | Yes | 0-6 or SUN-SAT | * / , - ?
编写的调度时间是否准确可以通过crontab.guru来判断,并且有具体的时间调度解释。