用LPush+BRPop可实现生产级FIFO队列:LPush左端入队,BRPop右端阻塞出队,支持多消费者负载均衡;需注意BRPop返回值取result[1]、消息JSON序列化、禁用Pub/Sub替代队列、延时任务须用ZSet+Lua原子迁移、严格管理context超时与client生命周期。

用 LPush + BRPop 实现最简可靠队列
Redis 原生没有“消息队列”类型,但用 List 配合阻塞弹出命令就能做出生产可用的队列。核心就是:LPush 从左端入队,BRPop 从右端阻塞出队——天然 FIFO,且消费者不会轮询空耗 CPU。
-
BRPop的 timeout 设为0表示无限等待,比轮询RPop省资源;设为正数则可配合超时重试逻辑 - 注意
BRPop返回的是[]string,其中result[0]是 key 名,result[1]才是真实消息体,直接取result[1]否则 panic - 如果多个消费者同时监听同一队列,Redis 自动负载分发,无需额外协调
- 消息内容建议 JSON 序列化,避免二进制或特殊字符导致解析失败
为什么别用 Publish/Subscribe 当队列
Pub/Sub 是广播模型,不是队列:消息发出去就没了,没消费者就丢;不保证顺序;不支持重放、确认、重试。它适合通知类场景(如配置变更广播),不适合任务分发。
- 一旦订阅者断连,期间发布的消息全部丢失,无持久化机制
- 无法实现“至少一次”语义,也不能做 ACK 确认,没法知道某条消息是否被处理成功
- 如果你看到代码里用
Subscribe接收任务并调用Process(),那它本质上是个单点通知通道,不是队列
延时任务必须用 Sorted Set + Lua 脚本迁移
想让某条消息 5 秒后才被消费?List 不支持延迟。正确做法是把消息写进 ZSet,score 设为 Unix 时间戳,再用定时器或独立 worker 轮询到期消息——但迁移过程必须原子。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 不能先
ZRangeByScore再ZRem再LPush:三步非原子,多 worker 可能重复迁移同一条消息 - 必须用 Lua 脚本封装这三步操作,通过
EVAL提交,Redis 单线程执行保障原子性 - 脚本内用
zrangebyscore ... WITHSCORES LIMIT 0 100控制每次迁移数量,防大 key 阻塞 - 不要依赖客户端时间做判断,所有时间戳统一用
redis.call("time")获取服务端时间
连接和上下文管理最容易漏掉的两件事
很多线上队列卡死或连接泄漏,不是逻辑错,而是 context 和 client 生命周期没理清。
- 每个 Redis 操作都要传入带超时的
context.Context,比如ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second),否则网络卡住会永久 hang 住 goroutine -
redis.Client是线程安全的,全局复用一个实例即可,不要每次NewClient;但要用完defer client.Close()关闭底层连接池(尤其测试时) - 用
client.Ping(ctx)做初始化连通性检查,别等第一次LPush才报错
真正难的不是写对几行 LPush 和 BRPop,而是处理消息重复、消费者崩溃、网络分区、时钟漂移这些边界。ZSet 延迟队列的 Lua 脚本、context 超时控制、JSON 错误 fallback —— 这些细节堆起来,才是生产级队列的门槛。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










