文/朱季谦

背景:最近在对一新开发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. Steam 钓鱼模拟器

    Steam 钓鱼模拟器 Fishing Planet Fishing Planet 是一个独特和高度现实的在线第一人称多人钓鱼模拟器,由狂热的钓鱼爱好者钓鱼给你带来实际钓鱼充分刺激开发! 选择你的诱饵 ...

  2. JavaScript 高级程序设计 (第4版) 思维导图/脑图 All In One

    JavaScript 高级程序设计 (第4版) 思维导图/脑图 All In One JavaScript 高级程序设计 (第4版) 思维导图下载 JavaScript 高级程序设计 (第4版) 脑图 ...

  3. express+mongodb开发网站

    准备工作: 1安装git 进入官网  使用方法:使用git教程 2安装node.js 进入官网 3安装mongodb 进入官网 需要技术: 1.基础知识:html .css. js .jquery 2 ...

  4. Prometheus时序数据库-内存中的存储结构

    Prometheus时序数据库-内存中的存储结构 前言 笔者最近担起了公司监控的重任,而当前监控最流行的数据库即是Prometheus.按照笔者打破砂锅问到底的精神,自然要把这个开源组件源码搞明白才行 ...

  5. 能取值亦能赋值的Python切片

    切片,就像面包,给几刀,切成一片一片,可以做成吐司,也可以做成三明治,口味更佳: 列表(list).元组(tuple).字符串(str)都能进行切片,得到子片段,实际上切片操作比想象的要强大很多,能取 ...

  6. 上天的源码要不要——GitHub 热点速览 v.21.08

    作者:HelloGitHub-小鱼干 前几天,"机智号" 所用的飞行软件框架 F´ 被 NASA 开源了,想看 F´ 这个嵌入式的代码不妨考虑下 Sourcetrail 这个神器, ...

  7. Vue和Element基础使用,综合案例学生列表实现

    知识点梳理 课堂讲义 1.Vue 快速入门 1.1.Vue的介绍 Vue是一套构建用户界面的渐进式前端框架. 只关注视图层,并且非常容易学习,还可以很方便的与其它库或已有项目整合. 通过尽可能简单的A ...

  8. 微信小程序:日期组件picker的使用

    1.页面代码 <view class="right"> <picker mode="date" value="{{material. ...

  9. Apache支持Vue router使用 HTML5History 模式

    一.前言 前端Vue router 使用history模式,URL会比hash模式好看,这种模式要玩好,还需要后端配置支持,否则会报404错误. 注:1.前端代码省略. 2.此处后台使用Apache服 ...

  10. Docker的镜像操作

    Docker镜像操作 一.查看镜像 docker images # 查看本地镜像 二.搜索镜像 docker search 镜像名字 docker search centos 三.下载(拉取)镜像 自 ...