用go-redis/v8写入Stream需用XAddArgs指定string类型键值对、ID设为"*"或自定义时间戳格式,并带超时context;读取应优先用XReadGroup配合ACK和XPending兜底,避免XRead重复消费;务必正确初始化consumer group并校验XInfo结果。

如何用 github.com/go-redis/redis/v8 写入 Stream 消息
Go 里写入 Redis Stream 最直接的方式是调用 XAdd,但别直接传空 ID —— 默认生成的 ID 虽然合法,但如果你需要严格时序或幂等重放,得自己控制 ID 格式(比如 "1678886400000-0" 这种毫秒时间戳+序列号)。XAdd 返回的是实际写入的 ID,务必检查是否为空或报错。
常见错误:传了 nil 作为 ctx,导致超时不可控;或者把 map[string]interface{} 直接塞进去,而 go-redis 要求 key/value 都是 string 类型。
- 用
redis.XAddArgs构造参数,显式指定Stream名、Values(map[string]string) - ID 设为
"*"让 Redis 自增,或设为"0-1"强制前置(仅调试用,生产慎用) - 务必带超时
context.WithTimeout(ctx, 5*time.Second),否则网络卡住会 hang 住 goroutine
怎么从 Stream 拉取新消息(非阻塞 + 阻塞两种场景)
XRead 和 XReadGroup 是两条路:XRead 适合单消费者临时读取,XReadGroup 才是真正支持多消费者、ACK、失败重试的方案。别误以为 XRead 加个 count 就能当队列用——它不标记已读,重复拉会反复拿到旧消息。
典型翻车点:用 XRead 实现“监听”,却没维护上次读到的 ID,每次从头读,CPU 白耗;或者在 XReadGroup 里忘了创建 consumer group,Redis 直接返回空,也不报错。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 首次读
XReadGroup,Start参数用"0"表示从头开始;后续用上一次返回的 ID +"-1"(如"1678886400000-0"→"1678886400000-1")避免漏消息 - 阻塞读必须设
Block: 5000(单位毫秒),否则就是纯轮询 - consumer group 名和 consumer 名不能含空格或斜杠,否则
redis.XReadGroup静默失败
消息处理完后怎么确认消费成功(ACK)
只有用了 XReadGroup,才需要调 XAck。没调 XAck 的消息会一直留在 PENDING 列表里,下次 XReadGroup 还可能被分发给你(取决于 Idle 设置)。但 XAck 不是原子操作——如果 ACK 成功但业务逻辑崩溃,消息就丢了;反之如果业务成功但 ACK 失败,消息会重复。
所以得配合 XPending 定期扫 PENDING 消息做兜底,而不是依赖 ACK 一锤定音。
-
XAck的参数顺序是:stream name、group name、message IDs...,ID 必须是XReadGroup返回的原始字符串,不能截断或格式化 - 不要在 defer 里统一 ACK —— 如果 handler panic,defer 不执行,消息卡死
- 建议把 ACK 放在业务逻辑最后一步,并用
redis.Watch或外部 DB 记录状态,实现至少一次语义
为什么 XInfo 查不到 consumer group 或 pending 数量
因为 XInfo 命令本身不报错,但返回结果结构复杂,go-redis 的 XInfoGroups 和 XPendingExt 返回的是自定义 struct,字段名和 Redis 原生响应不完全对应。最常踩的坑是:group 确实存在,但 XInfoGroups 返回空 slice —— 很可能是 stream 名拼错了,或压根没执行过 XGroupCreate。
另一个隐形陷阱:XPending 默认只查当前 consumer 的 pending 消息,不是全 group 的;要查全部得用 XPendingExt 并设 Consumer: ""。
- 先用
redis-cli手动执行XINFO GROUPS mystream确认 group 存在,再查 Go 代码 -
XInfoStream返回的Length是当前未被任何 group 消费的消息数,不是总消息量 - stream 数据过期靠
MAXLEN截断,Redis 不支持 TTL,别指望EXPIRE生效

















