登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  Redis

Redis Stream 消费组怎样使用 NOACK

来源:17golang原创

时间:2026-10-15 00:54:23 498浏览 收藏

Redis Stream 消费组在 XREADGROUP 中加上 NOACK,可以让本次读取的新消息不进入 Pending Entries List(PEL)。典型写法是 XREADGROUP GROUP 组名 消费者名 COUNT 100 NOACK STREAMS 流名 >。代价是:它把读取视作已确认,不等业务处理完成;消费者随后失败时,这些消息没有该组的 Pending 记录可供接管。

如果只是允许偶发缺失的实时展示,我会考虑它;如果是必须补偿的订单处理,我会保留普通消费与成功后的 XACK。这里用一个假设的实时展示任务解释选择,不声称亲历了故障或完成了压测。

官方命令地址:https://redis.io/docs/latest/commands/xreadgroup/

我会先把处理成功单独计数

假设有一个活动看板,把消息交给多个消费者做短时展示。旧消息很快失去展示价值,但是否允许丢失仍然需要业务明确决定。我不会先问“能快多少”,而会先区分三个数量:消费端收到多少条、业务真正完成多少条、组内还有多少条 Pending。

NOACK 最容易造成的误判,是 Pending 变成零后就认为任务全部成功。对我而言,零 Pending 只说明没有相应待确认记录,不等于每条消息的业务动作已完成。测量基线应保留业务成功计数,而不是只保留 Redis 返回数量。

计数观察对象不能代替什么
读取条数客户端实际收到的消息业务完成条数
成功条数业务动作完成的消息 ID全部曾读取的消息
Pending 条数PEL 中的待确认记录NOACK 模式下的失败条数

NOACK 省下的是待确认记录

普通消费组会跟踪已投递但未确认的消息,业务成功后用 XACK 移除相应 PEL 记录。NOACK 则跳过新消息的这份待确认记录。因此它改变的是确认与恢复约定,不是把业务处理变成 Redis 内部的原子操作。

在这个区别里,我会把 Stream 消息本体、PEL 与业务处理结果分开看。NOACK 不是删除 Stream 条目的命令;即便消息仍在 Stream 中,也不代表业务知道哪些已成功、哪些需要重做。保留的条目可以按 ID 范围查询,另做重放则需要自己定义范围、完成记录和去重规则,不能当作 Pending 接管的等价替代。

有关确认职责可参照官方 XACK 说明:https://redis.io/docs/latest/commands/xack/;原始条目的范围查询见:https://redis.io/docs/latest/commands/xrange/。

Stream 消息本体、消费组、PEL 与 NOACK 的静态关系
图1:确认记录结构图。普通读取对应 PEL 记录,NOACK 不为新读取消息建立这份记录;两种方式都不代表业务成功。不是数据库截图或运行结果。

把 NOACK 放到新消息读取中

下面是 shell 中的命令片段,假设流与组已经存在。注意把 > 引起来,避免 shell 将它当作输出重定向。

# 读取该组尚未投递的新消息,最多等待 1000 毫秒;不建立新消息的 PEL 记录
redis-cli XREADGROUP GROUP display_group worker_a COUNT 100 BLOCK 1000 NOACK STREAMS display_events '>'

GROUP 后面依次是组名和消费者名;组内不同工作实例应使用不同消费者名。COUNT 是每条流的数量上限,并不是必须凑满的批量大小。BLOCK 控制无可读消息时的等待,1000 的单位是毫秒,不是业务处理超时。

这里没有使用 CLAIM 扩展,> 表示读取该组尚未投递的新消息。若改用 0 或其他具体 ID,含义变成读取当前消费者已有的 Pending;此时 NOACK 与 BLOCK 被忽略。不要靠 NOACK STREAMS 流名 0 清空历史待确认记录。以上范围以官方 XREADGROUP 规则为准。

用两个组比较同一批消息

下面的示例供读者在独立测试实例执行,未在本机运行。前提是 demo:noack:events 这个键此前不存在,没有其他生产者、消费者或裁剪操作;两个组使用相同起点,从而能分别消费同样的两条消息。固定 ID 只用于教学,不表示真实生成时间。

# 创建两个独立组;起点 0 允许读取从头保留的消息,MKSTREAM 创建空流
redis-cli XGROUP CREATE demo:noack:events fast_group 0 MKSTREAM
redis-cli XGROUP CREATE demo:noack:events tracked_group 0

