Go Crontab

[TOC]

计划任务

from Godoc

包cron实现了cron规范解析器和作业运行器。

安装
要下载特定的标记版本,请运行:

go get github.com/robfig/cron/v3@v3.0.0
将其导入您的程序中:

import “github.com/robfig/cron/v3”
由于Go Modules的使用,它需要Go 1.11或更高版本。

用法
呼叫者可以在给定的时间表上注册要调用的Func。Cron将在他们自己的goroutines中运行它们。

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()
..
// Funcs are invoked in their own goroutine, asynchronously.
...
// Funcs may also be added to a running Cron
c.AddFunc("@daily", func() { fmt.Println("Every day") })
..
// Inspect the cron job entries' next and previous run times.
inspect(c.Entries())
..
c.Stop()  // Stop the scheduler (does not stop any jobs already running).

CRON表达格式

cron表达式表示一组时间,使用5个以空格分隔的字段。

| 字段名称 | 强制性? | 允许值 | 允许特殊字符 |
| ————– | ——– | ————- | ———— |
| 分钟 | 是的 | 0-59 | * /, – |
| 小时 | 是的 | 0-23 | * /, – |
| 一个月的某一天 | 是的 | 1-31 | * /, – ? |
| 月 | 是的 | 1-12或JAN-DEC | * /, – |
| 星期几 | 是的 | 0-6或SUN-SAT | * /, – ? |
月份和星期几字段值不区分大小写。“SUN”,“Sun”和“sun”同样被接受。

该格式的具体解释基于Cron维基百科页面:https://en.wikipedia.org/wiki/Cron

替代格式

备用Cron表达式格式支持其他字段,如秒。您可以通过创建自定义Parser来实现,如下所示。

cron.New(
cron.WithParser(
cron.NewParser(
cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)))
**由于添加Seconds是对标准cron规范的最常见修改,cron提供了一个内置函数来执行此操作,这相当于您之前看到的自定义解析器,除了它的秒字段是必需的: **

cron.New(cron.WithSeconds())
这模仿Quartz,最流行的替代Cron计划格式:http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/tutorials/tutorial-lesson-06.html

 # ┌───────────── min (0 - 59)
 # │ ┌────────────── hour (0 - 23)
 # │ │ ┌─────────────── day of month (1 - 31)
 # │ │ │ ┌──────────────── month (1 - 12)
 # │ │ │ │ ┌───────────────── day of week (0 - 6) (0 to 6 are Sunday to
 # │ │ │ │ │                  Saturday, or use names; 7 is also Sunday)
 # │ │ │ │ │
 # │ │ │ │ │
 # * * * * *  command to execute

cron特定字符说明

特殊字符
星号(*)

星号表示cron表达式将匹配该字段的所有值; 例如,在第5个字段(月)中使用星号表示每个月。

斜线(/)

斜杠用于描述范围的增量。例如,第1场(分钟)中的3-59 / 15表示小时的第3分钟,之后每15分钟。形式“* \ / …”等同于“first-last / …”形式,即在该字段的最大可能范围内的增量。形式“N / …”被接受为“N-MAX / …”,即从N开始,使用增量直到该特定范围的结束。它没有环绕。

逗号(,)

逗号用于分隔列表的项目。例如,在第5个字段(星期几)中使用“MON,WED,FRI”将表示星期一,星期三和星期五。

连字符( – )

连字符用于定义范围。例如,9-17表示每天上午9点到下午5点之间的每小时。

问号(?)

可以使用问号而不是’*’来留下日期或星期几的空白。

预定义的时间表

您可以使用几个预定义的计划之一来代替cron表达式。

进入 说明 相当于
@yearly(或@annually) 每年一次,午夜,1月1日 0 0 1 1 *
@monthly 每月运行一次,午夜,月初 0 0 1 * *
@weekly 每周一次,午睡/周日午夜 0 0 * * 0
@daily(或@midnight) 每天运行一次,午夜 0 0 * * *
@hourly 每小时运行一次,小时开始 0 * * * *

