不能直接用 github.com/robfig/cron 做分布式调度,因其无内置协调机制,多实例会各自触发同一任务导致重复执行;必须借助 etcd 或 Redis 实现 Leader 选举与任务分发,将 cron 仅作本地触发器使用。

为什么不能直接用 github.com/robfig/cron 做分布式调度
它本身不带分布式协调能力,多个服务实例会各自执行同一任务,导致重复触发。哪怕加了锁(比如用 Redis 实现),也得自己处理故障恢复、Leader 选举、任务分片——这已经超出 cron 库的设计范畴。
真正可用的方案是换掉底层调度器,而不是给 cron 打补丁。
-
robfig/cron适合单机场景,启动时注册几个定时任务就够了 - 微服务集群里必须有中心化或共识驱动的任务分发机制
- 任务元信息(表达式、状态、执行日志)需要持久化,不能只存在内存里
用 go.etcd.io/etcd/client/v3 + github.com/robfig/cron/v3 实现轻量级分布式调度
核心思路:用 etcd 做分布式锁和任务注册中心,每个服务实例启动时尝试抢锁;抢到锁的节点才初始化 cron 实例并加载任务。
关键点不在“怎么写 cron 表达式”,而在“谁有资格执行”:
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 所有实例监听同一个 etcd key(如
/scheduler/leader),用CompareAndSwap竞争 Leader - Leader 节点定期刷新租约(TTL),其他节点 watch 该 key,一旦失效立即重新竞选
- 任务定义统一存放在 etcd 的
/jobs/下,Leader 启动时遍历加载,非 Leader 不解析也不运行 - 避免把任务逻辑写死在代码里——每次增删任务只需写入 etcd,Leader 自动 reload
示例片段(抢锁逻辑):
sess, err := concurrency.NewSession(client, concurrency.WithTTL(15))
if err != nil {
log.Fatal(err)
}
elected := concurrency.NewElection(sess, "/scheduler/leader")
if err := elected.Campaign(context.TODO(), "instance-001"); err != nil {
// 未当选,退出定时器初始化
}用 github.com/go-co-op/gocron 替代 robfig/cron 的注意事项
gocron 支持内置的分布式模式(基于 Redis),但默认行为容易踩坑:
- 它用 Redis 的
SETNX做锁,但没做自动续期,长任务可能被误判为失败而被其他节点抢占 -
StartDaemon()默认会阻塞当前 goroutine,微服务里要确保它在独立 goroutine 中运行 - 任务函数必须是无状态的,不能依赖本地缓存或未共享的 struct 字段——因为下次执行可能落在另一台机器上
- Redis 连接池大小要调高(尤其任务多时),否则
redis: connection pool exhausted错误频发
配置示例:
s := gocron.NewScheduler(gocron.WithDistributedRunner(
gocron.WithRedis(&redis.UniversalOptions{
Addrs: []string{"redis://localhost:6379"},
})),
)
s.StartAsync() // 注意不是 StartBlocking任务幂等性和失败重试怎么设计
分布式环境下,任务可能执行中网络断开、进程崩溃、Leader 切换,必须假设“一次调度 = 可能多次执行”。
- 所有任务入口加唯一 ID(比如从 etcd key 或 job definition hash 生成),写入执行记录前先查 DB 是否已成功
- 避免用
time.Now().Unix()当唯一标识——不同机器时间不同步会导致冲突 - 重试间隔不能固定,要用指数退避(
2^attempt * base),否则大量失败任务会在同一时刻集中重试,压垮下游 - 不要在定时任务里直接调用 RPC 更新状态——应发消息到 Kafka / NATS,由消费者保证最终一致性
最容易被忽略的是:etcd lease 续期失败后,Leader 节点未必立刻退出,可能继续运行几秒再 panic。这段时间内新任务仍会被调度,但后续节点已开始竞选——造成短暂双 Leader。

















