redis pub/sub 本身不防重,重复消费是设计使然,必须在业务层用 setnx+ex 做幂等校验;消息id由发布方生成,key 建议 seen:order:{msg_id},过期时间设为1小时至24小时,并注意redis故障导致去重失效的风险。

Redis Pub/Sub 本身不防重,重复消费是设计使然,不是故障。必须在业务层加幂等校验,否则无论怎么调订阅逻辑都无效。
为什么 SUBSCRIBE 和 PSUBSCRIBE 都会重复收到消息
Redis 的发布订阅是纯广播机制:PUBLISH 一次,所有当前在线的 SUBSCRIBE 客户端(包括同一服务的多个进程、多台机器)都会各自收到一份完整副本。没有 ACK、不记录消费状态、也不做去重。
- 一个订单服务启了 3 个实例监听
order.pay,每条支付消息就会被投递 3 次 -
PSUBSCRIBE order.*只是扩大匹配范围,不会减少投递次数;每个匹配上的客户端仍各收一份 - 网络抖动导致客户端断连重连后,会重新收到已发过的消息(因为 Redis 不维护 offset)
用 SETNX + EX 做轻量级幂等校验(推荐)
核心思路:消息 ID 必须由发布方生成并透传(如 uuid4()),消费者用该 ID 在 Redis 中原子写入标记,成功才执行业务逻辑。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 必须用
SET key value NX EX seconds(或setnx+expire),不能分开两步调用,否则并发下会漏判 - key 命名建议统一前缀,例如
seen:order:{msg_id},避免 key 冲突 - 过期时间按业务场景定,一般 1 小时到 24 小时足够;太短可能误重放,太长占内存
- 校验失败必须直接 return,不要抛异常、不重试、不 ACK——Pub/Sub 本就不支持这些
示例(Python redis-py):
if not redis_client.set(f"seen:{msg_id}", "1", nx=True, ex=3600):
return # 已处理过,跳过
# 执行下单、发通知等真实逻辑
注意 Redis 实例故障导致的去重失效风险
用 Redis 做幂等缓存,最大隐患不是性能,而是数据“意外丢失”:
- Redis 单点重启、主从切换、集群 slot 迁移,都可能导致
seen:{msg_id}记录消失 - 如果业务对“绝对不重复”要求极高(如支付扣款),仅靠
SET不够,需结合 DB 去重表或带持久化的分布式锁服务 - 用 Redis Cluster 时,确保
seen:{msg_id}的 key 落在同一个 slot(可通过{...}包裹 msg_id 强制哈希)
真正难的不是写对那几行 SETNX,而是想清楚:你的业务能容忍多少次“Redis 故障引发的误重放”,以及是否值得为它上更重的方案。










