Why

在应用开发中,经常需要一些周期性的操作,如:在每天凌晨分析前一天的日志、每隔5分钟检查某些业务情况并触发告警等等。这些功能需要使用定时任务的方法去实现,那我们是如何使用 Golang 去实现的呢?

What

推荐使用 github 上的 Golang 开源库来实现定时任务,介绍两个常用的库:robfig/cron 和 jasonlvhit/gocron

至于选取哪个?熟悉 crontab 的可以选择 cron,想可读性佳的可以选择 gocron,两者的功能及稳定性都还不错。

如果需要开箱即用的定时任务管理系统(前后端),推荐国人开源的 ouqiang/gocron   Star & Fork 数 Σ(o゚д゚oノ)

部门内部目前用的就是这,还是很方便哒~  (づ。◕‿‿◕。)づ  作者 README 写得很清晰,这里就不引申介绍了

How

以下,简单介绍下 robfig/cron 和 jasonlvhit/gocron 的使用

cron

安装 go get -u github.com/robfig/cron

Demo

package mainimport (	"log""github.com/robfig/cron")func main() {
	i := 0
	c := cron.New()
	spec := "*/5 * * * *"
	c.AddFunc(spec, func() {
		i++
		log.Println("execute per 5 seconds", i)
	})
	c.Start()	select {}
}复制代码

执行结果如下(代码说明:每5秒执行一次,i累加并记录打印):cron demo

其中,select的用法:golang 的 select 的功能和 select, poll, epoll 相似,就是监听 IO 操作,当 IO 操作发生时,触发相应的动作。

类似的,如果需要执行每分钟执行,其中 spec := "0 */1 * * * *" 改一下即可,了解 crontab 的人应该不陌生其表达格式。总结如下:

字段名是否必须允许的值允许的特定字符
秒(Seconds)0-59* / , –
分(Minutes)0-59* / , –
时(Hours)0-23* / , –
日(Day of month)1-31* / , – ?
月(Month)1-12 or JAN-DEC* / , –
星期(Day of week)0-6 or SUM-SAT* / , – ?

备注:

  1. 月和星期段的值不区分大小写,如:SUN、Sun和 sun是一样的。
  2. 星期(Day of week)字段以前的版本貌似非必填,默认* 现在版本就带上吧。

特殊字符说明

  1. 星号( * ):表示 cron表达式能匹配该字段的所有值。
  2. 斜线( / ):表示增长间隔;如:spec := "*/5 * * * * *" 
  3. 逗号( , ):用于枚举值,如第6个字段值是 MON,WED,FRI,表示星期一、三、五执行;又例如: spec := "* 0,59 1 * * *",表示每天01:00和 01:59分的每秒都执行一次
  4. 连字号( - ):表示一个范围,如第3个字段的值为 9-17 表示 9am到 5pm直接每个小时(包括9和17)
  5. 问号( ? ):只用于日(Day of month)和星期(Day of week),表示不指定值,可以用于代替 *

你也可以使用一些预设的定时器来方便执行,如下:

EntryDescriptionEquivalent To
@yearly (or @annually)每年执行 Run once a year, midnight, Jan. 1st0 0 0 1 1 *
@monthly每月执行 Run once a month, midnight, first of month0 0 0 1 * *
@weekly每周执行 Run once a week, midnight between Sat/Sun0 0 0 * * 0
@daily (or @midnight)每天执行 Run once a day, midnight0 0 0 * * *
@hourly每小时执行 Run once an hour, beginning of hour0 0 * * * *


gocron

安装  go get -u github.com/jasonlvhit/gocron

Demo

package mainimport (	"log""github.com/jasonlvhit/gocron")func main() {
	i := 0
	s := gocron.NewScheduler()
	s.Every(5).Seconds().Do(func() {
		i++
		log.Println("execute per 5 seconds", i)
	})
	<-s.Start()
}复制代码

执行效果如下:gocron demo

以上为基础定时器使用方式,gocron接口的命名及使用相对人性化,基本可直读,参考以下官方示例:

package mainimport (	"fmt""github.com/jasonlvhit/gocron")func task() {
	fmt.Println("I am runnning task.")
}func taskWithParams(a int, b string) {
	fmt.Println(a, b)
}func main() {	// Do jobs with params
	gocron.Every(1).Second().Do(taskWithParams, 1, "hello")	
	// Do jobs safely, preventing an unexpected panic from bubbling up
	gocron.Every(1).Second().DoSafely(taskWithParams, 1, "hello")	// Do jobs without params
	gocron.Every(1).Second().Do(task)
	gocron.Every(2).Seconds().Do(task)
	gocron.Every(1).Minute().Do(task)
	gocron.Every(2).Minutes().Do(task)
	gocron.Every(1).Hour().Do(task)
	gocron.Every(2).Hours().Do(task)
	gocron.Every(1).Day().Do(task)
	gocron.Every(2).Days().Do(task)	// Do jobs on specific weekday
	gocron.Every(1).Monday().Do(task)
	gocron.Every(1).Thursday().Do(task)	// function At() take a string like 'hour:min'
	gocron.Every(1).Day().At("10:30").Do(task)
	gocron.Every(1).Monday().At("18:30").Do(task)	// remove, clear and next_run
	_, time := gocron.NextRun()
	fmt.Println(time)

	gocron.Remove(task)
	gocron.Clear()	// function Start start all the pending jobs
	<- gocron.Start()	// also, you can create a new scheduler// to run two schedulers concurrently
	s := gocron.NewScheduler()
	s.Every(3).Seconds().Do(task)
	<- s.Start()
}复制代码