Redis Pub/Sub 无法保证消息不丢失,必须改用 STREAM;因 Pub/Sub 是纯内存广播模型,离线消息直接丢弃,而 STREAM 提供持久化、可回溯、需 ACK 的可靠消息机制。

Redis 的 Pub/Sub 无法保证消息不丢失,这是设计使然,不是配置或运维能解决的;必须换用 STREAM 才能获得持久化、可回溯、有确认机制的消息能力。
为什么 Pub/Sub 一定会丢消息
Pub/Sub 是纯内存、无状态的广播模型:订阅者离线期间发布的所有消息直接被丢弃,服务端不存、不重发、不记录 offset。哪怕只断开 100ms,就可能错过关键事件。
常见错误现象包括:
- 消费者重启后收不到断连期间的消息
- 用
PSUBSCRIBE监听通配符频道,但某次网络抖动后彻底“失联”且无法补漏 - 多个消费者共用一个
SUBSCRIBE连接,其中一个处理慢导致缓冲区溢出,READ ERROR后连接中断,消息永久丢失
用 STREAM 替代的最小可行写法
Redis 5.0+ 的 STREAM 提供了类似 Kafka 的日志语义:消息写入即持久化,消费者按 ID 拉取,支持多组消费(CONSUMER GROUP),失败可重试。
实操建议:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 生产者改用
XADD mystream * field value,其中*表示自动生成递增 ID(形如1712345678901-0) - 消费者首次启动时用
XREADGROUP GROUP mygroup consumer1 COUNT 10 STREAMS mystream >,>表示只读新消息 - 若需回溯,把
>换成具体 ID,例如0-0从头开始读,或1712345678000-0从某个时间点开始 - 处理成功后必须调用
XACK mystream mygroup <id>,否则该消息会持续出现在未确认队列中
STREAM 消费组里的坑:消费者名不是可选的
很多人以为 consumer1 只是个标识,其实它在 Redis 内部是强状态——同一组下不同 consumer 名对应独立的 PENDING 列表和最后读取位置(LAST_DELIVERED_ID)。如果代码里随机生成 consumer 名(比如加个时间戳),等于每次都在创建新消费者,旧消息永远不被 ACK,堆积越来越多。
正确做法:
- consumer 名应稳定,例如用部署实例的 hostname 或容器 ID
- 避免在代码里拼接
Math.random()、Date.now()等动态值 - 可通过
XINFO CONSUMERS mystream mygroup查看当前活跃 consumer 及 pending 数量
性能与兼容性提醒
STREAM 不是零成本升级:单条 XADD 比 PUBLISH 多约 15% CPU 开销,且消息体超过 4KB 时,XREADGROUP 的响应延迟会上升明显。
注意事项:
- 不要把大文件 Base64 后塞进
STREAM;应存对象到 OSS/S3,STREAM只传 URL 和元数据 - 老版本客户端(如 jedis 3.0 以下)不支持
XREADGROUP的 block 模式,需升级或手动轮询 - 集群模式下,
STREAMkey 必须有 hash tag(如mystream{shard1}),否则XGROUP命令会报CROSSSLOT错误
真正容易被忽略的是:即使用了 STREAM,如果没配 XTRIM 或没启用 MAXLEN,日志会无限增长,磁盘迟早打满。上线前务必确认是否设置了自动截断,比如 XADD mystream MAXLEN ~ 1000 * field value。

















