
robfig/cron库简介
robfig/cron 是 Go 生态里最常用、最稳定的定时任务库之一。它支持标准 Cron 表达式、秒级调度、任务并发控制、任务日志、带上下文执行等功能,非常适合生产环境。
Quick Start
安装命令
go
go get github.com/robfig/cron/v3
最简单的demo
go
package main
import (
"fmt"
"github.com/robfig/cron/v3"
)
func main() {
c := cron.New()
// 每隔 1 分钟执行
c.AddFunc("*/1 * * * *", func() {
fmt.Println("task run")
})
c.Start()
select {}
}
上面的程序表示,每隔一分钟就那个执行这个函数,打印 task run 字符串。这仅仅是最简单的用法,还可以添加 job 实例以实现更复杂的逻辑。
进阶使用
熟悉cron表达式
robfig/cron(v3 版本)完整支持 标准 Cron 表达式,包括可选的秒字段。它的表达式格式十分灵活,同时支持特殊字符(* 、,、-、/ 等)以及扩展语法(如 @every)。
robfig/cron 提供两种模式:
- 标准 5 字段(默认):
txt
分 时 日 月 星期
30 2 * * *
表示每天 02:30 执行
- 启用
WithSeconds()后变成 6 字段:
txt
秒 分 时 日 月 星期
0 */5 * * * *
表示每 5 分钟执行一次(带秒,所以第 1 位是秒)
在 cron 表达式中,有一些特殊字符,如:*、/、-、,、? 符合,这些符号有着特殊含义,用于灵活地指定时间点。
*(星号):
表示"所有可能的值"。例如,在 "分钟" 字段中使用 *,表示 "每分钟"。
/(斜杠):
表示 "每隔" 一定时间。例如,在 "分钟" 字段中使用 */15,表示 "每隔 15 分钟"(即 0, 15, 30, 45 分)。在 "小时" 字段中使用 2/4,表示 "从 2 点开始,每隔 4 小时"(即 2, 6, 10, 14, 18, 22 点)。
-(连字符):
表示一个 "范围"。例如,在 "小时" 字段中使用 9-17,表示 "从 9 点到 17 点之间的每一小时"(即 9, 10, ..., 17)。
,(逗号)
表示 "列举多个值"。例如,在 "星期" 字段中使用 1,3,5,表示 "星期一、星期三和星期五"。
?(问号)
这个字符是 robfig/cron 库对标准 Cron 的一个扩展(标准 Cron 中没有 ?),但它遵循了 Quartz Scheduler 等流行库的约定。它用于在 "日期" 和 "星期" 字段中表示 "不指定值" 或 "任意值",用于避免这两个字段之间的冲突。因为在 Cron 表达式中,"日期" 和 "星期" 是互斥的(指定了日期,星期就无关紧要了,反之亦然)。
最佳实践:当你想指定一个具体的日期(例如每月 10 号),就在 "星期" 字段使用 ?。当你想指定一个具体的星期几(例如每周一),就在 "日期" 字段使用 ?。
0 0 10 * ? 表示 "每月 10 号的 0 点 0 分"(星期几无关)。
0 0 ? * 1 表示 "每周一的 0 点 0 分"(几号无关)。
在 6 字段格式的基础上,再增加一个 年 字段:
txt
┌───────────── 秒 (0 - 59)
│ ┌───────────── 分钟 (0 - 59)
│ │ ┌───────────── 小时 (0 - 23)
│ │ │ ┌───────────── 日期 (1 - 31)
│ │ │ │ ┌───────────── 月份 (1 - 12)
│ │ │ │ │ ┌───────────── 星期 (0 - 6) (星期日为 0 或 7)
│ │ │ │ │ │ ┌───────────── 年 (可选, 1970-2099)
│ │ │ │ │ │ │
* * * * * * *
0 0 0 1 1 ? 2025 表示 "在 2025 年 1 月 1 日 0 点 0 分 0 秒" 执行。
预定义的时间表
为了让常见的调度任务更容易编写,robfig/cron 库还支持一些预定义的字符串,它们是标准 Cron 表达式的别名:
| 预定义字符串 | 等价的 Cron 表达式 | 含义 |
|---|---|---|
@yearly 或 @annually |
0 0 0 1 1 * |
每年执行一次(1 月 1 日 00:00) |
@monthly |
0 0 0 1 * * |
每月执行一次(每月 1 日 00:00) |
@weekly |
0 0 0 * * 0 |
每周执行一次(每周日 00:00) |
@daily 或 @midnight |
0 0 0 * * * |
每天执行一次(00:00) |
@hourly |
0 0 * * * * |
每小时执行一次(整点) |
robfig/cron 还提供了一种扩展预定义关键字,@every 它用于表示 "每隔固定的时间间隔" 执行一次任务。这与标准 Cron 表达式基于 "特定时间点" 的调度方式有本质区别。
- @every 的核心特点:
基于间隔,而非时点:标准的 Cron 表达式(如 0 */5 * * * *)是在每个小时的第 0、5、10... 分钟执行。而 @every 5m 是在任务启动后,每隔 5 分钟执行一次,无论当前是几点几分。
不受系统时间变化影响:如果系统时间因为同步或调整而向后跳跃,基于时点的 Cron 任务可能会被跳过或重复执行。而 @every 是基于内部计时器的,它会严格按照设定的间隔来触发,不受系统时间跳跃的影响(只要时间不向前跳跃)。 语法简洁直观:对于简单的周期性任务,@every 比写 Cron 表达式更直观。例如,@every 1h30m 一眼就能看出是每隔 1 小时 30 分钟执行一次。
- @every 的语法
@every 后面紧跟一个时间间隔字符串,格式为:
txt
@every <duration>
其中 <duration> 是一个符合 Go 语言 time.Duration 格式的字符串,支持的单位包括:
- ns:纳秒
- us:微秒
- ms:毫秒
- s:秒
- m:分钟
- h:小时
- d:天(注意:d 是
robfig/cron库支持的扩展单位,标准的time.Duration并不直接支持 d,但库内部会将其转换为小时)
go
// 假设现在是 10:03:25
c.AddFunc("@every 5m", func() {
fmt.Println("任务执行了")
})
- 第一次执行时间: 10:03:25(任务添加后立即开始计时,5 分钟后触发)
- 第二次执行时间: 10:08:25
- 第三次执行时间: 10:13:25
- ... 以此类推,严格间隔 5 分钟。
使用场景:
-
使用
@every的场景:- 你需要一个简单、固定的时间间隔来执行任务,不关心具体在哪个时间点。
- 任务的执行间隔较短(如几秒、几分钟)。
- 你希望任务从启动后就开始按照间隔执行,而不是等待下一个整点。
- 例如:定期检查某个服务的健康状态、定期从数据库中清理过期数据、实时数据处理等。
-
使用标准 Cron 表达式的场景:
- 你需要任务在特定的时间点执行,而不是固定的间隔。
- 任务的执行周期与日历相关(如每天早上 8 点、每周一晚上、每月 15 号)。
- 例如:每天凌晨 3 点生成日报、每周五下午 5 点备份数据库、每月 1 号结算账单。
@every 是 robfig/cron 库提供的一个非常方便的工具,它提供了一种基于固定间隔 的调度方式,与标准 Cron 表达式基于特定时点的调度方式形成了很好的互补。
选择使用 @every 还是标准 Cron 表达式,主要取决于你的任务是需要 "每隔多久执行一次" 还是 "在某个特定时间执行"。
自定义Job实例
好的,在 robfig/cron/v3 中自定义 Job 实例通常有两种方式:结构体嵌入和函数包装。以下是具体实现和说明:
结构体嵌入
通过定义一个结构体,嵌入 cron.Job 接口,然后实现自定义的 Run() 方法。
go
package main
import (
"fmt"
"time"
"github.com/robfig/cron/v3"
)
// 定义一个自定义任务结构体
type MyJob struct {
Name string
// 可以添加其他字段,比如配置、依赖等
}
// 实现 cron.Job 接口的 Run 方法
func (m *MyJob) Run() {
fmt.Printf("任务 [%s] 执行时间: %s\n", m.Name, time.Now().Format("2006-01-02 15:04:05"))
// 这里可以写具体的任务逻辑
}
func main() {
// 创建一个 cron 调度器
c := cron.New()
// 实例化自定义任务
job1 := &MyJob{Name: "任务A"}
job2 := &MyJob{Name: "任务B"}
// 添加任务到调度器
_, err := c.AddJob("*/5 * * * *", job1) // 每5分钟执行一次
if err != nil {
fmt.Println("添加任务A失败:", err)
return
}
_, err = c.AddJob("@hourly", job2) // 每小时执行一次
if err != nil {
fmt.Println("添加任务B失败:", err)
return
}
// 启动调度器
c.Start()
// 保持程序运行
select {}
}
优点:
- 结构清晰,易于扩展和维护
- 可以在结构体中存储任务相关的配置和状态
- 支持依赖注入
带有上下文的任务
如果你需要在任务中处理上下文(比如取消信号),可以实现 cron.Job 接口并在 Run() 方法中使用上下文。
go
package main
import (
"context"
"fmt"
"time"
"github.com/robfig/cron/v3"
)
type ContextJob struct {
ctx context.Context
// 其他字段
}
func (c *ContextJob) Run() {
select {
case <-c.ctx.Done():
fmt.Println("任务被取消")
return
default:
fmt.Printf("上下文任务执行时间: %s\n", time.Now().Format("2006-01-02 15:04:05"))
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
c := cron.New()
job := &ContextJob{ctx: ctx}
_, err := c.AddJob("*/30 * * * *", job)
if err != nil {
fmt.Println("添加任务失败:", err)
return
}
c.Start()
// 等待用户输入后停止
fmt.Println("按 Enter 键停止程序...")
fmt.Scanln()
cancel() // 取消上下文
c.Stop() // 停止调度器
}
任务的优先级和并发控制
robfig/cron/v3 默认是并发执行任务的,如果需要控制并发,可以使用 cron.WithMaxConcurrentJobs() 选项。
go
c := cron.New(cron.WithMaxConcurrentJobs(3, cron.DefaultLogger))
这样可以限制最多同时运行 3 个任务。