lettuce redis客户端

一般的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

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值