必须用lua脚本或zpopmin封装“取+标+删”三步,因zrangebyscore与zrem分离调用存在毫秒级竞态窗口,导致多worker重复消费或任务永久丢失;score须统一为毫秒级时间戳并设ttl防堆积。

直接用 ZADD + ZRANGEBYSCORE 轮询做延时队列,在多 worker 场景下必然重复消费或丢任务——这不是配置问题,是原子性缺失导致的状态竞争,必须用 Lua 或 ZPOPMIN 封装“取+标+删”三步。
为什么 ZRANGEBYSCORE + ZREM 分开调用会翻车
两个命令之间存在毫秒级时间窗口:Worker A 查到 3 条到期任务,还没来得及 ZREM,Worker B 同样查到这 3 条,结果两边都执行;更糟的是,若 A 执行 panic 或网络中断,ZREM 没发出去,B 又没拿到,任务就永久丢失。
-
ZRANGEBYSCORE返回的是快照,不是锁;它不阻塞其他 client 的读写 - 哪怕只差 2ms,分布式环境下也足以让两个 goroutine 同时命中同一 score 区间
- 老版本 Redis(ZPOPMIN,必须靠 Lua 脚本兜底,否则没法上线
Go 中用 ZPOPMIN 实现安全出队的实操要点
ZPOPMIN 是 Redis 5.0+ 原生解法,单命令弹出最小 score 元素,天然避免查删分离。但它只管“取”,不管“后续怎么活”,直接裸用照样翻车。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 调用
ZPopMin时必须传count参数(如 10),避免单次只取一个拖慢吞吐 - 返回的
ZSetZ.Score是毫秒时间戳,务必用time.UnixMilli(int64(score))校验是否真到期——防系统时钟回拨或网络延迟误触发 - 别只取
Member,忽略Score;否则重试逻辑可能把 5 秒后任务当成现在执行 - pop 成功后立刻用
HSET delay_processing <member><worker_id></worker_id></member>标记,否则崩溃后无法恢复
Lua 脚本必须覆盖“取→标→推”全链路
只封装“取+删”不够,必须把标记处理中状态也包进去。否则 worker crash 后,任务既不在 zset 也不在 processing hash,彻底消失。
- 脚本示例(Go 客户端调用时传
KEYS[1]="delay_queue",KEYS[2]="delay_processing",ARGV[1]=worker_id):local res = redis.call('ZPOPMIN', KEYS[1])<br>if not res or #res == 0 then return nil end<br>redis.call('HSET', KEYS[2], res[1], ARGV[1])<br>return res - 消费成功后不能只写
HDEL delay_processing <payload></payload>—— panic、context timeout、write 超时都会让它发不出去,必须用 Lua 做条件清理 - 所有状态变更(zset → processing → success/fail → dlq)都得进 Lua,否则任意一环失败,状态就断了
- 脚本里别做耗时操作(比如 HTTP 请求),超时会阻塞整个 Redis 实例
score 精度和轮询节奏怎么拿捏
用秒级时间戳当 score 是最常见的时间精度翻车点。哪怕业务容忍 ±1 秒,只要 QPS > 100,同秒内插入的任务就会因 score 相同而随机排序,ZPOPMIN 一次吐出一堆,下游扛不住。
- 生产必须统一用
time.Now().UnixMilli(),不是Unix(),更不是UnixNano()(转 float64 会丢精度) - 轮询前先用
ZCOUNT delay_queue -inf <now_ms></now_ms>预判,为空就跳过ZPOPMIN,省掉无效调用 - 空转时休眠从 100ms 起,连续 5 次无任务升到 500ms;有任务立刻回落,避免响应毛刺
-
delay_queuekey 必须设EXPIRE(如 86400),否则测试残留、失败任务永久堆积,内存只增不减
真正难的不是第一次跑通,而是让系统在节点宕机、网络分区、consumer panic 后还能自己续上——所有状态变更都得有反向可逆路径,且每条路径都得进 Lua。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










