gocron多实例会重复执行,因其本质是本地定时器,每个实例独立维护timer heap和job列表,无内置协调机制;需配合redis/etcd分布式锁,在cron触发后由tryrunjob抢锁决定唯一执行者。

gocron 本身不支持分布式调度,直接多实例部署必然重复执行任务。它只是个本地定时器,不是调度中心。
为什么 gocron 多实例会重复执行
-
gocron.NewScheduler()每个进程都独立维护一套 timer heap 和 job 列表 - 所有节点同时解析
Every(1).Hour().Do(...),各自在整点触发,毫无协调 - 日志里看到
send_report executed出现 3 次/小时?那是 3 台机器各跑了一次,不是“高可用”,是“高重复” - 它的
WithDistributedLocker接口只是占位符,不带任何实现,文档没说清楚这点,容易误判
怎么用 gocron 实现真正分布式调度
把 gocron 当作“本地闹钟”,只负责精准触发;真正的调度决策、去重、选主,全交给外部协调层:
所有节点共用一个
gocron.Every(1).Minutes().Do(tryRunJob),每分钟 tick 一次
Alibabacloud Sdk Client Initialization For Golang下载初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
-
tryRunJob内部做三件事:- 查询 Redis 或 Etcd,确认当前节点是否被选为该任务的执行者(例如通过
SET scheduler:leader nodeA NX EX 10) - 尝试获取任务粒度锁:
SET task:lock:sync_user_20240609 1 NX EX 180 - 锁成功才调用真实业务函数,失败直接 return
- 查询 Redis 或 Etcd,确认当前节点是否被选为该任务的执行者(例如通过
不要在
Do()里写耗时逻辑或阻塞 IO,否则会拖慢整个调度周期,导致后续 tick 积压
Redis 锁实现要注意的关键细节
-
EX时间必须 ≥ 任务最长可能耗时 × 3,否则锁提前释放,其他节点可能并发进入 - value 写入唯一标识(如
os.Getenv("HOSTNAME")或 pod UID),出问题时能快速定位谁在执行 - 别用
GET + SET模拟原子操作:网络延迟下无法区分“锁已存在”和“锁已过期但 key 还没被删” - 使用
github.com/redis/go-redis/v9的SetNX方法,确保原子性 - 如果任务需幂等重试(比如发邮件失败后 5 分钟重试),锁 key 要包含 execID,不能只用 jobName
容易被忽略的故障点
时钟偏移会让基于时间的判断失效——比如用 time.Now().Unix() 生成锁 key 或判断任务是否“该执行了”,不同机器差几秒就可能导致漏触发或双触发。真正在意一致性时,优先用 Etcd 的 lease + watch,或者 Redis 的 Lua 脚本做原子校验,而不是依赖本地时间。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










