PUB/SUB扛不住瞬时流量高峰,因其不缓冲、不落盘、不排队,消息仅推给当前在线订阅者;PUBLISH同步阻塞主线程,任一客户端卡顿即拖慢整体,且SUBSCRIBE独占连接易触达maxclients上限;真正削峰需改用STREAM做持久化缓冲,PUB/SUB仅作轻量广播。

为什么PUB/SUB扛不住瞬时流量高峰
PUB/SUB不是消息队列,它不缓冲、不落盘、不排队,消息只推给当前在线的订阅者。瞬时高峰来临时,PUBLISH会同步阻塞 Redis 主线程,挨个往每个订阅连接写数据;只要其中某个客户端网络慢或处理卡顿,整个发布就卡住。实测向 5000 个订阅者发一条消息,PUBLISH延迟可能突破 200ms,上游业务线程直接被拖死。
更麻烦的是,每个 SUBSCRIBE 都独占一个连接,maxclients 默认 10000,但活跃订阅连接撑不过 2000 就可能触发 ERR max number of clients reached。PHP 反复调用 subscribe()、Java 客户端未复用连接,都会快速打满连接数。
真正能削峰的结构:用STREAM替代PUB/SUB做主干
想削峰填谷,必须换掉 PUB/SUB 的核心链路——改用 STREAM 做持久化缓冲,再让 PUB/SUB 退居轻量广播位。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 订单创建后执行:
XADD orders * order_id 123 status created - 后台 worker 启动时建组:
XGROUP CREATE orders wg1 $ MKSTREAM(注意必须带MKSTREAM,否则读不到历史) - worker 拉取并确认:
XREADGROUP GROUP wg1 c1 COUNT 10 STREAMS orders > - 处理完业务逻辑后,只发一次轻量通知:
PUBLISH notify:order_created {"order_id":"123"}
这样,STREAM 承担积压、重放、分摊负载;PUBLISH 只负责秒级广播,不耦合业务逻辑,也不拖慢主线程。
订阅端如何避免连接爆炸
别为每个频道起一个新连接——Linux 文件描述符和 Redis maxclients 都扛不住。正确做法是复用连接,批量订阅:
- 用单个连接执行:
SUBSCRIBE ch1 ch2 ch3 ... ch200(一次最多订阅几百个频道,视业务聚合粒度而定) - 避免
PSUBSCRIBE alert.*这类通配符,它要 O(N) 扫描所有订阅模式;优先合并频道,比如把alert:error和alert:warn统一成alert:all - Node.js 推荐
ioredis,Python 推荐redis-py的PubSub+run_in_thread(blocking=False),Go 推荐go-redis/v9的Subscribe返回*PubSub对象(内部已用 goroutine 分发)
关键细节容易被忽略
很多人以为只要换成 STREAM 就万事大吉,但漏掉几个点就会出问题:
-
XGROUP CREATE时没加MKSTREAM,导致新组无法读历史消息,等于白搭 - 不巡检
XPENDING,pending list 里消息卡住超时,既不重试也不告警 - 对
STREAM设置了MAXLEN过小(比如MAXLEN 1000),高峰期消息被自动裁剪,下游永远收不全 - 把
PUBLISH放进核心交易链路(如下单回调里直接发),等于又绕回原路,削峰失效

















