2021-11-13 00:52:59

golang cron源码解读

cron

https://github.com/robfig/cron 这个定时任务比较流行,我在项目中也在用。不过在阅读源码前,为了保证任务是串行运行,我还加了处理代码。
看了源码后才知道本身就是支持的。

源码学习

整理类图如下

cron

说明

核心概念其实就3个。

第一个是怎么定义调度。

包括2种,一种是crontab那种,不过这里包括了秒。 秒 分 时 几号 几月 周几

第二种时文字描述型的,以 @符号开头,如 @yearly @every 3ms

第二个就是怎么调度。

他是通过比特位来计算的。譬如 几月, 有效月是1~12月,那么 就是1~12的比特位置1, 其他位置0. 当你选3月时,只有3月的比特位是1.

如此就可以计算出下次运行的时间。

第三个是运行任务。Cron结构体干的事。

把待运行的entries 先计算好下一次的运行时间。

然后排序,时间按升序排,时间无效的排最后。

没有合适的任务,就启动一个超长的timer, 否则选离得最近的时间启个timer.

然后就是for循环处理事件:

  1. 到时间了。运行到时间的Job(启动goroutine运行的), 并计算好下一次运行时间。

  2. 有新添加任务。停止timer, 重新计算。

  3. 需要取快照。把entries 塞到channel 里。这时 Entries() 调用返回拿到数据。

  4. stop, 停止timer, 结束goroutine.

  5. 删除, 停止timer, 删除entry, 重新计算。

使用

官网示例是:

	c := cron.New()
	c.AddFunc("30 * * * *", func() { fmt.Println("Every hour on the half hour") })
	c.AddFunc("30 3-6,20-23 * * *", func() { fmt.Println(".. in the range 3-6am, 8-11pm") })
	c.AddFunc("CRON_TZ=Asia/Tokyo 30 04 * * *", func() { fmt.Println("Runs at 04:30 Tokyo time every day") })
	c.AddFunc("@hourly",      func() { fmt.Println("Every hour, starting an hour from now") })
	c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty, starting an hour thirty from now") })
	c.Start()

我个人建议日志写文件,可以结合其他的logger 库。

然后把recover, 串行运行等加上。

	logger := cron.DefaultLogger
	c := cron.New(
		cron.WithChain(cron.Recover(logger), cron.SkipIfStillRunning(logger)))
	c.AddFunc("30 * * * *", func() { fmt.Println("Every hour on the half hour") })
	c.AddFunc("30 3-6,20-23 * * *", func() { fmt.Println(".. in the range 3-6am, 8-11pm") })
	c.AddFunc("CRON_TZ=Asia/Tokyo 30 04 * * *", func() { fmt.Println("Runs at 04:30 Tokyo time every day") })
	c.AddFunc("@hourly",      func() { fmt.Println("Every hour, starting an hour from now") })
	c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty, starting an hour thirty from now") })
  c.Start()// 如果是web服务等,用Start. 如果就是单纯的cron跑任务。用Run()

默认的版本是标准的cron 格式,时间粒度只能到分。如果要支持秒级的定时任务。需要自动一下。

logger := cron.DefaultLogger
c := cron.New(
		cron.WithChain(cron.Recover(logger), cron.SkipIfStillRunning(logger)),
		cron.WithParser(cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)),
		)
...
  c.Start()// 如果是web服务等,用Start. 如果就是单纯的cron跑任务。用Run()

本文链接:http://blog.go2live.cn/post/golang-cron.html

-- EOF --