DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

XREAD 多 Stream 漏读

XREAD每次传$会重选当前末尾。为每条Stream保存具体处理位置,保留未返回和超时后的游标,明确重启与裁剪边界。

用 XREAD BLOCK ... STREAMS stream1 stream2 $ $ 监听两条 Stream,第一条不断收到消息,第二条却很久没有结果。先检查下一轮请求:是不是只更新返回过消息的 Stream ID,让另一条继续使用 $?这样会反复跳过它在两次请求之间写入的数据。

这篇讨论普通 XREAD 的读取位置。它没有消费组的 PEL,也不通过 XACK 推进位置;XDEL 后 XPENDING 仍有消息属于另一种消费流程。

$ 每次都重新表示当前末尾

$ 表示从这次请求时的末尾开始,只等待之后新增的消息。它适合首次开始观察、不需要历史数据的场景,不是可永久保留的游标。

例如第一次等待被 stream1 的新增消息唤醒,客户端拿到它的 ID。此后 stream2 写入一条消息,但客户端还没发出下一次请求。如果下一次仍为 stream2 传 $,这条消息已经属于请求开始前的历史,于是被跳过。

问题不仅在“每轮都传两个 $”。下面这种一个具体 ID、一个 $ 的混合请求,也会让第二条 Stream 重复从当前末尾开始:

XREAD BLOCK 5000 STREAMS stream1 stream2 1700000000000-3 $

没有数据返回、等待超时后继续传 $,同样会重新选起点。交换 Stream 顺序或降低等待时间,不能替代保存各自的读取位置;也不能仅凭一条 Stream 收得少,就认定当前 Redis 存在公平调度故障。

开始前为每条 Stream 取得具体位置

如果要读取现有全部历史,每条都从 0-0 开始。如果业务只需要启动后的新数据,先查询每条 Stream 的实际末尾 ID,例如通过 XINFO STREAM 的 last-generated-id,再带这些具体 ID 开始读取。空 Stream 或尚不存在的键,要按业务约定处理,可用 0-0 作为随后新建流的起点;类型错误、权限错误与连接失败不能当作空流。

分别查询多条 Stream 不构成一个共同的原子快照,起点也就可能对应不同瞬间。需要跨流统一截止点时,必须另行设计同步与持久记录,不能把依次查询写成全局一致性保证。

以下 ID 仅演示参数排列:先列全部键,再按相同顺序列各自的位置。实际应替换为独立查得、或已处理后保存的 ID。

XREAD BLOCK 5000 COUNT 100 STREAMS stream1 stream2 1700000000000-3 1700000000100-0

不要把 stream1 的 ID 随意复制给 stream2。Stream 可以使用显式指定的 ID,Redis TIME 的当前时间也不能普遍代替每条流的真实位置。

只推进已成功处理的数据

客户端保存一份“Stream 名称 → 最后处理 ID”的映射。请求返回哪些 Stream,就逐条处理其消息,并在处理成功后推进对应位置;没有返回的 Stream 保留原位置,超时也保留全部位置。

不要收到一批数据就先把游标改成批末,再处理业务。中途失败时,重试可能从尚未处理的数据之后开始。按业务完成顺序保存位置,恢复时允许重读并用业务唯一标识处理幂等;业务结果与位置不能原子提交时,要明确这段故障窗口。

进程重启后需要续读,就将每条 Stream 的位置持久保存。重新使用 $ 等于主动放弃重启前积压,而不是恢复消费;已经被裁剪或删除的内容,也不会因保存了 ID 自动回来。

在隔离 Stream 验证:先只向第一条写入,第一次返回后再向第二条写入,然后继续读取。检查第二条的实际消息 ID 被处理,超时后位置未重置,重启后按持久位置续读。验收依据是消息 ID 与业务结果的对应关系,不是两条 Stream 的返回次数恰好相同。