Go 每日一库之定时任务库:cron

共 6716字,需浏览 14分钟

 ·

2020-09-07 09:49


在Linux中,Cron是计划任务管理系统,通过crontab命令使任务在约定的时间执行已经计划好的工作,例如定时备份系统数据、周期性清理缓存、定时重启服务等。本文介绍的cron库,就是用Go实现Linux中crontab命令的相似效果。


使用示例


安装下载cron,目前最新的稳定版已经迭代到了v3


go get github.com/robfig/cron/v3@v3.0.0


在项目中导入包


import "github.com/robfig/cron/v3"


使用


 1package main
2
3import (
4    "fmt"
5
6    "github.com/robfig/cron/v3"
7)
8
9func main() {
10    c := cron.New()
11    c.AddFunc("30 * * * *"func() { fmt.Println("Every hour on the half hour") })
12    c.AddFunc("30 3-6,20-23 * * *"func() { fmt.Println(".. in the range 3-6am, 8-11pm") })
13    c.AddFunc("CRON_TZ=Asia/Tokyo 30 04 * * *"func() { fmt.Println("Runs at 04:30 Tokyo time every day") })
14    c.AddFunc("@hourly"func() { fmt.Println("Every hour, starting an hour from now") })
15    c.AddFunc("@every 1h30m"func() { fmt.Println("Every hour thirty, starting an hour thirty from now") })
16    c.AddFunc("@every 1s"func() {fmt.Println("Every 1 second, starting an hour thirty from now")})
17    c.Start()
18    select {}
19}


  • 创建cron对象


使用时,首先通过cron.New()创建cron对象,通过该对象管理定时任务。通过调用cronAddFunc()方法添加定时任务。AddFun()入参为二,参数一是以字符串的形式指定触发任务规则,参数二是无入参的函数,任务触发时执行函数。



  • 添加触发任务


30 * * * *表示每个小时内的第30分钟时触发;30 3-6,20-23 * * *表示在早上3点到6点,下午8点到11点的第30分钟时触发;CRON_TZ=Asia/Tokyo 30 04 * * *表示东京时间每天早上4点半触发;@hourly表示从添加该任务时算起的之后每小时触发;@every 1h30m表示从添加该任务时算起的之后每一个半小时触发;@every 1s表示从添加该任务时算起的之后每秒触发。



  • 启动定时循环


通过调用cron.Start()启动定时循环任务。

输出


1 $ go run main.go 
2Every 1 second, starting an hour thirty from now
3Every 1 second, starting an hour thirty from now
4Every 1 second, starting an hour thirty from now
5Every 1 second, starting an hour thirty from now
6...


由于只让以上程序运行了几秒的时间,因此,输出中只包含执行了每秒触发的打印。随着程序运行时间的加长,其他触发任务也会在满足条件时进行打印。



cron时间表达式规则


cron表达式默认通过使用5个以空格分隔的字段组合来表示触发时间(和linux的crontab保持一致)。


1Field name   | Mandatory? | Allowed values  | Allowed special characters
2----------   | ---------- | --------------  | --------------------------
3Minutes      | Yes        | 0-59            | * / , -
4Hours        | Yes        | 0-23            | * / , -
5Day of month | Yes        | 1-31            | * / , - ?
6Month        | Yes        | 1-12 or JAN-DEC | * / , -
7Day of week  | Yes        | 0-6 or SUN-SAT  | * / , - ?


如同30 * * * *一样,默认第1个字段表示分钟,第2个字段表示小时,第3个字段表示每月中的日期,第4个字段表示月份数,第5个字段表示星期几。

cron还提供了强大的自定义时间格式功能,可以通过调用cron.NewParser()创建自定义Parser对象,例如通过以下方式定义新的cron时间表达式规则


1cron.New(
2    cron.WithParser(
3        cron.NewParser(
4            cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)))


这样,时间字段一共是7位,第1位就是指定秒。秒执行的时间表达式就可以用1 * * * * * *表示。

因为添加Seconds是对标准cron规范的最常见修改,因此cron提供了一个内置函数cron.WithSeconds()来执行此操作,该函数等效于之前使用的自定义解析器。


1cron.New(cron.WithSeconds())


  • 预定义时间表


由于cron的时间表达式可读性不是很好,因此cron库预定义了一些字符串来表示特定的时间规则。


1Entry                  | Description                                | Equivalent To
2-----                  | -----------                                | -------------
3@yearly (or @annually) | Run once a year, midnight, Jan. 1st        | 0 0 1 1 *
4@monthly               | Run once a month, midnight, first of month | 0 0 1 * *
5@weekly                | Run once a week, midnight between Sat/Sun  | 0 0 * * 0
6@daily (or @midnight)  | Run once a day, midnight                   | 0 0 * * *
7@hourly                | Run once an hour, beginning of hour        | 0 * * * *



  • 时间间隔


cron还提供了更具可读性的固定时间间隔格式


1@every 


它代码每隔duration触发执行一次任务。这里的duration是通过调用标准库timeParseDuration()函数解析的,所以只要ParseDuration()支持的格式都能支持。例如上文示例的@every 1h30m@every 1s


cron可控选项


