peakchao

搜索

peakchao

peakchao

前端开发工程师 | Go 爱好者

联系方式

Go Cron 定时任务完全指南:从入门到生产实践

peakchao 2025-12-13 05:47 16 次浏览 0 条评论

定时任务概述

什么是定时任务

定时任务(Scheduled Task)是指在预定时间或按照固定时间间隔自动执行的程序任务。

┌─────────────────────────────────────────────────────────────────┐
│                     定时任务应用场景                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   📊 数据处理                    📧 通知服务                     │
│   ├── 数据备份                   ├── 邮件发送                   │
│   ├── 日志清理                   ├── 消息推送                   │
│   ├── 报表生成                   └── 定时提醒                   │
│   └── 数据同步                                                  │
│                                                                 │
│   🔄 系统维护                    💰 业务处理                     │
│   ├── 缓存刷新                   ├── 订单超时处理               │
│   ├── 会话清理                   ├── 账单生成                   │
│   ├── 健康检查                   ├── 库存同步                   │
│   └── 证书更新                   └── 积分过期处理               │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Go 中的定时任务方案

方案特点适用场景
time.Ticker标准库,固定间隔执行简单的心跳检测、定期轮询
time.AfterFunc标准库,延迟执行一次超时处理、延迟任务
robfig/cron功能丰富,支持 cron 表达式复杂的定时调度需求
go-co-op/gocron更简洁的 API中等复杂度的定时任务

为什么选择 robfig/cron

┌─────────────────────────────────────────────────────────────────┐
│                   robfig/cron 优势                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ✅ 标准 Cron 表达式支持      ✅ 秒级精度                       │
│   ✅ 时区支持                  ✅ 任务链与中间件                  │
│   ✅ 并发安全                  ✅ 动态添加/删除任务               │
│   ✅ 活跃的社区维护            ✅ 生产环境验证                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

robfig/cron 库介绍

安装

# 安装 v3 版本(推荐)
go get github.com/robfig/cron/v3

版本差异

特性v2v3
默认格式6 字段(含秒)5 字段(标准 cron)
秒级支持默认支持需要 WithSeconds() 选项
时区全局设置可在选项中配置
中间件不支持支持 Job Wrapper(中间件)
返回值无任务 ID返回 EntryID

快速开始

package main

