文/朱季谦

背景:最近在对一新开发Springboot系统做压测,发现刚开始压测时,可以正常对redis集群进行数据存取,但是暂停几分钟后,接着继续用jmeter进行压测时,发现redis就开始突然疯狂爆出异常提示:Command timed out after 6 second(s)......

  1 Caused by: io.lettuce.core.RedisCommandTimeoutException: Command timed out after 6 second(s)
2 at io.lettuce.core.ExceptionFactory.createTimeoutException(ExceptionFactory.java:51)
3 at io.lettuce.core.LettuceFutures.awaitOrCancel(LettuceFutures.java:114)
4 at io.lettuce.core.cluster.ClusterFutureSyncInvocationHandler.handleInvocation(ClusterFutureSyncInvocationHandler.java:123)
5 at io.lettuce.core.internal.AbstractInvocationHandler.invoke(AbstractInvocationHandler.java:80)
6 at com.sun.proxy.$Proxy134.mget(Unknown Source)
7 at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.mGet(LettuceStringCommands.java:119)
8 ... 15 common frames omitted

我急忙检查redis集群,发现集群里的各节点都一切正常,且cpu和内存使用率还不到百分之二十,看着这一切,我突然陷入漫长的沉思,到底是哪里出现问题......百度一番,发现不少人都出现过类似情况的,有人说把超时timeout设置更大一些就可以解决了。我按照这样的解决方法,把超时timeout的值设置到更大后,依然没有解决该超时问题。

其中,springboot操作redis的依赖包是——

  1 <dependency>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-starter-data-redis</artifactId>
4 </dependency>

集群配置——

  1 redis:
2 timeout: 6000ms
3 cluster:
4 nodes:
5 - xxx.xxx.x.xxx:6379
6 - xxx.xxx.x.xxx:6379
7 - xxx.xxx.x.xxx:6379
8 jedis:
9 pool:
10 max-active: 1000
11 max-idle: 10
12 min-idle: 5
13 max-wait: -1

点进spring-boot-starter-data-redis进去,发现里面包含了lettuce的依赖:

看到一些网友说,springboot1.x默认使用的是jedis,到了Springboot2.x就默认使用了lettuce。我们可以简单验证一下,在redis驱动加载配置类里,输出一下RedisConnectionFactory信息:

  1 @Configuration
2 @AutoConfigureAfter(RedisAutoConfiguration.class)
3 public class Configuration {
4 @Bean
5 public StringRedisTemplate redisTemplate(RedisConnectionFactory factory) {
6 log.info("测试打印驱动类型:"+factory);
7 }

打印输出——

测试打印驱动类型:org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory@74ee761e

可见,这里使用正是是lettuce驱动连接,因此,当把它换成以前用的比较多的jedis驱动连接时,就没有再出现这个Command timed out after 6 second(s)问题了。

  1 <dependency>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-starter-data-redis</artifactId>
4 <exclusions>
5 <exclusion>
6 <groupId>io.lettuce</groupId>
7 <artifactId>lettuce-core</artifactId>
8 </exclusion>
9 </exclusions>
10 </dependency>
11 <dependency>
12 <groupId>redis.clients</groupId>
13 <artifactId>jedis</artifactId>
14 </dependency>

那么问题来了,Springboot2.x是如何默认使用了lettuce,这得去研究下里面的部分代码。我们可以可进入到Springboot2.x自动装配模块的redis部分,其中有一个RedisAutoConfiguration类,其主要作用是对Springboot自动配置连接redis类:

  1 @Configuration(
2 proxyBeanMethods = false
3 )
4 @ConditionalOnClass({RedisOperations.class})
5 @EnableConfigurationProperties({RedisProperties.class})
6 @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
7 public class RedisAutoConfiguration {
8 public RedisAutoConfiguration() {
9 }
10 ......省略
11 }

这里只需要关注里面的一行注解:

  1
2 @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
3

这就意味着使用spring-boot-starter-data-redis依赖时,可自动导入lettuce和jedis两种驱动,按理来说,不会同时存在两种驱动,这样没有太大意义,因此,这里的先后顺序就很重要了,为什么这么说呢?

分别进入到LettuceConnectionConfiguration.class与JedisConnectionConfiguration.class当中,各自展示本文需要涉及到的核心代码:

  1 //LettuceConnectionConfiguration
2 @ConditionalOnClass({RedisClient.class})
3 class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
4 ......省略
5 @Bean
6 @ConditionalOnMissingBean({RedisConnectionFactory.class})
7 LettuceConnectionFactory redisConnectionFactory(ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers, ClientResources clientResources) throws UnknownHostException {
8 LettuceClientConfiguration clientConfig = this.getLettuceClientConfiguration(builderCustomizers, clientResources, this.getProperties().getLettuce().getPool());
9 return this.createLettuceConnectionFactory(clientConfig);
10 }
11 }
12 //JedisConnectionConfiguration
13 @ConditionalOnClass({GenericObjectPool.class, JedisConnection.class, Jedis.class})
14 class JedisConnectionConfiguration extends RedisConnectionConfiguration {
15 ......省略
16 @Bean
17 @ConditionalOnMissingBean({RedisConnectionFactory.class})
18 JedisConnectionFactory redisConnectionFactory(ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) throws UnknownHostException {
19 return this.createJedisConnectionFactory(builderCustomizers);
20 }
21 }
22

