不能直接用lpush+brpop做优先级队列,因其仅支持fifo,无法跳过低优先级任务;多list轮询有竞态且非原子;正确做法是zset+lua实现原子取最高优任务。

为什么不能直接用 LPUSH + BRPOP 做优先级队列
Redis 原生没有优先级队列数据结构,LPUSH/RPUSH + BRPOP 只能实现 FIFO,高优先级任务一旦被低优先级任务“挡在后面”,就得等。真实场景里,你发了个紧急重试任务,结果卡在 500 个普通同步任务后面,延迟飙升——这不是设计问题,是数据结构选错了。
常见错误是试图靠多个 list(如 queue:high、queue:normal)+ 客户端轮询来模拟优先级,但会引入竞态:两个 worker 同时 BRPOP 不同 key,可能都阻塞,或漏掉高优任务;更糟的是,客户端需维护连接和超时逻辑,可靠性直线下滑。
- 多 list 轮询无法保证原子性:没有“从 high 里取一个,没就去 normal 取”的单次原子操作
-
BRPOP不支持按 score 或条件过滤,没法跳过低优任务 - 手动分发增加网络往返,吞吐下降明显,尤其在高并发短任务场景
用 ZSET 存任务 + EVAL 执行 Lua 脚本的正确姿势
核心思路:把任务 ID 当 member,优先级(越小越优先)当 score,存进 ZSET;消费时用 Lua 原子读取并移除最高优任务。Lua 脚本在 Redis 单线程内执行,ZRANGE + ZREM 不会穿插其他操作,彻底规避竞态。
示例脚本(保存为 pop_highest.lua):
local task = redis.call('ZRANGE', KEYS[1], 0, 0, 'WITHSCORES')
if #task == 0 then
return nil
end
redis.call('ZREM', KEYS[1], task[1])
return {task[1], task[2]}
调用方式:
redis-cli --eval pop_highest.lua queue:tasks ,
注意点:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
-
KEYS[1]是队列名,必须传;,后面是ARGV,这里没用到,所以留空 - 返回值是数组:
{'task_id_123', '1'},其中'1'是 score,即优先级数值 - 不要用
ZRANGEBYSCORE,它不保证“最小 score”唯一;ZRANGE ... WITHSCORES取第 0 个,才是严格最高优
如何让不同服务安全共享同一套优先级队列
多个服务往同一个 ZSET 写任务,必须避免 score 冲突导致顺序错乱。单纯用时间戳当 score(如 System.currentTimeMillis())在毫秒级并发下极易重复;全用固定值(如 0)又退化成无序。
推荐方案:score = timestamp_ms * 1000000 + sequence,sequence 由服务本地递增(每毫秒重置),确保全局单调递增且可比较。
实际写入命令:
ZADD queue:tasks 1672531200000001 "task:retry:user_456"
关键约束:
- 所有写入必须走
ZADD,禁用ZINCRBY或ZADD ... NX,否则无法控制 score 生成逻辑 - 消费端不要依赖 score 数值本身做业务判断,只用于排序;业务字段放 task payload 里(如 JSON 字符串中)
- 如果需要动态调整某任务优先级,用
ZADD ... XX更新 score,但要注意:这会改变它在队列中的位置,可能打断正在处理的任务流
Lua 脚本上线前必须验证的三个边界
线上出问题往往不是逻辑错,而是边界没兜住。这三个点不测,脚本一压就崩:
- 空队列:脚本返回
nil,客户端必须判空,不能直接解包,否则报attempt to index a nil value - 超大 score:Redis
ZSETscore 是 double,超过2^53会精度丢失;你的时间戳乘 100 万后若接近9007199254740992,后续排序就乱 - 任务内容含特殊字符:如果 task ID 里有空格或逗号,
ZADD仍能存,但 Lua 中redis.call返回的 member 是原样字符串,客户端解析时别用简单split(' '),要用 Redis 协议解析器或严格 JSON 封装
真正麻烦的不是写对脚本,是让所有写入方遵守 score 编码规则、所有消费方正确处理 nil 和长整数——协议比代码更难统一。










