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

相关推荐
稚辉君.MCA_P8_Java1 小时前
通义千问 SpringBoot 性能优化全景设计(面向 Java 开发者)
大数据·hadoop·spring boot·分布式·架构
周末程序猿1 小时前
开源项目|不一样的思维导图
人工智能·后端
小夏coding1 小时前
分布式锁工具类
后端
n***84071 小时前
Spring Boot(快速上手)
java·spring boot·后端
Wgrape2 小时前
一文了解常见AI搜索方案的代码实现
人工智能·后端
小兔崽子去哪了2 小时前
Docker部署ZLMediaKit流媒体服务器并自定义配置指南
java·后端·容器
程序猿小蒜2 小时前
基于springboot的人口老龄化社区服务与管理平台
java·前端·spring boot·后端·spring
aiopencode2 小时前
iOS 开发者工具推荐,构建从调试到性能优化的多维度生产力工具链(2025 深度工程向)
后端
iOS开发上架哦2 小时前
iOS APP 抓包全流程解析,HTTPS 调试、网络协议分析与多工具组合方案
后端