可见,LettuceConnectionConfiguration.class与JedisConnectionConfiguration.class当中都有一个相同的注解 @ConditionalOnMissingBean({RedisConnectionFactory.class}),这是说,假如RedisConnectionFactory这个bean已经被注册到容器里,那么与它相似的其他Bean就不会再被加载注册,简单点说,对LettuceConnectionConfiguration与JedisConnectionConfiguration各自加上 @ConditionalOnMissingBean({RedisConnectionFactory.class})注解,两者当中只能加载注册其中一个到容器里,另外一个就不会再进行加载注册。

那么,问题就来了,谁会先被注册呢?

这就回到了上面提到的一句,@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})这一句里的先后顺序很关键,LettuceConnectionConfiguration在前面,就意味着,LettuceConnectionConfiguration将会被注册。

可见,Springboot默认是使用lettuce来连接redis的。

当我们引入spring-boot-starter-data-redis依赖包时,其实就相当于引入lettuce包,这时就会使用lettuce驱动,若不想使用该默认的lettuce驱动,直接将lettuce依赖排除即可。

  1 <dependency>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-starter-data-redis</artifactId>
4 <exclusions>
5 <exclusion>
6 <groupId>io.lettuce</groupId>
7 <artifactId>lettuce-core</artifactId>
8 </exclusion>
9 </exclusions>
10 </dependency>

然后再引入jedis依赖——

  1 <dependency>
2 <groupId>redis.clients</groupId>
3 <artifactId>jedis</artifactId>
4 </dependency>

这样,在进行RedisAutoConfiguration的导入注解时,因为没有找到lettuce依赖,故而这注解@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})的第二个位置上的JedisConnectionConfiguration就有效了,就可以被注册到容器了,当做springboot操作redis的驱动。

lettuce与jedis两者有什么区别呢?

lettuce:底层是用netty实现,线程安全,默认只有一个实例。

jedis:可直连redis服务端,配合连接池使用,可增加物理连接。

根据异常提示找到出现错误的方法,在下列代码里的LettuceConverters.toBoolean(this.getConnection().zadd(key, score, value))——

  1 public Boolean zAdd(byte[] key, double score, byte[] value) {
2 Assert.notNull(key, "Key must not be null!");
3 Assert.notNull(value, "Value must not be null!");
4 ​
5 try {
6 if (this.isPipelined()) {
7 this.pipeline(this.connection.newLettuceResult(this.getAsyncConnection().zadd(key, score, value), LettuceConverters.longToBoolean()));
8 return null;
9 } else if (this.isQueueing()) {
10 this.transaction(this.connection.newLettuceResult(this.getAsyncConnection().zadd(key, score, value), LettuceConverters.longToBoolean()));
11 return null;
12 } else {
13 return LettuceConverters.toBoolean(this.getConnection().zadd(key, score, value));
14 }
15 } catch (Exception var6) {
16 throw this.convertLettuceAccessException(var6);
17 }
18 }

LettuceConverters.toBoolean()是将long转为Boolean,正常情况下,this.getConnection().zadd(key, score, value)如果新增成功话,那么返回1,这样LettuceConverters.toBoolean(1)得到的是true,反之,如果新增失败,则返回0,即LettuceConverters.toBoolean(0),还有第三种情况,就是这个this.getConnection().zadd(key, score, value)方法出现异常,什么情况下会出现异常呢?

应该是,connection连接失败的时候。

这就意味着,以lettuce驱动连接redis的过程当中,会出现连接断开的情况,导致无法新增成功,超过一定时间还没有正常,就会出现连接超时的情况。

至于是什么原因导致的断开连接,暂时还没有比较好思路,暂且把这个问题留着,等慢慢研究看是否能找到问题所在,若有大神指点,也感激不尽。

