三十的博客

Go Cron 定时任务开发指南

发布时间
最后更新
阅读量 加载中...
Cron 官方文档

安装

要下载特定版本的发布包,请运行:

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

在程序中导入:

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

由于使用了 Go Modules,需要 Go 1.11 或更高版本。

使用方式

调用者可以注册函数在指定时间表上执行。Cron 将在自己的 goroutine 中运行这些函数。

go
c := cron.New()
c.AddFunc("30 * * * *", func() { fmt.Println("每小时半点执行") })
c.AddFunc("30 3-6,20-23 * * *", func() { fmt.Println("在凌晨3-6点和晚上8-11点范围内执行") })
c.AddFunc("CRON_TZ=Asia/Tokyo 30 04 * * *", func() { fmt.Println("每天东京时间04:30执行") })
c.AddFunc("@hourly", func() { fmt.Println("每小时执行一次,从现在开始一小时后") })
c.AddFunc("@every 1h30m", func() { fmt.Println("每1小时30分钟执行一次,从现在开始1小时30分钟后") })
c.Start()
...
// 函数在它们自己的goroutine中异步执行
...
// 也可以向运行中的Cron添加函数
c.AddFunc("@daily", func() { fmt.Println("每天执行") })
...
// 检查cron任务的下次和上次运行时间
inspect(c.Entries())
...
c.Stop() // 停止调度器(不会停止已经运行的作业)

CRON 表达式格式

Cron 表达式用于定义定时任务的执行计划。支持标准分钟级和秒级精度两种格式

标准格式(5 字段,分钟级)

┌───────────── 分钟 (0 - 59)
│ ┌───────────── 小时 (0 - 23)
│ │ ┌───────────── 日 (1 - 31)
│ │ │ ┌───────────── 月 (1 - 12)
│ │ │ │ ┌───────────── 星期 (0 - 6) 周日=0
│ │ │ │ │

\* \* \* \* \*

秒级格式(6 字段,启用 WithSeconds()时使用)

┌───────────── 秒 (0 - 59)
│ ┌───────────── 分钟 (0 - 59)
│ │ ┌───────────── 小时 (0 - 23)
│ │ │ ┌───────────── 日 (1 - 31)
│ │ │ │ ┌───────────── 月 (1 - 12)
│ │ │ │ │ ┌───────────── 星期 (0 - 6)
│ │ │ │ │ │

\* \* \* \* \* \*

特殊符号说明

符号 含义 示例 说明
* 任意值 * * * * * 每分钟执行
, 值列表 0,30 * * * * 每小时的 0 分和 30 分
- 范围 0 9-18 * * * 9 点到 18 点每小时执行
/ 步长 _/5 _ * * * 每 5 分钟执行
? 不指定 0 0 ? * 1 每周一 0 点(忽略日字段)

常用配置示例

秒级任务示例

go
// 每10秒执行
"*/10 * * * * *"

// 每分钟的第30秒执行
"30 * * * * *"

// 每小时的30分0秒执行
"0 30 * * * *"

// 每天8点0分0秒执行
"0 0 8 * * *"

分钟级任务示例

go
// 每分钟执行
"* * * * *"

// 每小时的第30分钟执行
"30 * * * *"

// 每天3点30分执行
"30 3 * * *"

// 每周一0点执行
"0 0 * * 1"

// 每月1日2点执行
"0 2 1 * *"

复杂表达式示例

go
// 工作日的9点到18点,每30分钟执行
"0,30 9-18 * * 1-5"

// 每月的1号和15号,上午10点和下午4点执行
"0 10,16 1,15 * *"

// 周末的每小时执行
"0 * * * 0,6"

替代格式

替代的 Cron 表达式格式支持其他字段,如秒。您可以通过创建自定义解析器来实现:

go
cron.New(
    cron.WithParser(
        cron.NewParser(
            cron.SecondOptional | cron.Minute | cron.Hour |
            cron.Dom | cron.Month | cron.Dow | cron.Descriptor)))

由于添加秒是最常见的标准 cron 规范修改,cron 提供了一个内置函数来实现:

cron.New(cron.WithSeconds())

预定义调度(推荐使用)

您可以使用几个预定义的调度来代替 cron 表达式。

条目 描述 等同于
@yearly (or @annually) 每年运行一次,1 月 1 日午夜 0 0 1 1 *
@monthly 每月运行一次,每月第一天午夜 0 0 1 * *
@weekly 每周运行一次,周六/周日午夜 0 0 * * 0
@daily (or @midnight) 每天运行一次,午夜 0 0 * * *
@hourly 每小时运行一次,整点开始 0 * * * *
go
// 使用预定义调度(更易读)
"@every 1h30m"      // 每1小时30分钟
"@every 10s"        // 每10秒
"@hourly"          // 每小时(等效于 "0 * * * *")
"@daily"           // 每天午夜(等效于 "0 0 * * *")
"@weekly"          // 每周日午夜(等效于 "0 0 * * 0")
"@monthly"         // 每月1日午夜(等效于 "0 0 1 * *")

间隔

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

@every <duration>

其中"duration"是 time.ParseDuration 接受的字符串。

例如:"@every 1h30m10s"表示一个调度,在 1 小时 30 分钟 10 秒后激活,然后每隔该间隔执行一次。

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

时区

默认情况下,所有解释和调度都在机器的本地时区 ( time.Local ) 中完成。

您可以在构造时指定不同的时区:

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

单个 cron 调度也可以通过在其规范开头提供额外的空格分隔字段来覆盖它们被解释的时区,格式为"CRON_TZ=Asia/Tokyo"。

例如:

go
// 在time.Local时区早上6点运行
cron.New().AddFunc("0 6 * * ?", ...)

// 在America/New_York时区早上6点运行
nyc, _ := time.LoadLocation("America/New_York")
c := cron.New(cron.WithLocation(nyc))
c.AddFunc("0 6 * * ?", ...)

// 在Asia/Tokyo时区早上6点运行
cron.New().AddFunc("CRON_TZ=Asia/Tokyo 0 6 * * ?", ...)

// 在Asia/Tokyo时区早上6点运行
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 的所有作业安装包装器:

go
cron.New(cron.WithChain(
    cron.SkipIfStillRunning(logger),
))

通过显式包装为单个作业安装包装器:

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

线程安全

由于 Cron 服务与调用代码并发运行,必须采取一定措施确保适当的同步。

所有 Cron 方法都设计为正确同步,只要调用者确保调用之间具有明确的 happens-before 顺序。

日志记录

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

为了深入了解 Cron 操作,可以激活详细日志记录,它将记录作业运行、调度决策以及添加或删除的作业。

使用一次性记录器激活它:

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

实现

Cron 条目存储在数组中,按其下次激活时间排序。Cron 休眠直到下一个作业应该运行。

唤醒时:

#Cron #Golang