关于crontab格式的一些理解
业务有延迟任务需求,开始考虑使用 crontab
实现。但调研之后发现 crontab
对于实现某小时后执行某个操作这类需求并不方便,特此记录。
crontab
格式一般为 * * * * *
,分别代表 minute
, hour
, day of month
, month
, year
。指定某个时刻执行任务,表达式比较直观, 7 8 9 10 *
表示 每年的10月9号8点7分执行一次
,*/5 * * * *
表示 每5分钟执行一次
。
如果要实现每隔2小时执行任务,第一反应会将表达式写为 * */2 * * *
。在crontab解析工具中获取最近7次执行时间如下:
2022-01-15 16:22:00
2022-01-15 16:23:00
2022-01-15 16:24:00
2022-01-15 16:25:00
2022-01-15 16:26:00
2022-01-15 16:27:00
2022-01-15 16:28:00
得到的结果为每分钟执行一次,与预期不符。因为分钟域的策略与小时域策略冲突,会将小时域策略短路。为了实现预期目的,需在分钟域指定具体分钟数。改为 22 */2 * * *
,假设当前时间为 16:39:30
,查询到的近7次执行时间入下:
2022-01-15 18:22:00
2022-01-15 20:22:00
2022-01-15 22:22:00
2022-01-16 00:22:00
2022-01-16 02:22:00
2022-01-16 04:22:00
2022-01-16 06:22:00
执行间隔与预期相符。
但也存在一些场景,首次执行时间会跟预期出现偏差:
- 场景一
2022/01/15 16:57:00 cronStr: 57 */2 * * *
2022/01/15 16:57:00 base time: 2022-01-15 16:27:00.023912 +0800 CST m=-1799.999701853
2022/01/15 16:57:00 next time: 2022-01-15 16:57:00 +0800 CST
- 场景二
2022/01/15 17:09:57 cronStr: 9 */2 * * *
2022/01/15 17:09:57 base time: 2022-01-15 17:09:57.354229 +0800 CST m=+0.000260331
2022/01/15 17:09:57 next time: 2022-01-15 18:09:00 +0800 CST
场景一下第一次触发时刻为base time的半小时后,场景二下第一次触发时刻为1小时后。使用 crontab
做指定间隔任务,首次执行时间是不固定的。
经过多次测试,发现 crontab
在指定执行间隔的场景下,执行时刻规律如下:
计算小时域执行时刻时,始终以0点作为基准。如果小时域表达式为 */2
,则小时域计算得到的触发时刻列表为 2,4,6,8,10,12...18,20,22,0
。如果表达式为 */7
,结果为 7,14,21
。即触发时刻列表为表达式 */n
中n的整数倍(不一定包含0)。最近触发时刻则为列表中距离基准时刻最近的时刻。
func testCron() {
parser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
min := time.Now().Minute()
cronStr := fmt.Sprintf("%d */7 * * *", min)
log.Printf("cronStr: %s\n", cronStr)
sched, err := parser.Parse(cronStr)
if err != nil {
log.Fatal(err)
}
m, _ := time.ParseDuration("-30m")
baseTime := time.Now().Add(m)
log.Printf("base time: %+v\n", baseTime)
next := sched.Next(baseTime)
log.Printf("next time: %+v\n", next)
}