import (
    "fmt"
    "time"

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

func main() {
    // 创建 cron 调度器
    c := cron.New()

    // 添加定时任务:每分钟执行一次
    c.AddFunc("* * * * *", func() {
        fmt.Println("每分钟执行:", time.Now().Format("15:04:05"))
    })

    // 启动调度器(非阻塞)
    c.Start()

    // 保持主程序运行
    select {}
}

Cron 表达式详解

标准 Cron 表达式(5 字段)

┌─────────────────────────────────────────────────────────────────┐
│                   Cron 表达式格式(5 字段)                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ┌───────────── 分钟 (0 - 59)                                  │
│   │ ┌───────────── 小时 (0 - 23)                                │
│   │ │ ┌───────────── 日 (1 - 31)                                │
│   │ │ │ ┌───────────── 月 (1 - 12)                              │
│   │ │ │ │ ┌───────────── 星期 (0 - 6, 0 = 周日)                 │
│   │ │ │ │ │                                                     │
│   │ │ │ │ │                                                     │
│   * * * * *                                                     │
│                                                                 │
│   示例:                                                        │
│   "30 8 * * *"     → 每天 08:30 执行                            │
│   "0 */2 * * *"    → 每 2 小时执行                              │
│   "0 9 * * 1-5"    → 周一到周五 09:00 执行                      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

扩展 Cron 表达式(6 字段,含秒)

┌─────────────────────────────────────────────────────────────────┐
│                   Cron 表达式格式(6 字段)                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ┌───────────── 秒 (0 - 59)                                    │
│   │ ┌───────────── 分钟 (0 - 59)                                │
│   │ │ ┌───────────── 小时 (0 - 23)                              │
│   │ │ │ ┌───────────── 日 (1 - 31)                              │
│   │ │ │ │ ┌───────────── 月 (1 - 12)                            │
│   │ │ │ │ │ ┌───────────── 星期 (0 - 6)                         │
│   │ │ │ │ │ │                                                   │
│   * * * * * *                                                   │
│                                                                 │
│   示例:                                                        │
│   "0 30 8 * * *"   → 每天 08:30:00 执行                         │
│   "*/5 * * * * *"  → 每 5 秒执行                                │
│   "0 0 0 1 * *"    → 每月 1 号 00:00:00 执行                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

特殊字符说明

字符说明示例含义
*匹配所有值* * * * *每分钟
,列举多个值0,30 * * * *每小时的 0 分和 30 分
-范围0 9-17 * * *9 点到 17 点的每小时
/步长*/15 * * * *每 15 分钟
?不指定(日/星期)0 0 * * ?任意星期
L最后0 0 L * *每月最后一天
W最近工作日0 0 15W * *每月 15 号最近的工作日
#第 N 个星期几0 0 * * 5#3每月第 3 个周五

常用表达式示例

// 常用 Cron 表达式集合
var CronExpressions = map[string]string{
    // ===== 基础间隔 =====
    "每分钟":       "* * * * *",
    "每5分钟":      "*/5 * * * *",
    "每15分钟":     "*/15 * * * *",
    "每30分钟":     "*/30 * * * *",
    "每小时":       "0 * * * *",
    "每2小时":      "0 */2 * * *",
    
    // ===== 每天固定时间 =====
    "每天凌晨":     "0 0 * * *",
    "每天早8点":    "0 8 * * *",
    "每天中午12点": "0 12 * * *",
    "每天晚上10点": "0 22 * * *",
    
    // ===== 工作日 =====
    "工作日早9点":  "0 9 * * 1-5",
    "周末早10点":   "0 10 * * 0,6",
    "周一早9点":    "0 9 * * 1",
    
    // ===== 每周/每月 =====
    "每周一凌晨":   "0 0 * * 1",
    "每月1号凌晨":  "0 0 1 * *",
    "每月15号10点": "0 10 15 * *",
    "每季度首日":   "0 0 1 1,4,7,10 *",
    "每年1月1日":   "0 0 1 1 *",
}

预定义表达式

robfig/cron 支持一些预定义的表达式:

预定义等效表达式说明
@yearly / @annually0 0 1 1 *每年 1 月 1 日 00:00
@monthly0 0 1 * *每月 1 日 00:00
@weekly0 0 * * 0每周日 00:00
@daily / @midnight0 0 * * *每天 00:00
@hourly0 * * * *每小时整点
@every <duration>-固定间隔,如 @every 1h30m
// 使用预定义表达式
c.AddFunc("@daily", dailyTask)
c.AddFunc("@every 30s", heartbeat)
c.AddFunc("@hourly", cleanupCache)

基础使用方法

方式一:使用 AddFunc 添加函数任务

package main

import (
    "fmt"
    "log"
    "time"

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

func main() {
    c := cron.New()

    // 添加任务,返回任务 ID
    entryID, err := c.AddFunc("*/5 * * * *", func() {
        log.Println("每5分钟执行的任务")
    })
    if err != nil {
        log.Fatal("添加任务失败:", err)
    }
    fmt.Printf("任务已添加, ID: %d\n", entryID)

    // 添加多个任务
    c.AddFunc("0 * * * *", func() {
        log.Println("每小时整点执行")
    })

    c.AddFunc("0 9 * * 1-5", func() {
        log.Println("工作日早上9点执行")
    })

    c.Start()
    
    // 优雅关闭
    defer c.Stop()
    
    select {}
}

方式二:使用 AddJob 添加 Job 接口

package main

import (
    "fmt"
    "log"
    "sync/atomic"
    "time"

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

// 定义任务结构体,实现 cron.Job 接口
type DataSyncJob struct {
    Name      string
    Source    string
    Target    string
    RunCount  int64
}

// 实现 Run 方法
func (j *DataSyncJob) Run() {
    count := atomic.AddInt64(&j.RunCount, 1)
    log.Printf("[%s] 第 %d 次执行: 从 %s 同步到 %s",
        j.Name, count, j.Source, j.Target)
    
    // 模拟数据同步
    time.Sleep(2 * time.Second)
    log.Printf("[%s] 同步完成", j.Name)
}

func main() {
    c := cron.New()

    // 创建任务实例
    syncJob := &DataSyncJob{
        Name:   "用户数据同步",
        Source: "MySQL",
        Target: "Elasticsearch",
    }

    // 添加 Job
    entryID, err := c.AddJob("*/1 * * * *", syncJob)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("任务 ID: %d\n", entryID)

    c.Start()
    
    // 程序运行一段时间后检查执行次数
    time.Sleep(5 * time.Minute)
    fmt.Printf("总执行次数: %d\n", syncJob.RunCount)
}

方式三:使用 FuncJob 包装函数

package main

import (
    "log"

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

func main() {
    c := cron.New()

    // 将普通函数包装为 Job
    myFunc := func() {
        log.Println("执行任务")
    }

    // 使用 cron.FuncJob 包装
    c.AddJob("* * * * *", cron.FuncJob(myFunc))

    c.Start()
    select {}
}

启用秒级精度

package main

import (
    "log"
    "time"

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

func main() {
    // 使用 WithSeconds 选项启用 6 字段格式
    c := cron.New(cron.WithSeconds())

    // 现在可以使用秒级表达式
    c.AddFunc("*/5 * * * * *", func() {
        log.Println("每5秒执行:", time.Now().Format("15:04:05"))
    })

    c.AddFunc("0 */1 * * * *", func() {
        log.Println("每分钟整点执行:", time.Now().Format("15:04:05"))
    })

    c.AddFunc("30 * * * * *", func() {
        log.Println("每分钟的第30秒执行:", time.Now().Format("15:04:05"))
    })

    c.Start()
    select {}
}

高级配置选项

cron.New() 配置选项详解

// 所有可用的配置选项
c := cron.New(
    cron.WithSeconds(),           // 启用秒级解析(6字段)
    cron.WithLocation(loc),       // 设置时区
    cron.WithParser(parser),      // 自定义解析器
    cron.WithChain(wrappers...),  // 添加中间件
    cron.WithLogger(logger),      // 自定义日志
)

时区配置

package main

import (
    "log"
    "time"

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

func main() {
    // 加载上海时区
    loc, err := time.LoadLocation("Asia/Shanghai")
    if err != nil {
        log.Fatal("加载时区失败:", err)
    }

    // 创建使用上海时区的调度器
    c := cron.New(cron.WithLocation(loc))

    // 任务将按上海时间执行
    c.AddFunc("0 9 * * *", func() {
        log.Println("上海时间早上9点执行")
    })

    // 也可以使用 UTC
    utc, _ := time.LoadLocation("UTC")
    cUTC := cron.New(cron.WithLocation(utc))
    cUTC.AddFunc("0 1 * * *", func() {
        log.Println("UTC 时间凌晨1点执行")
    })

    c.Start()
    cUTC.Start()
    select {}
}

自定义解析器

package main

import (
    "log"

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

func main() {
    // 自定义解析器:支持秒级 + 可选的描述符(如 @every)
    parser := cron.NewParser(
        cron.Second |      //        cron.Minute |      //        cron.Hour |        //        cron.Dom |         //        cron.Month |       //        cron.Dow |         // 星期
        cron.Descriptor,   // 预定义描述符(@every, @daily 等)
    )

    c := cron.New(cron.WithParser(parser))

    // 现在可以混合使用秒级表达式和预定义描述符
    c.AddFunc("*/5 * * * * *", func() {
        log.Println("每5秒")
    })
    
    c.AddFunc("@every 30s", func() {
        log.Println("每30秒")
    })

    c.Start()
    select {}
}

解析器标志位说明:

标志位说明
cron.Second启用秒字段
cron.Minute启用分钟字段
cron.Hour启用小时字段
cron.Dom启用日期字段
cron.Month启用月份字段
cron.Dow启用星期字段
cron.DowOptional星期字段可选
cron.Descriptor启用预定义描述符

中间件(Job Wrapper)

中间件可以在任务执行前后添加额外逻辑,如日志、恢复 panic、延迟执行等。

内置中间件

package main

import (
    "log"

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

func main() {
    c := cron.New(
        cron.WithChain(
            // 1. 恢复 panic,防止单个任务崩溃影响整个调度器
            cron.Recover(cron.DefaultLogger),
            
            // 2. 如果上一次执行未完成,跳过本次执行
            cron.SkipIfStillRunning(cron.DefaultLogger),
            
            // 3. 延迟执行(等待上一次完成后再执行)
            // cron.DelayIfStillRunning(cron.DefaultLogger),
        ),
    )

    c.AddFunc("* * * * *", func() {
        log.Println("安全的任务执行")
    })

    c.Start()
    select {}
}

内置中间件说明:

中间件作用
Recover捕获任务中的 panic,记录日志并继续运行
SkipIfStillRunning如果任务上次执行还未完成,跳过本次执行
DelayIfStillRunning如果任务上次执行还未完成,等待完成后立即执行

自定义中间件

package main

import (
    "context"
    "log"
    "time"

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

// 执行耗时统计中间件
func TimingWrapper() cron.JobWrapper {
    return func(job cron.Job) cron.Job {
        return cron.FuncJob(func() {
            start := time.Now()
            job.Run()
            duration := time.Since(start)
            log.Printf("任务执行耗时: %v", duration)
        })
    }
}

// 超时控制中间件
func TimeoutWrapper(timeout time.Duration) cron.JobWrapper {
    return func(job cron.Job) cron.Job {
        return cron.FuncJob(func() {
            ctx, cancel := context.WithTimeout(context.Background(), timeout)
            defer cancel()

            done := make(chan struct{})
            go func() {
                job.Run()
                close(done)
            }()

            select {
            case <-done:
                log.Println("任务正常完成")
            case <-ctx.Done():
                log.Println("任务执行超时")
            }
        })
    }
}

// 重试中间件
func RetryWrapper(maxRetries int, delay time.Duration) cron.JobWrapper {
    return func(job cron.Job) cron.Job {
        return cron.FuncJob(func() {
            var lastErr error
            for i := 0; i <= maxRetries; i++ {
                func() {
                    defer func() {
                        if r := recover(); r != nil {
                            lastErr = fmt.Errorf("panic: %v", r)
                            log.Printf("任务失败 (尝试 %d/%d): %v", i+1, maxRetries+1, lastErr)
                        }
                    }()
                    job.Run()
                    lastErr = nil
                }()
                
                if lastErr == nil {
                    return
                }
                
                if i < maxRetries {
                    time.Sleep(delay)
                }
            }
            if lastErr != nil {
                log.Printf("任务最终失败: %v", lastErr)
            }
        })
    }
}

func main() {
    c := cron.New(
        cron.WithChain(
            cron.Recover(cron.DefaultLogger),
            TimingWrapper(),
            TimeoutWrapper(30 * time.Second),
        ),
    )

    c.AddFunc("* * * * *", func() {
        log.Println("执行任务")
        time.Sleep(2 * time.Second)
    })

    c.Start()
    select {}
}

自定义日志

package main

import (
    "log"
    "os"

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

// 实现 cron.Logger 接口
type CronLogger struct {
    logger *log.Logger
}

func (l *CronLogger) Info(msg string, keysAndValues ...interface{}) {
    l.logger.Printf("[INFO] %s %v", msg, keysAndValues)
}

func (l *CronLogger) Error(err error, msg string, keysAndValues ...interface{}) {
    l.logger.Printf("[ERROR] %s: %v %v", msg, err, keysAndValues)
}

func main() {
    // 创建自定义日志器
    customLogger := &CronLogger{
        logger: log.New(os.Stdout, "[CRON] ", log.LstdFlags|log.Lshortfile),
    }

    c := cron.New(
        cron.WithLogger(customLogger),
        cron.WithChain(cron.Recover(customLogger)),
    )

    c.AddFunc("* * * * *", func() {
        log.Println("任务执行")
    })

    c.Start()
    select {}
}

任务管理与控制

任务的生命周期

┌─────────────────────────────────────────────────────────────────┐
│                     任务生命周期                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ┌──────────┐     ┌──────────┐     ┌──────────┐               │
│   │  创建    │ ──► │  就绪    │ ──► │  运行    │               │
│   │ AddFunc  │     │ 等待触发 │     │ 执行任务 │               │
│   └──────────┘     └────┬─────┘     └────┬─────┘               │
│                         │                │                      │
│        ┌────────────────┼────────────────┘                      │
│        │                │                                       │
│        ▼                ▼                                       │
│   ┌──────────┐     ┌──────────┐                                 │
│   │  移除    │     │  暂停    │                                 │
│   │ Remove   │     │  Stop    │                                 │
│   └──────────┘     └──────────┘                                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

启动与停止

package main

import (
    "context"
    "log"
    "os"
    "os/signal"
    "syscall"

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

func main() {
    c := cron.New()
    
    c.AddFunc("* * * * *", func() {
        log.Println("任务执行中...")
    })

    // 启动(非阻塞)
    c.Start()
    log.Println("调度器已启动")

    // 等待退出信号
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    log.Println("正在关闭...")

    // 方式1:普通停止(立即返回,不等待运行中的任务)
    // c.Stop()

    // 方式2:优雅停止(等待所有运行中的任务完成)
    ctx := c.Stop()
    <-ctx.Done()

    log.Println("所有任务已完成,程序退出")
}

动态添加和删除任务

package main

import (
    "fmt"
    "log"
    "time"

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

func main() {
    c := cron.New(cron.WithSeconds())
    c.Start()

    // 动态添加任务
    entryID1, _ := c.AddFunc("*/5 * * * * *", func() {
        log.Println("任务1: 每5秒")
    })
    log.Printf("添加任务1, ID: %d", entryID1)

    time.Sleep(15 * time.Second)

    // 再添加一个任务
    entryID2, _ := c.AddFunc("*/3 * * * * *", func() {
        log.Println("任务2: 每3秒")
    })
    log.Printf("添加任务2, ID: %d", entryID2)

    time.Sleep(10 * time.Second)

    // 删除任务1
    c.Remove(entryID1)
    log.Printf("已删除任务1 (ID: %d)", entryID1)

    // 任务2 继续运行
    select {}
}

获取任务信息

package main

import (
    "fmt"
    "log"
    "time"

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

func main() {
    c := cron.New(cron.WithSeconds())

    id1, _ := c.AddFunc("*/10 * * * * *", func() {
        log.Println("任务1")
    })

    id2, _ := c.AddFunc("*/30 * * * * *", func() {
        log.Println("任务2")
    })

    c.Start()
    
    // 等待一段时间让任务执行
    time.Sleep(35 * time.Second)

    // 获取所有任务条目
    entries := c.Entries()
    for _, entry := range entries {
        fmt.Printf("任务 ID: %d\n", entry.ID)
        fmt.Printf("  下次执行: %v\n", entry.Next)
        fmt.Printf("  上次执行: %v\n", entry.Prev)
        fmt.Printf("  Schedule: %v\n", entry.Schedule)
        fmt.Println()
    }

    // 获取单个任务
    entry := c.Entry(id1)
    fmt.Printf("任务1下次执行时间: %v\n", entry.Next)
}

任务状态监控

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "sync"
    "time"

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

// 任务状态信息
type JobStatus struct {
    ID          cron.EntryID `json:"id"`
    Name        string       `json:"name"`
    Expression  string       `json:"expression"`
    NextRun     time.Time    `json:"next_run"`
    LastRun     time.Time    `json:"last_run"`
    RunCount    int64        `json:"run_count"`
    LastError   string       `json:"last_error,omitempty"`
    IsRunning   bool         `json:"is_running"`
}

// 任务管理器
type JobManager struct {
    cron    *cron.Cron
    jobs    map[cron.EntryID]*JobStatus
    mu      sync.RWMutex
}

func NewJobManager() *JobManager {
    return &JobManager{
        cron: cron.New(cron.WithSeconds()),
        jobs: make(map[cron.EntryID]*JobStatus),
    }
}

func (m *JobManager) AddJob(name, expr string, fn func() error) (cron.EntryID, error) {
    status := &JobStatus{
        Name:       name,
        Expression: expr,
    }

    id, err := m.cron.AddFunc(expr, func() {
        m.mu.Lock()
        status.IsRunning = true
        status.LastRun = time.Now()
        status.RunCount++
        m.mu.Unlock()

        defer func() {
            m.mu.Lock()
            status.IsRunning = false
            m.mu.Unlock()
        }()

        if err := fn(); err != nil {
            m.mu.Lock()
            status.LastError = err.Error()
            m.mu.Unlock()
        } else {
            m.mu.Lock()
            status.LastError = ""
            m.mu.Unlock()
        }
    })

    if err != nil {
        return 0, err
    }

    status.ID = id
    m.mu.Lock()
    m.jobs[id] = status
    m.mu.Unlock()

    return id, nil
}

func (m *JobManager) GetAllStatus() []JobStatus {
    m.mu.RLock()
    defer m.mu.RUnlock()

    entries := m.cron.Entries()
    entryMap := make(map[cron.EntryID]cron.Entry)
    for _, e := range entries {
        entryMap[e.ID] = e
    }

    result := make([]JobStatus, 0, len(m.jobs))
    for id, status := range m.jobs {
        if entry, ok := entryMap[id]; ok {
            status.NextRun = entry.Next
        }
        result = append(result, *status)
    }
    return result
}

func (m *JobManager) Start() {
    m.cron.Start()
}

func (m *JobManager) Stop() context.Context {
    return m.cron.Stop()
}

// HTTP 接口
func (m *JobManager) StatusHandler(w http.ResponseWriter, r *http.Request) {
    status := m.GetAllStatus()
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(status)
}

func main() {
    manager := NewJobManager()

    // 添加任务
    manager.AddJob("数据同步", "*/10 * * * * *", func() error {
        log.Println("执行数据同步...")
        time.Sleep(3 * time.Second)
        return nil
    })

    manager.AddJob("日志清理", "0 0 * * * *", func() error {
        log.Println("执行日志清理...")
        return nil
    })

    manager.Start()

    // 提供 HTTP 接口查看任务状态
    http.HandleFunc("/jobs/status", manager.StatusHandler)
    log.Println("任务状态接口: http://localhost:8080/jobs/status")
    http.ListenAndServe(":8080", nil)
}

错误处理与日志

全局错误处理

package main

import (
    "log"
    "runtime/debug"

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

// 全局错误处理中间件
func ErrorHandlerWrapper(onError func(jobName string, err interface{})) cron.JobWrapper {
    return func(job cron.Job) cron.Job {
        return cron.FuncJob(func() {
            defer func() {
                if r := recover(); r != nil {
                    stack := debug.Stack()
                    log.Printf("任务 panic: %v\n%s", r, stack)
                    if onError != nil {
                        onError("unknown", r)
                    }
                }
            }()
            job.Run()
        })
    }
}

// 错误通知函数
func notifyError(jobName string, err interface{}) {
    // 发送告警:邮件、短信、钉钉等
    log.Printf("⚠️ 告警: 任务 [%s] 发生错误: %v", jobName, err)
}

func main() {
    c := cron.New(
        cron.WithChain(
            ErrorHandlerWrapper(notifyError),
        ),
    )

    c.AddFunc("* * * * *", func() {
        // 模拟可能panic的任务
        var arr []int
        _ = arr[0] // panic: index out of range
    })

    c.Start()
    select {}
}

结构化日志

package main

import (
    "os"
    "time"

    "github.com/robfig/cron/v3"
    "github.com/rs/zerolog"
    "github.com/rs/zerolog/log"
)

// 使用 zerolog 的 Cron Logger
type ZerologCronLogger struct {
    logger zerolog.Logger
}

func (l *ZerologCronLogger) Info(msg string, keysAndValues ...interface{}) {
    event := l.logger.Info()
    for i := 0; i < len(keysAndValues); i += 2 {
        if i+1 < len(keysAndValues) {
            event = event.Interface(keysAndValues[i].(string), keysAndValues[i+1])
        }
    }
    event.Msg(msg)
}

func (l *ZerologCronLogger) Error(err error, msg string, keysAndValues ...interface{}) {
    event := l.logger.Error().Err(err)
    for i := 0; i < len(keysAndValues); i += 2 {
        if i+1 < len(keysAndValues) {
            event = event.Interface(keysAndValues[i].(string), keysAndValues[i+1])
        }
    }
    event.Msg(msg)
}

func main() {
    // 配置 zerolog
    zerolog.TimeFieldFormat = time.RFC3339
    logger := zerolog.New(os.Stdout).With().
        Timestamp().
        Str("service", "cron-scheduler").
        Logger()

    cronLogger := &ZerologCronLogger{logger: logger}

    c := cron.New(
        cron.WithLogger(cronLogger),
        cron.WithChain(cron.Recover(cronLogger)),
    )

    c.AddFunc("* * * * *", func() {
        log.Info().Msg("任务执行中")
    })

    c.Start()
    select {}
}

任务执行日志记录

package main

import (
    "log"
    "time"

    "github.com/robfig/cron/v3"
    "gorm.io/gorm"
)

// 任务执行记录
type JobExecutionLog struct {
    ID        uint      `gorm:"primarykey"`
    JobName   string    `gorm:"index"`
    StartTime time.Time
    EndTime   time.Time
    Duration  int64     // 毫秒
    Status    string    // success, failed, timeout
    Error     string    `gorm:"type:text"`
    CreatedAt time.Time
}

// 日志记录中间件
func LoggingWrapper(db *gorm.DB, jobName string) cron.JobWrapper {
    return func(job cron.Job) cron.Job {
        return cron.FuncJob(func() {
            record := &JobExecutionLog{
                JobName:   jobName,
                StartTime: time.Now(),
                Status:    "running",
            }
            
            defer func() {
                record.EndTime = time.Now()
                record.Duration = record.EndTime.Sub(record.StartTime).Milliseconds()
                
                if r := recover(); r != nil {
                    record.Status = "failed"
                    record.Error = fmt.Sprintf("%v", r)
                } else if record.Status == "running" {
                    record.Status = "success"
                }
                
                // 保存执行记录
                if err := db.Create(record).Error; err != nil {
                    log.Printf("保存执行记录失败: %v", err)
                }
            }()
            
            job.Run()
        })
    }
}

分布式定时任务

在分布式环境中,需要确保定时任务不会被多个实例重复执行。

基于 Redis 的分布式锁

package main

import (
    "context"
    "log"
    "time"

    "github.com/go-redis/redis/v8"
    "github.com/robfig/cron/v3"
)

type DistributedLock struct {
    client *redis.Client
    key    string
    value  string
    ttl    time.Duration
}

func NewDistributedLock(client *redis.Client, key string, ttl time.Duration) *DistributedLock {
    return &DistributedLock{
        client: client,
        key:    "cron_lock:" + key,
        value:  generateUUID(), // 使用唯一标识
        ttl:    ttl,
    }
}

func (l *DistributedLock) Acquire(ctx context.Context) (bool, error) {
    return l.client.SetNX(ctx, l.key, l.value, l.ttl).Result()
}

func (l *DistributedLock) Release(ctx context.Context) error {
    // 使用 Lua 脚本确保只删除自己的锁
    script := `
        if redis.call("GET", KEYS[1]) == ARGV[1] then
            return redis.call("DEL", KEYS[1])
        else
            return 0
        end
    `
    _, err := l.client.Eval(ctx, script, []string{l.key}, l.value).Result()
    return err
}

// 分布式任务中间件
func DistributedWrapper(client *redis.Client, lockTTL time.Duration) cron.JobWrapper {
    return func(job cron.Job) cron.Job {
        return cron.FuncJob(func() {
            ctx := context.Background()
            
            // 获取任务名称(简化处理)
            lockKey := "default_job"
            
            lock := NewDistributedLock(client, lockKey, lockTTL)
            
            acquired, err := lock.Acquire(ctx)
            if err != nil {
                log.Printf("获取锁失败: %v", err)
                return
            }
            
            if !acquired {
                log.Println("未获取到锁,跳过执行")
                return
            }
            
            defer lock.Release(ctx)
            
            log.Println("获取锁成功,开始执行任务")
            job.Run()
        })
    }
}

func main() {
    // Redis 客户端
    rdb := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })

    c := cron.New(
        cron.WithChain(
            cron.Recover(cron.DefaultLogger),
            DistributedWrapper(rdb, 60*time.Second),
        ),
    )

    c.AddFunc("* * * * *", func() {
        log.Println("分布式任务执行中...")
        time.Sleep(10 * time.Second)
    })

    c.Start()
    select {}
}

使用 Redsync 库

package main

import (
    "log"
    "time"

    "github.com/go-redis/redis/v8"
    "github.com/go-redsync/redsync/v4"
    "github.com/go-redsync/redsync/v4/redis/goredis/v8"
    "github.com/robfig/cron/v3"
)

func main() {
    // 创建 Redis 连接池
    client := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })
    pool := goredis.NewPool(client)

    // 创建 Redsync 实例
    rs := redsync.New(pool)

    c := cron.New(cron.WithSeconds())

    c.AddFunc("*/10 * * * * *", func() {
        // 创建互斥锁
        mutex := rs.NewMutex("cron:data-sync",
            redsync.WithExpiry(30*time.Second),
            redsync.WithTries(1), // 只尝试一次
        )

        // 尝试获取锁
        if err := mutex.Lock(); err != nil {
            log.Println("其他实例正在执行,跳过")
            return
        }
        defer mutex.Unlock()

        log.Println("执行数据同步任务...")
        time.Sleep(5 * time.Second)
        log.Println("数据同步完成")
    })

    c.Start()
    select {}
}

分布式调度架构

┌─────────────────────────────────────────────────────────────────┐
│                   分布式定时任务架构                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ┌──────────────────────────────────────────────────────────┐  │
│   │                        服务实例                          │  │
│   │  ┌─────────┐    ┌─────────┐    ┌─────────┐              │  │
│   │  │ 实例 A  │    │ 实例 B  │    │ 实例 C  │              │  │
│   │  │  cron   │    │  cron   │    │  cron   │              │  │
│   │  └────┬────┘    └────┬────┘    └────┬────┘              │  │
│   │       │              │              │                    │  │
│   └───────┼──────────────┼──────────────┼────────────────────┘  │
│           │              │              │                       │
│           └──────────────┼──────────────┘                       │
│                          ▼                                      │
│   ┌──────────────────────────────────────────────────────────┐  │
│   │               Redis (分布式锁)                           │  │
│   │  ┌─────────────────────────────────────────────────────┐ │  │
│   │  │ cron_lock:data_sync = "instance_a_uuid" TTL=60s     │ │  │
│   │  │ cron_lock:cleanup = "instance_b_uuid" TTL=120s      │ │  │
│   │  └─────────────────────────────────────────────────────┘ │  │
│   └──────────────────────────────────────────────────────────┘  │
│                                                                 │
│   执行流程:                                                    │
│   1. 所有实例同时触发定时器                                     │
│   2. 尝试获取 Redis 分布式锁                                    │
│   3. 只有一个实例获取成功并执行                                 │
│   4. 其他实例跳过本次执行                                       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

实战案例

案例1:数据备份任务

package main

import (
    "compress/gzip"
    "fmt"
    "io"
    "log"
    "os"
    "os/exec"
    "path/filepath"
    "time"

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

type BackupConfig struct {
    DBHost     string
    DBUser     string
    DBPassword string
    DBName     string
    BackupDir  string
    RetainDays int
}

type DatabaseBackupJob struct {
    config BackupConfig
}

func (j *DatabaseBackupJob) Run() {
    log.Println("开始数据库备份...")
    
    // 创建备份目录
    if err := os.MkdirAll(j.config.BackupDir, 0755); err != nil {
        log.Printf("创建备份目录失败: %v", err)
        return
    }

    // 生成备份文件名
    timestamp := time.Now().Format("20060102_150405")
    filename := fmt.Sprintf("%s_%s.sql", j.config.DBName, timestamp)
    filepath := filepath.Join(j.config.BackupDir, filename)

    // 执行 mysqldump
    cmd := exec.Command("mysqldump",
        "-h", j.config.DBHost,
        "-u", j.config.DBUser,
        fmt.Sprintf("-p%s", j.config.DBPassword),
        j.config.DBName,
    )

    outfile, err := os.Create(filepath)
    if err != nil {
        log.Printf("创建备份文件失败: %v", err)
        return
    }
    defer outfile.Close()

    // 压缩输出
    gzWriter := gzip.NewWriter(outfile)
    defer gzWriter.Close()

    cmd.Stdout = gzWriter
    cmd.Stderr = os.Stderr

    if err := cmd.Run(); err != nil {
        log.Printf("备份执行失败: %v", err)
        return
    }

    log.Printf("备份完成: %s.gz", filepath)

    // 清理旧备份
    j.cleanOldBackups()
}

func (j *DatabaseBackupJob) cleanOldBackups() {
    cutoff := time.Now().AddDate(0, 0, -j.config.RetainDays)
    
    files, _ := os.ReadDir(j.config.BackupDir)
    for _, file := range files {
        info, _ := file.Info()
        if info.ModTime().Before(cutoff) {
            path := filepath.Join(j.config.BackupDir, file.Name())
            if err := os.Remove(path); err == nil {
                log.Printf("删除旧备份: %s", file.Name())
            }
        }
    }
}

func main() {
    c := cron.New()

    backupJob := &DatabaseBackupJob{
        config: BackupConfig{
            DBHost:     "localhost",
            DBUser:     "root",
            DBPassword: "password",
            DBName:     "myapp",
            BackupDir:  "/var/backups/mysql",
            RetainDays: 7,
        },
    }

    // 每天凌晨2点执行备份
    c.AddJob("0 2 * * *", backupJob)

    c.Start()
    log.Println("数据库备份任务已启动")
    select {}
}

案例2:订单超时处理

package main

import (
    "context"
    "log"
    "time"

    "github.com/robfig/cron/v3"
    "gorm.io/gorm"
)

type Order struct {
    ID        uint      `gorm:"primarykey"`
    OrderNo   string    `gorm:"uniqueIndex"`
    Status    string    // pending, paid, cancelled, timeout
    Amount    float64
    CreatedAt time.Time
    PaidAt    *time.Time
}

type OrderTimeoutJob struct {
    db            *gorm.DB
    timeoutMinutes int
    batchSize     int
}

func (j *OrderTimeoutJob) Run() {
    log.Println("开始处理超时订单...")
    
    ctx := context.Background()
    cutoff := time.Now().Add(-time.Duration(j.timeoutMinutes) * time.Minute)
    
    var totalProcessed int
    
    for {
        // 分批查询待付款且超时的订单
        var orders []Order
        result := j.db.WithContext(ctx).
            Where("status = ?", "pending").
            Where("created_at < ?", cutoff).
            Limit(j.batchSize).
            Find(&orders)

        if result.Error != nil {
            log.Printf("查询订单失败: %v", result.Error)
            return
        }

        if len(orders) == 0 {
            break
        }

        // 批量更新状态
        for _, order := range orders {
            err := j.db.WithContext(ctx).
                Model(&order).
                Update("status", "timeout").
                Error

            if err != nil {
                log.Printf("更新订单 %s 失败: %v", order.OrderNo, err)
                continue
            }
            
            log.Printf("订单 %s 已超时关闭", order.OrderNo)
            totalProcessed++

            // 释放库存(如果有)
            // j.releaseStock(order)
            
            // 发送通知
            // j.notifyUser(order)
        }
    }

    log.Printf("超时订单处理完成,共处理 %d", totalProcessed)
}

func main() {
    // 假设 db 已初始化
    var db *gorm.DB

    c := cron.New()

    orderTimeoutJob := &OrderTimeoutJob{
        db:            db,
        timeoutMinutes: 30, // 30分钟超时
        batchSize:     100,
    }

    // 每分钟检查一次
    c.AddJob("* * * * *", orderTimeoutJob)

    c.Start()
    select {}
}

案例3:缓存刷新任务

package main

import (
    "context"
    "encoding/json"
    "log"
    "time"

    "github.com/go-redis/redis/v8"
    "github.com/robfig/cron/v3"
    "gorm.io/gorm"
)

type Product struct {
    ID    uint    `json:"id"`
    Name  string  `json:"name"`
    Price float64 `json:"price"`
    Stock int     `json:"stock"`
}

type CacheRefreshJob struct {
    db    *gorm.DB
    redis *redis.Client
}

func (j *CacheRefreshJob) Run() {
    ctx := context.Background()
    log.Println("开始刷新缓存...")

    // 刷新热门商品缓存
    j.refreshHotProducts(ctx)
    
    // 刷新分类缓存
    j.refreshCategories(ctx)
    
    // 刷新配置缓存
    j.refreshConfig(ctx)

    log.Println("缓存刷新完成")
}

func (j *CacheRefreshJob) refreshHotProducts(ctx context.Context) {
    var products []Product
    
    // 查询热门商品
    j.db.Order("sales_count DESC").Limit(100).Find(&products)
    
    // 序列化并写入 Redis
    data, _ := json.Marshal(products)
    j.redis.Set(ctx, "cache:hot_products", data, 10*time.Minute)
    
    log.Printf("热门商品缓存已刷新,共 %d", len(products))
}

func (j *CacheRefreshJob) refreshCategories(ctx context.Context) {
    // 刷新分类缓存逻辑
}

func (j *CacheRefreshJob) refreshConfig(ctx context.Context) {
    // 刷新配置缓存逻辑
}

func main() {
    var db *gorm.DB
    var rdb *redis.Client

    c := cron.New()

    cacheJob := &CacheRefreshJob{
        db:    db,
        redis: rdb,
    }

    // 每5分钟刷新一次缓存
    c.AddJob("*/5 * * * *", cacheJob)

    c.Start()
    select {}
}

案例4:完整的任务调度服务

package main

import (
    "context"
    "encoding/json"
    "log"
    "net/http"
    "os"
    "os/signal"
    "sync"
    "syscall"
    "time"

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

// 任务定义
type JobDefinition struct {
    Name        string `json:"name"`
    Expression  string `json:"expression"`
    Description string `json:"description"`
    Enabled     bool   `json:"enabled"`
}

// 调度器服务
type SchedulerService struct {
    cron       *cron.Cron
    jobs       map[string]cron.EntryID
    mu         sync.RWMutex
    httpServer *http.Server
}

func NewSchedulerService() *SchedulerService {
    return &SchedulerService{
        cron: cron.New(
            cron.WithSeconds(),
            cron.WithChain(
                cron.Recover(cron.DefaultLogger),
                cron.SkipIfStillRunning(cron.DefaultLogger),
            ),
        ),
        jobs: make(map[string]cron.EntryID),
    }
}

// 注册内置任务
func (s *SchedulerService) RegisterBuiltinJobs() {
    // 健康检查
    s.RegisterJob("health-check", "*/30 * * * * *", func() {
        log.Println("健康检查: OK")
    })

    // 清理过期会话
    s.RegisterJob("session-cleanup", "0 */10 * * * *", func() {
        log.Println("清理过期会话...")
    })

    // 数据统计
    s.RegisterJob("daily-stats", "0 0 1 * * *", func() {
        log.Println("生成每日统计报告...")
    })
}

func (s *SchedulerService) RegisterJob(name, expr string, fn func()) error {
    s.mu.Lock()
    defer s.mu.Unlock()

    // 如果已存在,先移除
    if existingID, ok := s.jobs[name]; ok {
        s.cron.Remove(existingID)
    }

    id, err := s.cron.AddFunc(expr, fn)
    if err != nil {
        return err
    }

    s.jobs[name] = id
    log.Printf("注册任务: %s (%s), ID: %d", name, expr, id)
    return nil
}

func (s *SchedulerService) RemoveJob(name string) {
    s.mu.Lock()
    defer s.mu.Unlock()

    if id, ok := s.jobs[name]; ok {
        s.cron.Remove(id)
        delete(s.jobs, name)
        log.Printf("移除任务: %s", name)
    }
}

// HTTP API
func (s *SchedulerService) setupHTTPHandlers() {
    mux := http.NewServeMux()

    // 获取所有任务
    mux.HandleFunc("/api/jobs", func(w http.ResponseWriter, r *http.Request) {
        s.mu.RLock()
        defer s.mu.RUnlock()

        entries := s.cron.Entries()
        type JobInfo struct {
            Name     string    `json:"name"`
            ID       int       `json:"id"`
            Next     time.Time `json:"next"`
            Prev     time.Time `json:"prev"`
        }

        jobs := make([]JobInfo, 0)
        for name, id := range s.jobs {
            for _, entry := range entries {
                if entry.ID == id {
                    jobs = append(jobs, JobInfo{
                        Name: name,
                        ID:   int(id),
                        Next: entry.Next,
                        Prev: entry.Prev,
                    })
                    break
                }
            }
        }

        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(jobs)
    })

    // 手动触发任务
    mux.HandleFunc("/api/jobs/trigger", func(w http.ResponseWriter, r *http.Request) {
        if r.Method != http.MethodPost {
            http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
            return
        }

        name := r.URL.Query().Get("name")
        s.mu.RLock()
        id, ok := s.jobs[name]
        s.mu.RUnlock()

        if !ok {
            http.Error(w, "Job not found", http.StatusNotFound)
            return
        }

        entry := s.cron.Entry(id)
        go entry.Job.Run()

        w.Write([]byte("Job triggered"))
    })

    s.httpServer = &http.Server{
        Addr:    ":8080",
        Handler: mux,
    }
}

func (s *SchedulerService) Start() {
    s.cron.Start()
    s.setupHTTPHandlers()
    
    go func() {
        log.Println("HTTP API 启动: http://localhost:8080")
        if err := s.httpServer.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatal(err)
        }
    }()
}

func (s *SchedulerService) Stop() {
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    // 停止 HTTP 服务
    s.httpServer.Shutdown(ctx)

    // 停止 cron 并等待任务完成
    cronCtx := s.cron.Stop()
    <-cronCtx.Done()

    log.Println("调度器已停止")
}

func main() {
    scheduler := NewSchedulerService()
    scheduler.RegisterBuiltinJobs()
    scheduler.Start()

    // 等待退出信号
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    log.Println("正在关闭...")
    scheduler.Stop()
}

最佳实践与注意事项

任务设计原则

┌─────────────────────────────────────────────────────────────────┐
│                     定时任务设计原则                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   1️⃣  幂等性                                                    │
│      • 任务重复执行应产生相同结果                               │
│      • 使用唯一约束防止重复数据                                 │
│      • 记录处理进度,支持断点续传                               │
│                                                                 │
│   2️⃣  原子性                                                    │
│      • 使用事务保证数据一致性                                   │
│      • 失败时能够回滚或重试                                     │
│                                                                 │
│   3️⃣  可观测性                                                  │
│      • 记录详细的执行日志                                       │
│      • 监控任务执行时长和成功率                                 │
│      • 配置告警通知                                             │
│                                                                 │
│   4️⃣  容错性                                                    │
│      • 捕获并处理异常                                           │
│      • 实现重试机制                                             │
│      • 设置超时限制                                             │
│                                                                 │
│   5️⃣  可控性                                                    │
│      • 支持手动触发和停止                                       │
│      • 提供任务状态查询接口                                     │
│      • 支持动态调整执行时间                                     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

常见陷阱与解决方案

陷阱问题描述解决方案
任务堆积任务执行时间超过调度间隔使用 SkipIfStillRunningDelayIfStillRunning
时区混乱服务器时区与业务时区不一致明确指定 WithLocation
内存泄漏任务中的资源未正确释放使用 defer 释放资源,定期检查内存
Panic 导致崩溃未捕获的异常导致整个调度器停止使用 Recover 中间件
分布式重复执行多个实例同时执行同一任务使用分布式锁
长时间任务阻塞某个任务执行过久占用资源设置超时控制

性能优化建议

// 1. 批量处理数据
func (j *Job) Run() {
    const batchSize = 1000
    offset := 0
    
    for {
        var items []Item
        db.Offset(offset).Limit(batchSize).Find(&items)
        
        if len(items) == 0 {
            break
        }
        
        // 批量处理
        processItems(items)
        offset += batchSize
    }
}

// 2. 使用 goroutine 池
func (j *Job) Run() {
    items := getItems()
    
    wp := workerpool.New(10) // 10 个并发
    for _, item := range items {
        item := item
        wp.Submit(func() {
            processItem(item)
        })
    }
    wp.StopWait()
}

// 3. 避免在任务中进行大量内存分配
func (j *Job) Run() {
    // 重用 buffer
    buf := j.bufferPool.Get().(*bytes.Buffer)
    defer func() {
        buf.Reset()
        j.bufferPool.Put(buf)
    }()
    
    // 使用 buffer 进行操作
}

监控指标

建议监控以下指标:

指标说明告警阈值建议
cron_job_duration_seconds任务执行时长P99 > 预期时间的 2 倍
cron_job_success_total成功次数-
cron_job_failure_total失败次数连续 3 次失败
cron_job_last_success_time上次成功时间超过预期间隔的 2 倍未执行
cron_job_running当前运行中的任务数超过总任务数
// Prometheus 指标示例
var (
    jobDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "cron_job_duration_seconds",
            Help:    "Duration of cron job execution",
            Buckets: prometheus.DefBuckets,
        },
        []string{"job_name"},
    )
    
    jobSuccess = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "cron_job_success_total",
            Help: "Total number of successful cron job executions",
        },
        []string{"job_name"},
    )
    
    jobFailure = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "cron_job_failure_total",
            Help: "Total number of failed cron job executions",
        },
        []string{"job_name"},
    )
)

func MetricsWrapper(jobName string) cron.JobWrapper {
    return func(job cron.Job) cron.Job {
        return cron.FuncJob(func() {
            start := time.Now()
            defer func() {
                duration := time.Since(start).Seconds()
                jobDuration.WithLabelValues(jobName).Observe(duration)
                
                if r := recover(); r != nil {
                    jobFailure.WithLabelValues(jobName).Inc()
                    panic(r) // 继续传递 panic
                }
                jobSuccess.WithLabelValues(jobName).Inc()
            }()
            job.Run()
        })
    }
}

生产环境检查清单

┌─────────────────────────────────────────────────────────────────┐
│                   生产环境上线检查清单                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   □ Cron 表达式已验证(使用在线工具测试)                       │
│   □ 时区配置正确                                                │
│   □ 使用了 Recover 中间件                                      │
│   □ 配置了适当的日志记录                                       │
│   □ 实现了错误告警机制                                         │
│   □ 分布式环境使用了分布式锁                                   │
│   □ 任务具有幂等性                                             │
│   □ 设置了合理的超时时间                                       │
│   □ 监控指标已接入                                             │
│   □ 优雅关闭逻辑已实现                                         │
│   □ 任务执行记录可追溯                                         │
│   □ 手动触发接口可用(用于紧急情况)                           │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

配置文件示例

# config/cron.yaml
scheduler:
  timezone: "Asia/Shanghai"
  enable_seconds: true
  
jobs:
  - name: "data-backup"
    expression: "0 2 * * *"
    enabled: true
    timeout: 3600s
    retry:
      max_attempts: 3
      delay: 60s
      
  - name: "order-timeout"
    expression: "* * * * *"
    enabled: true
    timeout: 300s
    distributed_lock:
      enabled: true
      ttl: 120s
      
  - name: "cache-refresh"
    expression: "*/5 * * * *"
    enabled: true
    timeout: 60s
    
  - name: "daily-report"
    expression: "0 8 * * *"
    enabled: true
    timeout: 1800s
    notify:
      on_success: false
      on_failure: true
      channels: ["email", "dingtalk"]

参考资料

官方文档

  1. robfig/cron GitHub - 官方仓库
  2. robfig/cron GoDoc - API 文档

Cron 表达式工具

  1. Crontab Guru - 在线 Cron 表达式解析器
  2. Cron Expression Generator - 表达式生成器

相关库

  1. go-co-op/gocron - 另一个流行的 Go 定时任务库
  2. go-redsync/redsync - Redis 分布式锁
  3. hibiken/asynq - 基于 Redis 的任务队列

扩展阅读

  1. Cron 在分布式系统中的挑战
  2. 定时任务的几种实现方式对比
  3. Go 并发模式

评论 (0)

请先登录后再发表评论

暂无评论,来发表第一条评论吧!