在cron源码option.go文件中,暴露了5个函数供开发者控制cron对象的选项。


  • WithLocation()


指定时区。默认情况下基于当前时区(在Unix系统中,查询TZ环境变量确定要使用的时区,若未定义TZ,则使用/etc/localtime文件中的定义时区)。可通过在时间字符串前添加CRON_TZ=字符串再加上具体的时区。例如东京时区为Asia/Tokyo


1c.AddFunc("CRON_TZ=Asia/Tokyo 30 04 * * *"func() { fmt.Println("Runs at 04:30 Tokyo time every day") })



  • WithParser()


自定义时间解析器,上文已有示例,这里不再赘述。



  • WithSeconds()


增加对秒的时间格式支持,其内部调用的WithParser()方法。


1func WithSeconds() Option {
2    return WithParser(NewParser(
3        Second | Minute | Hour | Dom | Month | Dow | Descriptor,
4    ))
5}



  • WithChain()


Job包装器,下文中会讲解Job接口。



  • WithLogger()


Logger是cron中用于记录日志的接口,WithLogger()可以设置自定义的Logger


 1package main
2
3import (
4    "fmt"
5    "log"
6    "os"
7
8    "github.com/robfig/cron/v3"
9)
10
11func main() {
12    c := cron.New(cron.WithLogger(cron.VerbosePrintfLogger(log.New(os.Stdout, "cron process: ", log.LstdFlags))))
13    c.AddFunc("@every 1s"func() { fmt.Println("Every 1 second") })
14    c.Start()
15    select {}
16}


该logger记录了cron内部的调度过程,输入如下


 1go run main.go 
2cron process: 2020/08/30 00:07:04 start
3cron process: 2020/08/30 00:07:04 schedule, now=2020-08-30T00:07:04+08:00, entry=1, next=2020-08-30T00:07:05+08:00
4cron process: 2020/08/30 00:07:05 wake, now=2020-08-30T00:07:05+08:00
5cron process: 2020/08/30 00:07:05 run, now=2020-08-30T00:07:05+08:00, entry=1, next=2020-08-30T00:07:06+08:00
6Every 1 second
7cron process: 2020/08/30 00:07:06 wake, now=2020-08-30T00:07:06+08:00
8Every 1 second
9cron process: 2020/08/30 00:07:06 run, now=2020-08-30T00:07:06+08:00, entry=1, next=2020-08-30T00:07:07+08:00
10cron process: 2020/08/30 00:07:07 wake, now=2020-08-30T00:07:07+08:00
11cron process: 2020/08/30 00:07:07 run, now=2020-08-30T00:07:07+08:00, entry=1, next=2020-08-30T00:07:08+08:00
12Every 1 second
13...


自定义Job


cron中定义了Job接口,对象只要实现了Job接口所定义的Run()方法,均可以调用cron.AddJob()方法将该对象添加到定时管理器中。


1// Job is an interface for submitted cron jobs.
2type Job interface {
3    Run()
4}


  • AddFunc()


在上文示例中,通过cron.AddFunc()方法为cron对象添加定时任务。实质上,AddFunc()方法内部调用的也是AddJob()方法:定义新类型对象FuncJob,为其实现Job接口,在AddFunc()方法中,将回调参数func()转为FuncJob类型,调用AddJob()方法。


1type FuncJob func()
2
3func (f FuncJob) Run()
 { f() }
4
5func (c *Cron) AddFunc(spec string, cmd func()(EntryID, error) {
6    return c.AddJob(spec, FuncJob(cmd))
7}



  • 自实现Job接口


除了通过AddFunc()将无参函数直接作为回调外,我们还可以通过AddJon()自定义对象。


如下,自定义对象CallJob,实现Run()方法。


 1package main
2
3import (
4    "fmt"
5    "time"
6
7    "github.com/robfig/cron/v3"
8)
9
10type CallJob struct {
11    name   string
12    number int
13}
14
15func (c CallJob) Run() {
16    fmt.Printf("call %s : %d\n", c.name, c.number)
17}
18func main() {
19    c := cron.New()
20    c.AddJob("@every 1s", CallJob{
21        name:   "Bob",
22        number: 13888888888,
23    })
24    c.Start()
25
26    time.Sleep(3 * time.Second)
27}


输出


1 $ go run main.go 
2call Bob : 13888888888
3call Bob : 13888888888
4call Bob : 13888888888


总结


cron库为go开发者提供了强大的定时任务管理功能,它的时间表达式格式和linux下的crontab命令是对齐的。


cron的代码并不算多,其核心定时管理功能依赖了go标准库timesort,非常值得学习和参考。另外有一个基于该库抽离出来的最小化定时任务库gron,更易理解和使用,文末会附上该库地址。


cron是小菜刀在实际项目中引入过的三方库,感觉挺不错,就总结出来分享给大家。如果你喜欢看更多关于三方库的文章,请点赞支持。



仓库地址

1. https://github.com/robfig/cron

2. https://github.com/roylee0704/gron





推荐阅读



学习交流 Go 语言,扫码回复「进群」即可


站长 polarisxu

自己的原创文章

不限于 Go 技术

职场和创业经验


Go语言中文网

每天为你

分享 Go 知识

Go爱好者值得关注


浏览 69
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报