redis-benchmark 不适用于 Redis 发布订阅压测,因其仅支持单发布者单订阅者模型,无法模拟多订阅者、多频道的真实场景,且忽略输出缓冲区限制、连接数瓶颈及广播开销。

Redis发布订阅的压测不能只靠 redis-benchmark,它不支持多订阅者、多频道场景,直接用会严重低估真实瓶颈。
为什么 redis-benchmark 对 pub/sub 压测无效
redis-benchmark -t pubsub 仅模拟单个发布者 + 单个订阅者循环收发,完全忽略实际系统中「一对多」「多对多」的拓扑结构。真实业务里一个频道常有几十甚至上百个活跃订阅者,每个连接都占用输出缓冲区,而 redis-benchmark 根本不创建多个连接,测出来的吞吐量毫无参考价值。
常见错误现象:redis-benchmark -t pubsub -n 100000 跑出 80w+ ops/s,但线上一开 50 个 Python 订阅线程,Redis 就开始断连、丢消息。
- 它不控制 client-output-buffer-limit,无法触发缓冲区溢出的真实失败路径
- 不区分 channel 绑定与消息分发延迟,掩盖了 pubsub 内部广播逻辑的开销
- 所有消息都在同一个 TCP 连接上复用,绕过了连接数、文件描述符等真实限制
用 Python 多线程脚本模拟真实订阅负载
必须自己构造「N 个独立连接 + M 个频道 + 持续 listen」的模型,才能暴露缓冲区、CPU 分发、网络堆积等问题。核心是让每个线程持有独立的 redis.Redis 实例和 pubsub 对象。
示例关键片段:
import redis
import threading
<p>def subscribe_to_channel(channel_name):
r = redis.Redis(host='127.0.0.1', port=6379, decode_responses=True)
pubsub = r.pubsub()
pubsub.subscribe(channel_name)</p><h1>必须显式调用 listen() 启动阻塞循环</h1><pre class="brush:php;toolbar:false;">for msg in pubsub.listen():
if msg['type'] == 'message':
# 真实业务中这里要处理反序列化、路由等,不能空转
pass
启动 30 个线程,订阅 30 个不同 channel(避免单 channel 竞争)
for i in range(30): t = threading.Thread(target=subscribe_to_channel, args=(f'news:{i}',)) t.daemon = True t.start()
- 每个线程必须用新连接(不能共用
r实例),否则共享 socket 会互相干扰 - 务必设
daemon=True,否则主线程退出时子线程卡住进程无法结束 - 不要在
listen()循环里做耗时操作(如写 DB、HTTP 请求),否则积压消息导致缓冲区暴涨 - 观察
redis-cli info clients中的connected_clients和client_longest_output_list,后者超过 1000 就危险
定位 pub/sub 性能瓶颈的关键参数
压测过程中紧盯三个配置项,它们直接决定是否断连、丢消息、延迟飙升:
-
client-output-buffer-limit pubsub 32mb 8mb 60:这是最常被忽略的致命项。32MB 是硬上限,一旦某个订阅者消费慢,缓冲区涨到 32MB,Redis 立刻KILL它;8MB 是软限,持续 60 秒超限也会被踢。压测时若频繁看到Client closed connection due to output buffer overflow错误,就是它在起作用 -
hz 10:serverCron 每秒执行次数,影响 pubsub 消息分发频率。值太低(如默认 10)会导致高并发下消息堆积在内部队列,P99 延迟跳变;压测中可临时CONFIG SET hz 20观察是否改善,但注意 CPU 开销会上升 -
tcp-keepalive 300:防止中间网络设备(如 NAT、防火墙)静默断连。压测时间长了出现「订阅者突然不收消息但连接还显示 active」,大概率是这个没设或设得太小
修改后必须用 service redis restart 生效,CONFIG SET 对 client-output-buffer-limit 无效。
缓冲区溢出后的表现与验证方法
当订阅端消费跟不上发布速度,你不会立刻看到错误日志,而是经历几个阶段:
- 第一阶段:
client_longest_output_list持续 > 500,used_memory_rss缓慢上涨,但客户端仍正常收消息 - 第二阶段:某连接缓冲区触达 8MB 软限并维持超 60 秒 → Redis 主动
CLIENT KILL,日志出现Connection with output buffer too big - 第三阶段:被 kill 的客户端重连后重新 subscribe,但若消费逻辑没优化,几秒内又重复上述过程,形成雪崩
验证是否真因缓冲区问题,可在压测中实时运行:
redis-cli --stat | grep -E "(client|mem)"
或直接查特定客户端:
redis-cli CLIENT LIST | grep "omem="
omem= 值超过 8388608(即 8MB)就已踩软限红线,此时必须优化消费逻辑或扩容订阅节点。
真正难调的不是参数数字,而是「消费端处理能力」与「发布节奏」之间的匹配——缓冲区只是最后的保险丝,断了说明前面的链路已经失衡。别只盯着 Redis 配置,先确认你的消息处理函数是不是在同步调用 HTTP 或锁表。











