redis 5.x及更早版本publish阻塞主线程,因其同步遍历订阅客户端并逐个调用addreply;6.0+改用异步分发,但需调大client-output-buffer-limit防断连;客户端listen()或subscribe()阻塞属使用不当,应轮询或独立线程处理。

Redis 5.x 及更早版本的 PUBLISH 会阻塞主线程
根本原因在于 publish 命令同步遍历所有订阅客户端,逐个调用 addReply 写入响应缓冲区。只要其中任意一个客户端网络慢、缓冲区满、或处于 CLIENT PAUSE 状态,整个 PUBLISH 就卡住——此时所有其他命令(包括 GET、SET)都会延迟飙升。
- 典型现象:在 Redis 5.0 中,用
redis-cli SUBSCRIBE ch后按Ctrl+Z挂起订阅端,再反复PUBLISH,第二次起延迟常跳到几百毫秒甚至秒级 - 验证方式:配合
LATENCY LATEST查看是否有command类型尖峰紧随PUBLISH出现;或观察INFO stats中total_commands_processed是否滞涨 - 注意:
INFO clients的connected_clients数不反映写入压力,不能用来判断阻塞风险
Redis 6.0+ 默认不阻塞,但需避开 client-output-buffer-limit 陷阱
6.0 起,Pub/Sub 消息分发已从主线程事件循环剥离,改由各客户端连接上下文异步完成。主线程只做轻量广播调度,PUBLISH 自身耗时稳定在 0.1–0.3ms(本地环回)。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 真正起作用的是内部连接状态机重构,不是
io-threads配置(它默认为 1,且不加速 Pub/Sub 分发) - 但若某个订阅客户端消费太慢,其输出缓冲区仍可能突破
client-output-buffer-limit pubsub设置,触发服务器强制断连——这不是阻塞,而是保护性熔断 - 默认值
32mb 8mb 60(软限 32MB / 硬限 8MB / 60 秒),高吞吐场景下极易被击穿,必须根据实际消息速率和消费能力调大
Python redis-py 的 listen() 方法会阻塞当前线程
pubsub.listen() 是个无限迭代器,调用即挂起当前线程,后续代码全部停摆。这不是 Redis 服务端问题,而是客户端使用方式错误。
- 正确做法是用
ps.get_message(ignore_subscribe_messages=True)主动轮询,配合time.sleep(0.01)控制 CPU 占用 - 必须先调
ps.subscribe('ch'),再调get_message(),否则始终返回None且无提示 - 连接中断时
get_message()抛ConnectionError,需在外层捕获并重建pubsub实例 - 切记:同一个
pubsub实例不能跨线程复用;若用 threading,子线程必须持有独立Redis()连接
Java Jedis 的 subscribe() 默认阻塞主线程
Jedis 的 subscribe() 是同步阻塞调用,直接在主线程执行会导致整个应用挂起。Lettuce 和 Redisson 也存在类似风险,取决于配置是否隔离了订阅连接。
- 最稳妥解法:将
subscribe()放进独立线程,并确保该线程使用专属连接(不要复用主线程的Jedis实例) - Spring Data Redis 推荐用
RedisMessageListenerContainer,它自动管理连接池与线程,避免手动处理 - Redisson 用户务必显式设置
setSubscriptionConnectionPoolSize(20),默认只有 1 个订阅连接,高并发下排队超时极常见 - 云环境还需注意:NAT 网关或防火墙常在空闲 60–300 秒后断开长连接,建议在客户端配
socketTimeout或启用心跳(如 Lettuce 的pingBeforeActivateConnection)
listen() 调用卡住了主循环,跟 Redis 版本毫无关系。










