robfig/cron定时任务库快速入门

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 表达式基于 "特定时间点" 的调度方式有本质区别。

  1. @every 的核心特点:

基于间隔,而非时点:标准的 Cron 表达式(如 0 */5 * * * *)是在每个小时的第 0、5、10... 分钟执行。而 @every 5m 是在任务启动后,每隔 5 分钟执行一次,无论当前是几点几分。

不受系统时间变化影响:如果系统时间因为同步或调整而向后跳跃,基于时点的 Cron 任务可能会被跳过或重复执行。而 @every 是基于内部计时器的,它会严格按照设定的间隔来触发,不受系统时间跳跃的影响(只要时间不向前跳跃)。 语法简洁直观:对于简单的周期性任务,@every 比写 Cron 表达式更直观。例如,@every 1h30m 一眼就能看出是每隔 1 小时 30 分钟执行一次。

  1. @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 号结算账单。

@everyrobfig/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 个任务。

相关推荐
喵个咪17 分钟前
开箱即用的 GoWind Admin|风行,企业级前后端一体中后台框架:基于 GORM 从零实现新服务
后端·go·orm
a努力。1 小时前
【基础数据篇】数据等价裁判:Comparer模式
java·后端
开心猴爷1 小时前
苹果App Store应用程序上架方式全面指南
后端
小飞Coding2 小时前
三种方式打 Java 可执行 JAR 包,你用对了吗?
后端
bcbnb2 小时前
没有 Mac,如何在 Windows 上架 iOS 应用?一套可落地的工程方案
后端
用户8356290780512 小时前
从一维到二维:用Spire.XLS轻松将Python列表导出到Excel
后端·python
哈哈哈笑什么2 小时前
SpringBoot 企业级接口加密【通用、可配置、解耦的组件】「开闭原则+模板方法+拦截器/中间件模式」
java·后端·安全
期待のcode2 小时前
springboot依赖管理机制
java·spring boot·后端
用户095367515832 小时前
Go -- 模板方法模式 (Template Method)
go