背景
lettuce默认提供了线程安全的链接,大部分场景下,使用单个链接即可满足需求,但是在大并发场景,还是需要连接池,lettuce默认提供了 异步连接池配置,本文对Redis 的单链接,和连接池场景进行测试,比对性能差异和适用场景。
测试条件
Redis cluser 3主3从 6节点,
本机Intel 8核处理器,lettuce版本
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>5.3.7.RELEASE</version>
</dependency>
单链接(不用连接池)
做法可以参考我上篇文章《java 服务基于lettuce实现redis sentinel/cluster的高可用连接》
直接上测试代码
@Autowired
private RedisCluster redis;
@Autowired
protected ThreadPoolTaskScheduler taskScheduler;
private AtomicLong count = new AtomicLong(0);
private Long lastCount =0L;
@PostConstruct
public void initService() {
LOGGER.info("initService hello");
for(int i=0;i<10;i++)
{
taskScheduler.schedule(new Runnable() {
@Override
public void run() {
while(true) {
try {
long start = System.currentTimeMillis();
String clusterRe = redis.getCMD().set("testCluster", RandomUtils.nextInt()+"-hello",
SetArgs.Builder.ex(10));
start = System.currentTimeMillis();
String value = redis.get("testCluster");
count.incrementAndGet();
}catch (Exception e) {
LOGGER.error("redisSentinel test error:" + e.getMessage()); }
}
}
},new Date(System.currentTimeMillis() + 10000));
}
//10秒后开始每秒统计
taskScheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
long nowCount = count.get();
LOGGER.info("one cluster QPS " + (nowCount -lastCount )+"/s"; }
lastCount = nowCount;
},new Date(System.currentTimeMillis() + 10000),1000);
}
设置大于1个线程,逐步增加到60线程数,循环操作,qps不稳定,并且持续下降,本机8核cpu被打满
可以看到,一开始有20万QPS,但是持续下降,一直掉到 1000以下,猜测是由于竞争激烈导致
设置60个线程情况下循环操作,每次操作停1ms,减少竞争,qps稳定在3.2w,本机8核cpu维持在40%左右
结论
不使用连接池的情况下异步操作,多线程竞争激烈情况下,qps会持续下降,模拟真实业务场景(停顿1ms )场景,可以维持3.2w QPS
使用连接池
lettuce的连接池也是异步的,构建方法如下
/**
* 异步连接池
*/
private BoundedAsyncPool<StatefulRedisClusterConnection<String, String>> asyncPool;
// cluster client 创建异步连接池 clusterClient 需要提前创建好
asyncPool = AsyncConnectionPoolSupport.createBoundedObjectPool(
() -> clusterClient.connectAsync(StringCodec.UTF8).thenApply(connect->{
connect.setTimeout(Duration.ofSeconds(5));
connect.setAutoFlushCommands(false);
connect.setReadFrom(ReadFrom.MASTER_PREFERRED);
return connect;
}), BoundedPoolConfig.builder().maxTotal(getPoolMaxTotal()).maxIdle(getPoolMaxIdle()).minIdle(getPoolMinIdle()).build());
// 单节点,或者哨兵的client 创建异步连接池 asyncClient
RedisClient asyncClient = RedisClient.create();
//TODO 这里需要初始化uri,需要区别单节点和哨兵
RedisURI redisURI;
//单节点带有密码时,会有无法正确识别协议问题,需要手工指定 https://github.com/lettuce-io/lettuce-core/issues/1543
if (SINGLE.equals(type) && StringUtils.isNotBlank(password)) {
asyncClient.setOptions(ClientOptions.builder().build());
}
asyncPool = AsyncConnectionPoolSupport.createBoundedObjectPool(
() -> MasterReplica.connectAsync(asyncClient, StringCodec.UTF8, redisURI).thenApply(connect -> {
connect.setAutoFlushCommands(false);
connect.setReadFrom(ReadFrom.MASTER_PREFERRED);
return connect;
}), BoundedPoolConfig.builder().maxTotal(getPoolMaxTotal()).maxIdle(getPoolMaxIdle()).minIdle(getPoolMinIdle()).build());
这里最大最小连接数靠参数注入
poolMaxTotal #总连接数
poolMinIdle #最小空闲连接数
poolMaxIdle #最大空闲的连接数
连接数测试
这里设置最大链接数30,尝试获取50个链接且不释放
可以看到,获取到30个后,就无法获取
吞吐量测试
一开始实例化10个链接,用60线程循环操作的情况下,qps稳定在12w左右,本机8核cpu被打满
然后慢慢增加连接数,加到12个链接,执行一段时间后开始出现重连现象,QPS变得不稳定
结论
连接数建议设置成<12
总结
1、lettuce 连接池配置使用poolMaxTotal、poolMaxIdle配置,建议设置成一样,为了避免短连接的增加而引起资源上的消耗
2、poolMaxTotal建议设置成<12(默认设置8,自定义设置不建议超过12链接)
在12个链接下,redis cluster 多线程吞吐可以达到12万QPS
3、高并发场景(单节点 对redis 请求超过 2.5wQPS )下建议使用连接池,单个连接适用于普通业务