实时广播与保留回放具有不同的投递和恢复要求。

Redis 发布订阅与流是两样工具

Redis 发布订阅与流解决的是不同的投递问题。发布订阅面向当前生效的订阅广播,不保留供以后回放的消息;流保存条目,读者可以在它们仍被保留时重新访问。选择哪种机制,首先要回答谁必须收到事件,以及消费者缺席期间应该发生什么。 先确定每个事件交给谁 对于后续状态可以替代的实时看板更新,只向在线用户广播也许已经足够。但在假设新听众已经到场之前,应等待订阅确认。重连恢复的是未来投递,不是错过的发布。PUBLISH 返回正数,表示按 Redis 命令语义统计到了接收者,不等于应用确认业务处理已经完成。 流有两种不同的消费方式。普通 XREAD 让独立读者分别指定自己的起始 ID,双方可以收到同一份保留历史。它既不自动分担工作,也不替每个连接保管检查点。同一个消费组中的 XREADGROUP 则在具名消费者之间分配新投递,并在未启用 NOACK 时记录待处理条目。 不同消费组允许账单、分析等独立工作流在同一条流上分别推进。组内增加成员是在分担工作,新建一组则是增加一套投递历史。消费者名字不同、工作进程变多,都不承诺负载均分或业务按序完成。应先确定需要的是广播、独立回放还是竞争处理,再选择对应的读取命令,不能把三种模式混为一谈。 保留窗口不是持久化设置 XADD 把数据保存在 Redis 里,并不自动保证每个已接受的写入都能熬过崩溃或故障切换。RDB/AOF 设置、复制行为、键淘汰及恢复操作共同决定持久性。发布订阅也不会因为启用了持久化,就突然拥有可回放的日志。因此,把两者概括成时效性与持久性的取舍,会掩盖各自的真实契约。 MAXLEN 删除的是最旧的条目,不是裁掉最新队尾、同时让早期历史一直保留。近似裁剪可能留下额外记录,精确裁剪则遵循指定数量;两者都不提供无限回放。默认裁剪还可能删除待处理引用对应的载荷,因此应围绕允许的最长停机时间与恢复后的追赶过程设计保留策略和容量。 保留积压也有运行成本。需要控制增长,分别观察未读滞后和待处理任务,并定义无法正常处理的消息如何处置。发布订阅虽然不保存历史,却仍使用连接输出缓冲;慢听众触达相应限制后可能被断开。两种机制都不能让内存规划消失,只是占用与丢失发生的位置不同。 坏掉的样子 用发布订阅派发必须执行的任务,订阅者缺席时就会失去投递机会。能够重新加载权威状态的看板可能接受这点,必须执行的财务操作通常不能接受。合适的替代品可以是保留消息的流,也可以是其他持久队列,但恢复语义必须围绕业务操作设计,不能从产品名字直接推导出来。 流若没有恢复循环,会出现另一种失败:已经投递但未确认的条目停留在待处理列表,普通 > 读取却继续处理新消息。处理前确认可能丢工作,处理后确认则允许重试时重复副作用。如果重复执行不可接受,稳定的事件标识和幂等处理仍是必需的,不能仅靠建组和追加消息解决。 把同一事件分别 PUBLISH 和 XADD,也不是原子双写协议。执行若在两次操作之间中断,可能只有一边成功。下面的实验在隔离进程中有意比较两种机制,不是在推荐用这一串命令构成生产送达保证。比较实验里的执行顺序,不应被直接提升为跨系统一致性方案。 快速验证 需要 POSIX 环境、C 编译器、make、curl、tar,以及 Python 3.10 或更新版本。以下步骤已在 macOS、Python 3.14.4、Redis 8.10.1 和 redis-py 8.1.0 上执行。它们使用 libc 构建 Redis 核心,并把客户端安装到临时虚拟环境,不构建可选模块,也不做系统级安装。 ...

二月 6, 2028 · 3 分钟 · XB Field Notes
一条支持回放与消费组、只在后端追加的日志。

Redis 流按顺序保管事件供消费