# 在新建的空流中写入两条示例事件,固定 ID 便于后续引用
redis-cli XADD demo:noack:events 1-0 event_key screen_a value 17
redis-cli XADD demo:noack:events 2-0 event_key screen_b value 29

# 同一批消息由不同组分别读取;fast_group 不建立 Pending
redis-cli XREADGROUP GROUP fast_group fast_a COUNT 2 NOACK STREAMS demo:noack:events '>'
redis-cli XREADGROUP GROUP tracked_group tracked_a COUNT 2 STREAMS demo:noack:events '>'

# 比较各组 Pending,并单独查看仍保留的消息本体
redis-cli XPENDING demo:noack:events fast_group
redis-cli XPENDING demo:noack:events tracked_group
redis-cli XRANGE demo:noack:events - + COUNT 2

# 仅当对应业务处理成功时,普通消费组才确认这些 ID
redis-cli XACK demo:noack:events tracked_group 1-0 2-0

按命令语义预期,两个读取都可得到两条事件;fast_group 的 Pending 数量为 0,tracked_group 在确认前为 2。两条原始消息仍可通过 XRANGE 查询;最后的 XACK 移除普通组的待确认记录。这些是基于前提的预期,不是实测输出,也不是吞吐基准。

普通组最后的 XACK 演示确认动作的位置,不能照抄到业务失败分支。示例对象留在测试实例中,不自动清理其他键。若创建时返回 BUSYGROUP,说明组已存在,应停止并换未使用的测试对象,不能继续拿旧组状态作对照。

组起点与创建行为参照:https://redis.io/docs/latest/commands/xgroup-create/;Pending 查询参照:https://redis.io/docs/latest/commands/xpending/。

性能收益要和缺失代价一起量

少维护一份记录、少一次确认动作,可能减少开销,但我不会由此直接写出“性能提升一倍”。普通消费也可以批量 XACK,网络、业务耗时和批量大小都可能掩盖差异。真正的对比需要相同消息内容、消费者数量、并发、网络条件与持久化设置。

如果由我安排实验,会使用两轮隔离负载:一轮普通读取并在业务成功后批量确认,一轮 NOACK。每轮固定消息数量,记录相同观测窗口;除了读取速率,还要记录业务成功速率、端到端 P95 延迟、客户端和 Redis CPU,以及按实际成功 ID 计算的缺失情况。

指标普通读取+确认NOACK
业务成功条数/秒需实测需实测
端到端 P95 延迟需实测需实测
CPU 与资源开销需实测需实测
读取后进程退出的未完成 ID观察 Pending 与恢复结果观察业务缺失与外部补偿

故障实验也只应在测试环境进行:让消费者读取后、业务完成前退出,再比较未完成消息的可定位性。普通组虽然保留 Pending,仍要实现接管、重试和业务幂等;NOACK 则没有这些新消息的 PEL 依据。我的判断会同时看速度变化和缺失是否符合约定,而不是只挑吞吐好看的数字。

读取计数、成功计数、Pending 计数与业务 ID 的职责关系
图2:观测指标结构图。读取数量和业务成功数量必须分开记录,Pending 不能替代成功计数;图中没有性能数值,也不暗示已完成压测。

需要补偿时我会保留确认机制

对临时展示、允许明确范围内缺失的非关键数据,NOACK 可以是一个简化选择。但如果处理失败必须知道“哪一条没完成”,我会保留 PEL 与显式确认。业务要求不能因为 Pending 看起来更干净而被悄悄改变。

切换选项还要记住:NOACK 是本次读取的选择,不是永久关闭消费组的确认能力。之前积累的 Pending 不会被它自动清掉;后续不带 NOACK 的新消息仍按普通确认约定处理。混用前必须让监控能识别哪些消息采用了哪种处理策略。

常见问题

NOACK 的消息还需要 XACK 吗?对于该方式新读取且未加入 PEL 的消息,不需要用 XACK 移除不存在的记录;业务成功仍应自行记录。

Pending 为零能说明没有失败吗?不能。尤其在 NOACK 下,它没有收集这批消息的业务失败情况。

消息仍在 Stream 里,为什么不能直接补偿?原始事件不等于完成状态。重放前还需确定缺失范围与去重约定,不能把所有历史事件无差别重复处理。

我的取舍是:先明确可容忍的缺失,再衡量省下的开销。NOACK 解决的是“不需要维护待确认记录”,不是“可靠消费无需确认”。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>