Springboot2.x集成lettuce连接redis集群报超时异常Command timed out after 6 second(s)的更多相关文章

  1. redis客户端可以连接集群,但JedisCluster连接redis集群一直报Could not get a resource from the pool

    一,问题描述: (如题目)通过jedis连接redis单机成功,使用JedisCluster连接redis集群一直报Could not get a resource from the pool 但是使 ...

  2. lua连接redis集群

    连接redis集群需要用到llua-resty-redis-cluster模块 github地址:https://github.com/cuiweixie/lua-resty-redis-cluste ...

  3. 通过jedis连接redis单机成功,使用redis客户端可以连接集群,但使用JedisCluster连接redis集群一直报Could not get a resource from the pool

    一,问题描述: (如题目)通过jedis连接redis单机成功,使用JedisCluster连接redis集群一直报Could not get a resource from the pool 但是使 ...

  4. 使用DBeaver Enterprise连接redis集群的一些操作记录

    要点总结: 使用DBeaver Enterprise连接redis集群可以通过SQL语句查看key对应的value,但是没法查看key. 使用RedisDesktopManager连接redis集群可 ...

  5. redis集群报错

    写入redis集群报错:(error) MOVED 6918 解决方法:redis-cli -c -p 7001 -h 10.0.0.104

  6. redis集群报Jedis does not support password protected Redis Cluster configurations异常解决办法

    解决spring-data-redis操作redis集群报“Jedis does not support password protected Redis Cluster configurations ...

  7. spring boot下JedisCluster方式连接Redis集群的配置

    最近在使用springboot做项目,使用redis做缓存.在外网开发的时候redis服务器没有使用集群配置,所有就是用了RedisTemplate的方式进行连接redis服务器.但是项目代码挪到内网 ...

  8. java 连接 redis集群时报错:Could not get a resource from the pool

    由于弄这个的时候浪费了太多的时间,所以才记录下这个错,给大伙参考下 检查了一下,配置啥的都没问题的,但在redis集群机器上就可以,错误如下: Exception in thread "ma ...

  9. Java连接redis集群操作存储、删除以及获取值

    pom文件添加: <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> &l ...

随机推荐

  1. gradle中的增量构建

    目录 简介 增量构建 自定义inputs和outputs 运行时API 隐式依赖 输入校验 自定义缓存方法 输入归一化 其他使用技巧 gradle中的增量构建 简介 在我们使用的各种工具中,为了提升工 ...

  2. MBP 屏幕分辨率 All In One

    MBP 屏幕分辨率 All In One screen size bug https://stackoverflow.com/questions/65462643/how-to-get-the-rea ...

  3. H5 广告落地页

    H5 广告落地页 Landing Page 用于承接通过付费搜索渠道点击进入的用户,所以叫落地页 什么是登陆页面? 在数字营销中,登录页面是专门为营销或广告活动创建的独立网页. 访问者单击电子邮件中的 ...

  4. how to change svg polygon size by update it's points in js

    how to change svg polygon size by update it's points in js matrixTransform https://stackoverflow.com ...

  5. RocketMq灰皮书(二)------本地部署启动MQ

    RocketMq灰皮书(二)------本地部署启动MQ Windows10本地部署RocketMQ 在上一篇文章中,我们对rocket的几个基本概念进行了介绍,也了解了业内几大消息中间件的区别.在本 ...

  6. 2021年-在windwos下如何用TOMACT发布一个系统(完整配置案列)

    2021年新年第一篇:博主@李宗盛-关于在Windwos下使用TOMCAT发布一个系统的完成配置案列. 之前写过关于TOMCAT的小篇幅文档,比较分散,可以作为对照与参考. 此篇整合在一起,一篇文档写 ...

  7. 【Notes_1】现代图形学入门——计算机图形学概述

    跟着闫令琪老师的课程学习,总结自己学习到的知识点 课程网址GAMES101 B站课程地址GAMES101 课程资料百度网盘[提取码:0000] 计算机图形学概述 计算机图形学是一门将模型转化到屏幕上图 ...

  8. cartographer 调参(1)-lua文件配置参考文档

    cartographer 调参(1)-lua文件配置参考文档 https://blog.csdn.net/SimileciWH/article/details/84861718 Lua configu ...

  9. Vue框架-组件的概念及使用

    目录 一.Vue组件 1. 组件分类 1.1 根组件 1.2 局部组件 1.3 全局组件 2. 组件的特点 3. 如何创建组件 4. 组件的数据局部化 5. 组件传参·父传子 6. 组件传参·子传父 ...

  10. C++指针的算术运算 、关系运算

    下面随笔是关于指针的算术运算 .关系运算. 指针类型的算术运算 指针与整数的加减运算 指针++,--运算 指针类型的算术运算 指针p加上或减去n 其意义是指针当前指向位置的前方或后方第n个数据的起始位 ...