xadd/xreadgroup被选用是因为其原生支持消息持久化、多消费者协作与显式ack,而list无消费状态跟踪、pub/sub无持久化保障。

为什么不用 List 或 Pub/Sub,而选 XADD/XREADGROUP
因为 List 没有消费状态跟踪,RPOP 一执行消息就没了,消费者崩了就丢;Pub/Sub 更惨,PUBLISH 后没人在线就彻底消失,连 Redis 持久化都救不了。而 XADD + XREADGROUP 是唯一原生支持「消息持久化 + 多消费者协作 + 显式 ACK」的 Redis 方案。
- 消息 ID 自带时间戳,天然有序,适合订单、支付等强顺序场景
-
XREADGROUP调用后不自动确认,必须显式XACK,否则消息会留在 Pending Entries List(PEL)里,宕机恢复后可重试 - 一个 Stream 可建多个消费者组,比如
orders流同时供「通知组」「风控组」「对账组」独立消费,互不干扰 - 注意:
XREAD是广播式读取,所有客户端都能拿到同一条消息;真要解耦,必须用XREADGROUP+ 消费者组
创建消费者组时 $ 和 0 的区别必须搞清
这是上线后消息“收不到”或“重复消费”的最高发原因。
-
XGROUP CREATE orders notify_group $ MKSTREAM:从创建时刻起的新消息才投递,历史消息全部跳过 -
XGROUP CREATE orders notify_group 0 MKSTREAM:从 Stream 开头第一条消息开始消费,适合补数据或灰度迁移 - 误用
$后发现没消息?别急着删组——用XINFO GROUPS orders查看当前组的last-delivered-id,再用XRANGE orders <id> + COUNT 1</id>确认消息是否存在 - 生产环境建议默认用
$,避免冷启动时刷出大量旧事件冲击下游
如何防止 Stream 无限膨胀导致内存爆掉
Stream 不设上限就是定时炸弹,尤其日志类事件每秒几百条,一周就能吃光 Redis 内存。
- 最稳妥是写入时加
MAXLEN ~ N,例如XADD events MAXLEN ~ 10000 *,波浪线表示“近似裁剪”,Redis 会定期清理旧消息,性能比精确裁剪高得多 - 不要用
MAXLEN N(无波浪线),它每次插入都强制检查并删除,QPS 高时延迟飙升 - 如果业务要求保留全部消息(如审计日志),改用
XTRIM定期清理,但必须搭配监控:用XINFO STREAM events查length和first-entry,触发告警阈值(如长度 > 50w) - 注意:
MAXLEN对已存在的 Stream 无效,只对后续XADD生效;老数据得靠XTRIM
Spring Boot 中监听 Stream 的坑:别直接用 RedisMessageListenerContainer
那个是为 Pub/Sub 设计的,硬套 Stream 会漏消息、不支持 ACK、无法绑定消费者组。
- 必须用
StreamMessageListenerContainer,它是 Spring Data Redis 2.2+ 才提供的专用容器 - 注册监听器时,
StreamOffset.fromStart()对应0,StreamOffset.latest()对应$,别写反 - 消费逻辑里务必调用
acknowledge(),否则消息永远卡在 PEL;异常时别吞掉,要抛出让容器触发重试(默认 3 次) - 示例关键行:
container.receive(ConsumerOptions.builder().groupId("notify").consumerId("inst-1").build(), StreamOffset.latest("events"))
最容易被忽略的是消费者组名称的全局唯一性——不同服务如果共用同一个 Stream 和同名 group,会互相干扰位点。微服务环境下,建议 group 名带上服务名前缀,比如 order-service-notify。










