
本文介绍一种基于 redis 有序集合(sorted set)与 lua 脚本协同实现的线程安全优先队列方案,支持按优先级降序出队、同优先级按入队时间升序(fifo)的语义,并兼容高并发场景。
本文介绍一种基于 redis 有序集合(sorted set)与 lua 脚本协同实现的线程安全优先队列方案,支持按优先级降序出队、同优先级按入队时间升序(fifo)的语义,并兼容高并发场景。
Redis 原生不提供优先队列(Priority Queue)数据结构,但可通过 有序集合(Sorted Set) + 精心设计的 score 编码规则 + 原子化 Lua 脚本 高效模拟。核心目标是:
✅ 优先级高的任务先出队(priority 越大,越优先);
✅ 优先级相同时,先入队的任务先出队(即“先到先服务”,FIFO);
✅ 所有操作(入队/出队)在多客户端并发下保持原子性与一致性。
✅ 正确的 Score 设计逻辑
关键在于将二维排序条件(priority, timestamp)编码为单个 score 值,且满足字典序等价于业务优先级顺序:
- 若仅用
score = priority * 10^10 + timestamp,则时间戳越大(越新)score 越大 →zpopmax会优先弹出最新入队的任务,违反 FIFO; - 正确做法是:对时间戳取反向映射,使更早的时间戳对应更大的 score(从而在
zpopmax中优先被选中):
-- push.lua
redis.replicate_commands()
local key = KEYS[1]
local task_id = ARGV[1]
local priority = tonumber(ARGV[2])
local t = redis.call("time")
local time_s = t[1] -- 秒级时间戳(已足够区分常规任务)
-- ✅ 关键修正:用 (MAX_TIME - time_s) 实现「时间越早,score 越大」
-- 这里 MAX_TIME 取 10^10(确保不溢出且覆盖合理时间范围)
local score = priority * 10000000000 + (10000000000 - time_s)
redis.call("zadd", key, score, task_id)
return 1? 为什么用
10000000000 - time_s?
因为zpopmax总是移除 score 最大 的元素。我们希望:
priority主序:值越大,score 越大 → 直接乘以大系数;time_s次序:时间越小(越早),越应优先 → 故用大常数减去time_s,使t=1717000000得到8299999999,而t=1717000001得到8299999998,自然满足早入队者 score 更高。
? 使用方式(命令行示例)
入队任务(执行 Lua 脚本):
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
# 将 task:1001 以优先级 5 入队 redis-cli --eval push.lua queue , task:1001 5 # 将 task:1002 以优先级 5 入队(稍晚几秒) sleep 1; redis-cli --eval push.lua queue , task:1002 5 # 将 task:1003 以优先级 8 入队(最高优先级) redis-cli --eval push.lua queue , task:1003 8
出队任务(原子获取并删除最高优先级任务):
# 返回形如 ["task:1003", "80000000000.0"] → 任务ID + score redis-cli zpopmax queue # 若需仅获取任务ID(忽略score),可配合管道或客户端解析
⚠️ 注意事项与最佳实践
-
时间精度:当前脚本使用
redis.time()[1](秒级)。如需毫秒级区分,可改用redis.call("time")的[2](微秒部分)或组合time()[1]..time()[2]后哈希处理,但需同步调整 score 计算逻辑; -
Lua 复制安全:
redis.replicate_commands()确保脚本在 Redis Cluster 或主从环境中正确复制; -
避免浮点误差:
zpopmax返回的 score 是字符串形式浮点数(如"80000000000.0"),客户端解析时建议转为整型或直接忽略(任务 ID 才是关键); -
空队列处理:
zpopmax在空 key 时返回(nil),应用层需判空; -
扩展性提示:若需支持延迟任务、取消任务或批量出队,可在 score 中嵌入更多字段(如状态位、TTL 偏移),或搭配
zremrangebyscore使用。
✅ 总结
Redis 有序集合结合定制化 score 编码与原子 Lua 脚本,是实现高性能、线程安全优先队列的成熟方案。相比客户端自行维护队列或轮询判断,该方法充分利用 Redis 单线程原子性,规避了 GET+ZADD+DEL 类操作的竞态风险。只要 score 设计符合「高优先级→高 score」「同优级早入→高 score」的单调映射,即可通过 zpopmax / zpopmin(取决于方向)稳定支撑千万级任务调度场景。

















