Redis Pub/Sub延迟不在服务端而在客户端,因其仅瞬间广播、不缓冲不重试;延迟主因是客户端listen()阻塞、处理慢或连接混用,高并发时需多连接横向扩展,延时消息应改用ZSET+调度器方案。

Redis Pub/Sub 本身不解决海量消息推送延迟,它只负责“瞬间广播”——消息一发,立刻推给所有在线订阅者;但若订阅者处理慢、网络卡、或并发高,延迟就出现在客户端侧,而非 Redis 服务端。
为什么 Pub/Sub 的延迟不是 Redis 的问题
Pub/Sub 的延迟感知通常来自订阅端:连接数暴涨时 TCP 队列积压、客户端 listen() 处理不及时、反序列化或业务逻辑阻塞等。Redis 服务端在 PUBLISH 返回前已完成所有投递,不排队、不缓冲、不重试。
- 你看到的“延迟”,90% 是订阅客户端没来得及从 socket 读取或没及时处理
message字段 -
pubsub numsub channel可查当前订阅数,但无法反映客户端消费速度 - 没有“消息堆积”概念——离线期间发布的消息永远丢失,不存在 backlog
单个订阅连接扛不住高吞吐?别硬撑,拆
一个 PubSub 连接本质是单线程事件循环,每秒处理几百到几千条消息已是常态瓶颈。真要扛万级 QPS,必须横向扩展订阅端。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 用多个独立连接订阅同一 channel(不是多个线程共用一个连接)
- 每个连接绑定专属线程/协程,避免互相阻塞
- Spring Data Redis 中不要复用同一个
RedisTemplate实例做 pub/sub,改用专用RedisConnectionFactory+ 独立MessageListenerContainer - 注意客户端库的 buffer 设置(如 jedis 的
client.setTimeoutInfinite()或 lettuce 的timeoutOptions),防止因超时中断导致消息漏收
ZSET + 调度器才是海量延时消息的正解
如果你实际想问的是“如何让大量消息不立刻发、而是按计划时间发”,那 Pub/Sub 根本不是目标组件——它连“存”都不支持。此时必须切换方案:
- 把消息体和触发时间戳写入
ZADD delay:queue 1717023600000 "msg:order_123",score 为毫秒时间戳 - 用独立调度进程(非 Pub/Sub 客户端)轮询
ZRANGEBYSCORE delay:queue -inf (1717023600000,配合EVAL原子取出并删除 - 取出后调用
PUBLISH notify:channel ...—— 这一步才真正进入 Pub/Sub 流程,且是可控节奏的 - 调度器需支持失败回滚:PUBLISH 失败时,把消息写回 ZSET 并增加 backoff score(如 +5000ms),避免雪崩重试
别碰 keyspace notifications 做延时任务
监听 __keyevent@0__:expired 看似简单,但线上几乎不可靠:
- Redis 不保证过期事件 100% 投递,AOF rewrite、主从切换、内存淘汰都可能丢事件
- 事件只带 key 名,你需要额外维护
key → message映射表,引入一致性风险 - 事件通知是“尽力而为”,无法 ack,无法重试,无法排序
- Redis 8.2.3 虽修复了 RCE 漏洞,但未改善 keyspace notification 的投递稳定性
真正需要可靠延时,ZSET 方案虽需多一层调度,但可控、可监控、可降级;而依赖过期事件,等于把关键路径交给不确定性。

















