redis pub/sub压测不能用redis data set插件,因其仅支持同步命令,而publish/subscribe是长连接、事件驱动模型,subscribe会阻塞jedis连接导致超时;必须改用jsr223 sampler配合手动线程管理,分离订阅与发布逻辑,并通过latch确保订阅就绪后再发布。

Redis Pub/Sub 压测不能用 Redis Data Set 插件
因为 Redis Data Set 插件只支持同步命令(如 SET、GET、INCR),底层基于阻塞式 Jedis 连接,而 PUBLISH/SUBSCRIBE 是长连接、事件驱动模型——一旦调用 SUBSCRIBE,连接就进入监听状态,不再返回响应,JMeter 会一直等待超时,最终报 java.net.SocketTimeoutException 或卡死。
所以必须绕过插件,改用 JSR223 Sampler + 手动管理连接生命周期。关键点是:每个线程独占一个 Jedis 实例,且 SUBSCRIBE 必须在独立线程中异步执行,主采样器不能阻塞。
- 不要在
JSR223 Sampler中直接写jedis.subscribe(...)—— 它会阻塞整个线程,导致吞吐量归零 - 必须用
new Thread(...).start()启动监听,同时用vars.put("sub_thread", thread)持有引用,便于后续清理 -
PUBLISH可以复用同一连接(或另建短连接),但要注意连接池未启用时频繁新建Jedis实例会导致TIME_WAIT爆满
如何让每个线程正确启动独立的 SUBSCRIBE 监听
核心是分离「订阅启动」和「发布行为」:一个线程只负责监听,另一个(或多个)线程负责发消息。否则单线程既 SUBSCRIBE 又 PUBLISH 会因连接被占用而失败。
示例(Groovy,放在 JSR223 Sampler 中):
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
import redis.clients.jedis.Jedis
import java.util.concurrent.CountDownLatch
def host = "127.0.0.1"
def port = 6379
def channel = "test:channel"
// 启动监听线程(后台运行)
def latch = new CountDownLatch(1)
def subThread = new Thread({
def jedis = new Jedis(host, port)
try {
jedis.subscribe(new JedisPubSub() {
@Override
void onMessage(String channel, String message) {
// 可选:记录日志或统计接收数
log.info("Received: ${message} on ${channel}")
}
@Override void onSubscribe(String channel, int subscribedChannels) { latch.countDown() }
}, channel)
} catch (Exception e) {
log.error("Subscribe failed", e)
} finally {
jedis.close()
}
})
subThread.start()
// 等待订阅就绪(避免 publish 早于 subscribe)
latch.await(2, TimeUnit.SECONDS)
// 将监听线程存入变量,供 tearDown 线程组终止
vars.put("sub_thread", subThread)
- 必须加
latch.await(),否则PUBLISH可能发到尚未完成SUBSCRIBE的连接上,消息丢失(Redis Pub/Sub 不保证投递) - 不要在
onMessage中做耗时操作(如写文件、调 HTTP),否则积压消息会触发READONLY错误或连接中断 - 监听线程无法通过 JMeter GUI 实时看到“响应”,需靠日志或外部监控(如
redis-cli monitor)验证是否生效
高并发下 PUBLISH 的性能瓶颈与规避方式
真实压测中,PUBLISH 的吞吐量往往受限于网络往返和 Redis 单线程处理能力,而非客户端。当线程数 > 50 且每秒发布 > 1k 条时,容易出现:
-
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool—— 连接池耗尽,需显式配置JedisPoolConfig - 大量
java.io.IOException: Broken pipe—— 客户端发太快,Redis 内核缓冲区溢出,需调小tcp-wmem或加Thread.sleep()限速 - 消息重复或丢失 —— Pub/Sub 本身无 ACK,不适用金融级场景;压测时应关注「接收率」而非「发送率」
推荐做法:
- 用连接池替代每次新建
Jedis:new JedisPool(new JedisPoolConfig(), host, port),并设setMaxTotal(200) -
PUBLISH采样器中加随机延迟:Thread.sleep(org.apache.jmeter.util.JMeterUtils.getRandomInt(0, 5)),缓解突发冲击 - 避免在单个
JSR223 Sampler中混合SUBSCRIBE和PUBLISH—— 分拆为两个线程组:一个只SUBSCRIBE(设置循环次数为 1),另一个只PUBLISH(高并发循环)
如何验证 Pub/Sub 压测是否真实生效
不能只看 JMeter 的 Aggregate Report 中的 Throughput,因为 SUBSCRIBE 成功后无返回值,JMeter 默认标记为成功;必须外挂验证手段:
- 在 Redis 服务端执行
redis-cli --stat,观察pubsub_channels和pubsub_patterns是否随线程增长而上升 - 用另一台机器跑
redis-cli -h $HOST -p $PORT SUBSCRIBE test:channel,手动确认能否实时收到消息 - 在
onMessage回调里用props.put("recv_count", props.get("recv_count") as int + 1)统计接收总数,压测结束后用BeanShell Sampler输出全局计数 - 注意:JMeter 的
View Results Tree对 Pub/Sub 无效 —— 它只显示采样器返回值,而监听线程不返回任何东西
最易忽略的是连接泄漏:每个 SUBSCRIBE 线程都持有一个长连接,若没在 tearDown Thread Group 中显式 subThread.interrupt() 或关闭 Jedis,压测结束后 Redis 仍维持数百个空闲连接,下次压测可能直接失败。










