一般的lettuce配置示例
@Bean
public LettuceConnectionFactory lettuceConnectionFactory() {
GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
genericObjectPoolConfig.setMaxIdle(maxIdle);
genericObjectPoolConfig.setMinIdle(minIdle);
genericObjectPoolConfig.setMaxTotal(maxActive);
genericObjectPoolConfig.setMaxWaitMillis(maxWait); // 获取连接最大等待时间
genericObjectPoolConfig.setTimeBetweenEvictionRunsMillis(100); // 连接驱逐时间,如果默认或者-1则不开启链接池
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setDatabase(database);
redisStandaloneConfiguration.setHostName(host);
redisStandaloneConfiguration.setPort(port);
redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
.commandTimeout(Duration.ofMillis(timeout))
.shutdownTimeout(Duration.ofMillis(shutDownTimeout))
.clientResources(ClientResources.builder().build())
.poolConfig(genericObjectPoolConfig)
.build();
LettuceConnectionFactory factory = new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfig);
// factory.setShareNativeConnection(true); // 是否共享连接 默认 是true
// factory.setValidateConnection(false); // 每次获取连接是否检验连接 false
return factory;
}
lettuce配置示例
以下配置一般情况下需要调整的
@Bean
public ClientResources clientResources() {
return DefaultClientResources.builder()
// lettuce 提供的指标注册器
.commandLatencyRecorder(new MicrometerCommandLatencyRecorder(, MicrometerOptions.builder()
.enable()
// 记录那些命令 如果为空标识所有
.enabledCommands(CommandType.GET)
// 自定义标签
.tags(ta)
.histogram()
.maxLatency()
// 设置发出的百分位数 也就是 0.50, 0.90, 0.95, 0.99, 0.999
.targetPercentiles()
.build()))
// 自己可以实现
.commandLatencyRecorder(new CommandLatencyRecorder() {
@Override
public void recordCommandLatency(SocketAddress local, SocketAddress remote, ProtocolKeyword commandType, long firstResponseLatency, long completionLatency) {
log.info("local:{} remote:{} commandType:{} 首次响应时间:{} 整体响应时间:{}", local.toString(),remote.toString(),commandType.name(),firstResponseLatency,completionLatency);
}
})
// 收集指标的间隔
.commandLatencyPublisherOptions(DefaultEventPublisherOptions.builder().eventEmitInterval(Duration.ofSeconds(1)).build())
.build();
}
@Bean
public LettuceConnectionFactory lettuceConnectionFactory() {
GenericObjectPoolConfig<?> genericObjectPoolConfig = new GenericObjectPoolConfig<>();
genericObjectPoolConfig.setMaxIdle(10);
genericObjectPoolConfig.setMinIdle(1);
genericObjectPoolConfig.setMaxTotal(50);
genericObjectPoolConfig.setMaxWait(Duration.ofMillis(100)); // 获取连接最大等待时间
genericObjectPoolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(1000)); // 连接驱逐时间,如果默认或者-1则不开启链接池(配置的连接池无效)
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setDatabase(0);
redisStandaloneConfiguration.setHostName("host");
redisStandaloneConfiguration.setPort("port");
redisStandaloneConfiguration.setPassword(RedisPassword.of(""));
LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
.commandTimeout(Duration.ofMillis(100))
.shutdownTimeout(Duration.ofMillis(100))
.clientOptions(ClientOptions.builder().
socketOptions(SocketOptions.builder()
// tcp 连接创建超时时间时间
.connectTimeout(Duration.ofMillis(500))
// keepalive 有助于维持tcp连接 关于keepalive 可看 https://blog.csdn.net/yaomingyang/article/details/134344402
.keepAlive(SocketOptions.KeepAliveOptions.builder()
// 两次keep-alive之间的间隔
.interval(Duration.ofSeconds(5))
// 连接空闲多久开始keep-alive
.idle(Duration.ofSeconds(5))
// keep-alive多少次之后断开连接
.count(3)
// 开启
.enable()
.build()) // tcp keepAlive
.tcpNoDelay(false) // tcp 拥塞控制,会增加网络吞吐量,但可能会增加数据延迟 TCP Nagle
.publishOnScheduler(true) // 在使用单链接时候做大量请求时应该开启
// tcp 客户端超时时间,在使用keepAlive的时候需要配置 https://zhuanlan.zhihu.com/p/687078252
.tcpUserTimeout(SocketOptions.TcpUserTimeoutOptions.builder()
// 当tcp空闲时一定时间时,redis服务端会主动断开链接,但是断开链接没有使用四次挥手操作(而是RST),这个时候操作系统会重试(一般操作系统配置时间和次数较长),此时lettuce 不会恢复连接,6.3.0之后lettuce 增加了这个选项,可以通过此选项配置降低重试时间(之前的版本调整操作系统设置或者调整keepalive来控制吧)
.tcpUserTimeout(Duration.ofSeconds(60))
.enable(false)
.build()
)
.build())
.autoReconnect(true) // TCP连接断开是否自动重新连接
// .decodeBufferPolicy(DecodeBufferPolicies.ratio(3)) // 响应解码阶段后从响应聚合bufferCommandHandler. decode(ChannelHandlerContext ctx, ByteBuf buffer)中丢弃字节以回收内存的方法的策略。
// .disconnectedBehavior(ClientOptions.DisconnectedBehavior.DEFAULT) // 连接处于断开连接状态时 如果进行 命令调用(业务请求) 时应当怎么做 DEFAULT: 在开启自动重连时接受命令,否则拒绝命令
// .pingBeforeActivateConnection(true) // 在创建连接时是是否执行轻量级 PING 连接握手
// .protocolVersion(ProtocolVersion.RESP3) // redis 协议版本
// .requestQueueSize(Integer.MAX_VALUE) // 请求队列
// .readOnlyCommands() // 只读命令配置
.timeoutOptions(TimeoutOptions.enabled(Duration.ofMillis(500)))
// .scriptCharset() // lua 脚本编码
// .sslOptions() // ssl配置
.build())
.clientResources(clientResources())
.poolConfig(genericObjectPoolConfig)
.build();
LettuceConnectionFactory factory = new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfig);
factory.setShareNativeConnection(false);
return factory;
}
关于lettuce 连接池和命令的监控
命令监控
使用 ClientResources的commandLatencyRecorder注册并设置 commandLatencyPublisherOptions并设置指标收集事件的间隔
连接池监控
如果使用连接池的话,那么spring 会使用 LettucePoolingConnectionProvider
管理连接,
@Override
public <T extends StatefulConnection<?, ?>> T getConnection(Class<T> connectionType) {
GenericObjectPool<StatefulConnection<?, ?>> pool = pools.computeIfAbsent(connectionType, poolType -> {
return ConnectionPoolSupport.createGenericObjectPool(() -> connectionProvider.getConnection(connectionType),
poolConfig, false);
});
try {
StatefulConnection<?, ?> connection = pool.borrowObject();
poolRef.put(connection, pool);
return connectionType.cast(connection);
} catch (Exception ex) {
throw new PoolException("Could not get a resource from the pool", ex);
}
}
参照非async 来看,他通过pools 关了了多种类型的连接,并使用 computeIfAbsent 去创建连接池,进入 ConnectionPoolSupport.createGenericObjectPool
方法 线程池在这里被new出来
GenericObjectPool<T> pool = new GenericObjectPool<T>(new RedisPooledObjectFactory<T>(connectionSupplier), config)
这里他是传递了对象池config的,而GenericObjectPool 是有jmx 监控的,所以我们只需要配置jmx就可以监控了
但是一般jmx监控时无法导出到 promethues 或者是micrometer中的,所以我们需要将JMX 手动转化为micrometer指标.可以定时将JMX 准换为micrometer 指标
对象池一般关注的指标为,如果需要监控其他数据使用jconsole 查看,或者使用JMX 相关接口查看
"MaxIdle" 最大空闲数量 最多允许多少个空闲对象数量,当大与时会把对象驱逐,
"MinIdle" 最小空闲数量 最小允许多少个空闲对象,但是是在不违反MaxTotal的情况下,
"MaxTotal" 最大数量 池最大不会超过此数量,
"NumIdle" 当前空闲数量,
"NumActive" 当前使用数量,
对象池数量 = NumActive + NumIdle
链接池配置推荐
最大活跃连接数(max-active):定义了连接池中允许的最大并发连接数。默认值为8,推荐值大于CPU核数乘以2,通常为(CPU * 2) + 2。
最大空闲连接数(max-idle):定义了连接池中的最大空闲连接数。默认值为8,推荐值为CPU核数乘以2。
最小空闲连接数(min-idle):定义了连接池中的最小空闲连接数。默认值为0,推荐值为0。
最大等待时间(max-wait):定义了连接用完时,新的请求等待时间(秒或毫秒)。超过该时间会抛出异常,默认值为-1(无限制),推荐值为5s。
驱逐空闲连接的时间间隔(time-between-eviction-runs):定义了驱逐空闲连接的运行间隔时间(毫秒),默认值和推荐值未明确给出。
关闭超时时间(shutdown-timeout):定义了关闭连接池时的超时时间(毫秒),默认值和推荐值未明确给出。
也可以使用期望 qps 法计算, 如期望redis 每秒 1000 个请求 ,平均情况下 10ms 返回,那么 总需时间为 10000ms 需要在1s内完成,那么 需要 10000 / 1000 = 10 个连接 在这种情况下 max-idle 推荐 10 max-active 推荐为 超时时间所计算的大小 或者 p99耗时所计算的大小
关于spring boot 中lettuce 连接池和单连接 的说明
lettuce 是线程安全的,所以一般情况下单链接就可以
配置连接池的情况
如 redis 服务端(redis协议兼容,如 codis,twemproxy,garnet等 )单链接请求打到一个代理,可能出现问题
大数据量请求的时候 如 有大量mget pipline ,事务 等操作时
tcp 原因 一个tcp 连接,可能发生拥塞,丢包等情况,丢包会影响不相关的请求,并且可能导致后续请求严重堵塞和排队
如果配置连接池 一定要配置pool. TimeBetweenEvictionRuns 和 设置 factory.ShareNativeConnection = false
只设置 TimeBetweenEvictionRuns 会创建多个连接,但是使用时只有一个链接在使用 , 设置不共享链接后才会使用其他的
单链接 不应该配置 ShareNativeConnection = false 在没有上述问题需要配置连接池的情况下 大量请求开启 publishOnScheduler(true) 即可
关于长时间没有请求导致服务端断开的不回复连的问题
当tcp空闲时一定时间时,redis服务端会主动断开链接,但是断开链接没有使用四次挥手操作(而是RST),这个时候操作系统会重试(一般操作系统配置时间和次数较长),此时lettuce 不会恢复连接,6.3.0之后lettuce 增加了这个选项 tcpUserTimeout ,可以通过此选项配置降低重试时间(之前的版本调整操作系统设置或者调整keepalive来控制吧)
关于lettuce ClientResources 的说明
先看这个类的注释
策略接口,用于提供所有基础结构构建,如环境设置和线程池,以便客户端可以正确使用它。
ClientResources 如果在客户端创建之外创建,则可以在多个客户端实例之间共享的.
如果一个应用有多个lettuce客户端,那么多个lettuce是应该共享这个类的(个人认为逻辑上不同的客户端不使用同一个ClientResources,比如 连接redis1 和 redis2两个redis服务端,或者同一redis服务端但是应用在使用过程中需要分开的情况),需要共享的那就是redis连接池的情况了
ClientResources 实现是有状态的,必须在 shutdown() 它们不再使用之后。
因为clientResources 中有线程池,所以shutdown后就不能在使用了,一般在配置时不会将其shutdown,但是看springboot lettuce配置的源码是将其shutdown的
ClientResources 特别提供:
EventLoopGroupProvider 获得特定的 EventLoopGroups netty 的事件循环线程池
EventExecutorGroup 执行内部计算任务
Timer 用于调度
EventBus 用于客户端事件调度
EventPublisherOptions
CommandLatencyCollector 以收集延迟详细信息。需要 HdrHistogram 库。 在监控的时候需要
DnsResolver 以收集延迟详细信息。需要 LatencyUtils 库。
重新连接 Delay。
Tracing 以跟踪 Redis 命令。 在监控的时候需要
所以clientResources是与Lettuce的性能、并发和事件处理相关
线程池或者线程组相关配置占据客户端资源配置的大部分(EventLoopGroups和EventExecutorGroup)这些线程池或者线程组是连接程序的基础组件。
除非特别熟悉或者花长时间去测试调整下面提到的参数,否则在没有经验的前提下凭直觉修改默认值,有可能会踩坑。
常见问题:
lettuce连接池无效问题
https://blog.csdn.net/qq_41921994/article/details/109627736
java.io.IOException: 远程主机强迫关闭了一个现有的连接。或者控制台报连接超时异常
https://blog.csdn.net/hkl_Forever/article/details/128195713
参考文档
https://blog.csdn.net/zhangzehai2234/article/details/134586357
https://zhuanlan.zhihu.com/p/610671250
https://blog.csdn.net/u012549626/article/details/111506304
https://baijiahao.baidu.com/s?id=1792589548259444033&wfr=spider&for=pc