Redis 流把条目保存在有序 ID 下,读者可以重新读取尚未删除的历史。与发布订阅不同,生产者追加时读者不必在线。这个差别提供的是回放能力,不是无条件持久性:条目删除、保留限制及持久化设置仍决定延迟或故障后究竟还剩下什么。 区分写入顺序与事件时间 XADD 使用 * 时生成包含毫秒部分和序列部分的 ID。在同一条流内,ID 单调递增,Redis 必要时会补偿本机时钟回退。显式提供的 ID 也必须大于这条流最后生成的 ID。比较时应比较两个数值部分,不能把任意长度的字符串字典序当成数值顺序。 ID 排定的是写入该流的顺序,不会建立多条独立流之间的全局顺序,也不证明外部事件真正发生的时间。需要业务时间时,应单独保存相应字段。两次普通 XADD 即使字段完全相同,也会创建两个不同条目:自动 ID 标识记录,并不自动识别重复业务操作。 保存最后处理的位置 XRANGE 默认包含起止两端。把已经处理过的最后一个 ID 作为起点,会再次返回该条目;在边界前加左圆括号可以排除它。XREAD 则返回严格大于指定 ID 的条目。配合 COUNT 分批读取时,应保存最后返回的 ID,下次从这个位置继续。 普通 XREAD 不会替每个读者维护服务端偏移,也不会移除已投递条目。独立读者可以读取同一范围,重复请求也会重复返回仍被保留的数据。检查点由应用负责:副作用之前保存位置,故障后可能跳过尚未执行的工作;副作用之后保存,故障后可能重复已经完成的工作。不能接受这类差异时,需要把位置推进与幂等处理协调起来。 美元符号 ID 表示从 Redis 处理这次调用时的流末端之后开始。它适合首次进入实时订阅,不适合恢复已有进度。每次响应后继续用 $,会跳过两次调用之间追加的条目。收到数据后应使用真实的最后一个 ID。消费组另行增加投递与确认状态;XACK 并不是普通 XREAD 的偏移提交命令。 ...

五月 6, 2027 · 3 分钟 · XB Field Notes
一条没有存储、只把消息发给当前听众的广播频道。

发布订阅把消息同时递给每一位订阅者

Redis 发布订阅面向消息发布时已经生效的订阅广播,不会为断线的听众保留收件箱。它适合可以被后续状态替代的实时通知,但 PUBLISH 返回正数,并不证明某个工作进程完成了任务,也不表示离线消费者回来后能够补领消息。 订阅是一次握手 SUBSCRIBE 会先返回订阅确认,连接随后才能收到后续发布的消息。协调发布者与新听众时,应等待这份确认。仅仅把订阅请求发送出去还不够:Redis 处理请求之前,另一个连接就可能已经发布。重新连接和订阅只恢复未来的投递,不会填补之前的历史空白。 在持续在线的连接上,听众按发布顺序接收消息。这里的顺序反映 Redis 的处理顺序,不是多个独立生产者携带的业务时间戳。即使接收有序,把事件分发给并行处理器后,完成顺序仍可能改变。因此,声称发布订阅完全没有顺序是错误的;把有序接收扩大为业务副作用也必然有序,同样不成立。 多个听众各自收到广播,并不是竞争弹出同一个列表元素。频道订阅与匹配的模式订阅是两条独立投递路径。同时订阅 events 和 event*,一次发布就可能产生一份 message 和一份 pmessage;多个模式重叠也会如此。最多一次描述的是没有重试保证,不是跨订阅路径的业务去重承诺。 严格解释返回值 在本次验证的单机版本中,PUBLISH 分别统计频道和模式匹配的投递次数,因此两个客户端连接可以得到返回值三。固定版本的源码确实在两条路径分别递增计数。对于 Redis Cluster,官方说明计数只包含连接到发布节点的听众,因此它也不是整个集群的受众总数。 该响应不会等待处理器提交数据库事务、完成界面更新或确认业务完成。返回零只说明当时没有计入匹配接收者,返回正数不能证明处理成功。如果应用需要识别重叠订阅或生产者重复尝试造成的重复事件,应在消息中放入稳定的事件标识,不能用返回的数字替代业务确认。 坏掉的样子 订阅中断期间发布的消息会被错过。重连循环无法请求某个偏移之后的内容,因为发布订阅既无保留日志,也无回放游标。用于缓存失效时,一次通知丢失可能让旧值持续存在,直到另一个刷新机制介入;用于账单或任务派发时,则可能直接丢掉必须执行的工作。需要回放就应选择保留消息的队列或流,并明确恢复与持久化策略。 慢听众并不是一变慢就被随机丢掉几次读取。Redis 会缓存待发送数据,客户端输出缓冲触达配置的硬限制,或持续超过软限制后,连接可能被关闭。这限制了客户端缓冲占用,却会造成无法补读的订阅空白。应观察断连重连频率及缓冲占用,限制回调执行的工作量;调大缓冲并不等于获得送达保证。 快速验证 需要 POSIX 环境、C 编译器、make、curl、tar,以及 Python 3.10 或更新版本。以下步骤已在 macOS、Python 3.14.4、Redis 8.10.1 和 redis-py 8.1.0 上执行。它们使用 libc 构建 Redis 核心,并把客户端安装到临时虚拟环境,不构建可选模块,也不做系统级安装。 ...

二月 17, 2027 · 3 分钟 · XB Field Notes