间隔

您还可以安排作业以固定的时间间隔执行,从添加或运行cron开始。这可以通过格式化cron规范来支持,如下所示:

@every
其中“duration”是time.ParseDuration(http://golang.org/pkg/time/#ParseDuration)接受的字符串。

例如,“@ every 1h30m10s”表示在1小时30分10秒之后激活的计划,然后是之后的每个间隔。

注意: 间隔不会考虑作业运行时。例如,如果作业需要3分钟才能运行,并且计划每5分钟运行一次,则每次运行之间只有2分钟的空闲时间。

cron举例说明

每隔1分钟执行一次:  */1 * * * ?
每天23点执行一次:  0 23 * * ?
每天凌晨1点执行一次:  0 1 * * ?
每月1号凌晨1点执行一次:  0 1 1 * ?
在26分、29分、33分执行一次:  26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:  0 0,13,18,21 * * ?

时区

默认情况下,所有解释和计划都在计算机的本地时区(time.Local)中完成。您可以在构造中指定不同的时区:

cron.New(
    cron.WithLocation(time.UTC))

单个cron调度还可以通过在cron规范的开头提供CRON_TZ=Asia/Tokyo.覆盖它们将被解释的时区。

例如:

# Runs at 6am in time.Local
cron.New().AddFunc("0 6 * * ?", ...)

# Runs at 6am in America/New_York
nyc, _ := time.LoadLocation("America/New_York")
c := cron.New(cron.WithLocation(nyc))
c.AddFunc("0 6 * * ?", ...)

# Runs at 6am in Asia/Tokyo
cron.New().AddFunc("CRON_TZ=Asia/Tokyo 0 6 * * ?", ...)

# Runs at 6am in Asia/Tokyo
c := cron.New(cron.WithLocation(nyc))
c.SetLocation("America/New_York")
c.AddFunc("CRON_TZ=Asia/Tokyo 0 6 * * ?", ...)

传统兼容性还支持前缀“TZ =(TIME ZONE)”。

请注意,在夏令时跨越式过渡期间安排的作业将无法运行!

作业包装机/链条

Cron运行器可以配置一系列作业包装器,以便为所有提交的作业添加横切功能。例如,它们可用于实现以下效果:

  • 从作业中恢复任何恐慌(默认激活)
  • 如果先前的运行尚未完成,则延迟作业的执行
  • 如果之前的运行尚未完成,则跳过作业的执行
  • 记录每个作业的调用
    使用cron.WithChain选项为添加到cron的所有作业安装包装器:
cron.New(cron.WithChain(
    cron.SkipIfStillRunning(logger),
))

通过显式包装它们来为各个作业安装包装器:

job = cron.NewChain(
    cron.SkipIfStillRunning(logger),
).Then(job)

线程安全
由于Cron服务与调用代码同时运行,因此必须采取一些措施以确保正确同步。

只要调用者确保调用在它们之间进行排序之前有明确的发生,所有cron方法都被设计为正确同步。

记录
Cron定义了一个Logger接口,它是github.com/go-logr/logr中定义的接口的一个子集。它有两个日志记录级别(信息和错误),参数是键/值对。这使得cron日志记录可以插入结构化日志记录系统。提供了一个适配器[Verbose] PrintfLogger来包装标准库* log.Logger。

为了进一步了解Cron操作,可以激活详细日志记录,这将记录作业运行,调度决策以及添加或删除的作业。使用一次性记录器激活它,如下所示:

cron.New(
    cron.WithLogger(
        cron.VerbosePrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags))))

履行
Cron条目存储在一个数组中,按其下一个激活时间排序。Cron睡着,直到下一份工作开始运行。

醒来后:

  • 它运行在该秒钟上处于活动状态的每个条目
  • 它计算运行的作业的下一个运行时间
  • 它按下一个激活时间重新排序条目数组。
  • 它会一直睡到最快的工作。