gocron不提供开箱即用的分布式能力,withdistributedlocker仅声明接口,需自行实现redis锁,且必须满足原子加锁、ttl≥3倍任务耗时、lua校验value再删锁等关键细节。

gocron 本身不提供分布式能力,直接用 Start 或 StartBlocking 会在每个实例上独立跑任务,必然重复执行。 它的 WithDistributedLocker 接口只是占位,没有内置实现,必须自己补全锁逻辑,否则等于没用。
gocron.WithDistributedLocker 不是开箱即用的分布式开关
这个方法只声明了一个接口类型 DistributedLocker,要求你传入实现了 Lock 和 Unlock 的对象。但 gocron 不管你锁怎么实现、何时加、是否续期、失败怎么退——它只在每次任务触发前调一次 Lock(),成功就执行,失败就跳过。
- 如果你传了个假实现(比如直接 return true),那还是重复执行
- 如果锁没设 TTL,节点宕机后锁永远不释放,任务彻底卡死
- 如果
Unlock没做 value 校验(比如用 Lua 脚本比对 key 当前值),可能删掉别人刚抢到的锁 - gocron 不处理锁自动续期,长任务容易中途丢锁
用 Redis 实现 gocron 分布式锁的关键细节
要让 gocron.WithDistributedLocker 真正生效,必须自己写一个符合语义的 RedisLocker。核心不是“能加锁”,而是“锁住期间不被误踢、不被误删、不过期失效”。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
-
Lock必须用SET key value EX seconds NX原子操作,value 设为唯一标识(如os.Getenv("HOSTNAME") + ":" + strconv.Itoa(os.Getpid())) - TTL 必须 ≥ 3 × 单次任务最大耗时(例如任务最长 20s,TTL 至少设 60s)
-
Unlock必须用 Lua 脚本校验 value 再删:if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end - 不要用
time.Now().Unix()拼 lock key —— 机器时钟不同步会导致 key 冲突或漏锁;改用任务名 + 下次计划时间戳(由统一调度源生成)
gocron 的触发时机决定了锁必须在任务入口加
gocron 的 Job 是函数对象,WithDistributedLocker 只在 job.Run() 前调用 Lock()。这意味着:锁必须在业务逻辑真正开始前拿到,不能放在函数内部靠后位置。
- 错误写法:
func() { doSomething(); if !tryLock() { return }; doRealWork() }—— 中间耗时操作已执行,锁形同虚设 - 正确写法:把整个业务逻辑包进一个函数,交给 gocron 调度,锁由
DistributedLocker.Lock()在最外层控制 - 注意 context:gocron 不传递
context.Context给Lock(),超时需在Lock()内部自行控制(比如用context.WithTimeout包裹 Redis 调用)
真正难的不是写个 Lock() 方法,而是锁的生命周期管理:任务执行中是否续期?节点崩溃时锁如何清理?多个任务共用一把锁还是每任务独立锁?这些细节一旦疏忽,就会出现“看似上了锁,实则照常并发”的静默故障。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










