Why
在应用开发中,经常需要一些周期性的操作,如:在每天凌晨分析前一天的日志、每隔5分钟检查某些业务情况并触发告警等等。这些功能需要使用定时任务的方法去实现,那我们是如何使用 Golang 去实现的呢?
What
推荐使用 github 上的 Golang 开源库来实现定时任务,介绍两个常用的库:robfig/cron 和 jasonlvhit/gocron/
- robfig/cron:说到定时任务,会想到
crontab
,其常见于Unix和类Unix的操作系统之中。robfig/cron 库使用了类 crontab 的方式来执行定时任务。 - jasonlvhit/gocron:类 crontab 的设置方式可能并不友好,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 main
import (
"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累加并记录打印):
其中,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 | * / , – ? |
备注:
- 月和星期段的值不区分大小写,如:SUN、Sun和 sun是一样的。
- 星期(Day of week)字段以前的版本貌似非必填,默认* 现在版本就带上吧。
特殊字符说明
- 星号( * ):表示 cron表达式能匹配该字段的所有值。
- 斜线( / ):表示增长间隔;如:
spec := "*/5 * * * * *"
- 逗号( , ):用于枚举值,如第6个字段值是 MON,WED,FRI,表示星期一、三、五执行;又例如:
spec := "* 0,59 1 * * *"
,表示每天01:00和 01:59分的每秒都执行一次 - 连字号( - ):表示一个范围,如第3个字段的值为 9-17 表示 9am到 5pm直接每个小时(包括9和17)
- 问号( ? ):只用于日(Day of month)和星期(Day of week),表示不指定值,可以用于代替 *
你也可以使用一些预设的定时器来方便执行,如下:
Entry | Description | Equivalent To |
---|---|---|
@yearly (or @annually) | 每年执行 Run once a year, midnight, Jan. 1st | 0 0 0 1 1 * |
@monthly | 每月执行 Run once a month, midnight, first of month | 0 0 0 1 * * |
@weekly | 每周执行 Run once a week, midnight between Sat/Sun | 0 0 0 * * 0 |
@daily (or @midnight) | 每天执行 Run once a day, midnight | 0 0 0 * * * |
@hourly | 每小时执行 Run once an hour, beginning of hour | 0 0 * * * * |
更多参考:https://godoc.org/github.com/robfig/cron
gocron
安装 go get -u github.com/jasonlvhit/gocron
Demo
package main
import (
"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接口的命名及使用相对人性化,基本可直读,参考以下官方示例:
package main
import (
"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()
}