Springboot2.x集成lettuce连接redis集群报超时异常Command timed out after 6 second(s)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 最近在对一新开发Springboot系统做压测,发现刚开始压测时,可以正常对redis集群进行数据存取,但是暂停几分钟后,接着继续用jmeter进行压测时,发现redis就开始突然疯狂爆出异常提示:Command timed out after 6 second(s)......

文/朱季谦

背景:最近在对一新开发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)

 3publicclass 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})

 7publicclass 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})

 3class 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         returnthis.createLettuceConnectionFactory(clientConfig);

10    }

11 }

12//JedisConnectionConfiguration

13 @ConditionalOnClass({GenericObjectPool.class, JedisConnection.class, Jedis.class})

14class JedisConnectionConfiguration extends RedisConnectionConfiguration {

15    ......省略

16     @Bean

17     @ConditionalOnMissingBean({RedisConnectionFactory.class})

18     JedisConnectionFactory redisConnectionFactory(ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) throws UnknownHostException {

19         returnthis.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))——

 1public 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             returnnull;

 9        } elseif (this.isQueueing()) {

10             this.transaction(this.connection.newLettuceResult(this.getAsyncConnection().zadd(key, score, value), LettuceConverters.longToBoolean()));

11             returnnull;

12        } else {

13             return LettuceConverters.toBoolean(this.getConnection().zadd(key, score, value));

14        }

15    } catch (Exception var6) {

16         throwthis.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的过程当中,会出现连接断开的情况,导致无法新增成功,超过一定时间还没有正常,就会出现连接超时的情况。

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

 

最后发现,阿里云官网上关于redis的客户端连接,是不推荐使用Lettuce客户端——


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
6月前
|
存储 缓存 NoSQL
深入理解Django与Redis的集成实践
深入理解Django与Redis的集成实践
164 0
|
1月前
|
NoSQL Java 关系型数据库
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 介绍
本文介绍在 Spring Boot 中集成 Redis 的方法。Redis 是一种支持多种数据结构的非关系型数据库(NoSQL),具备高并发、高性能和灵活扩展的特点,适用于缓存、实时数据分析等场景。其数据以键值对形式存储,支持字符串、哈希、列表、集合等类型。通过将 Redis 与 Mysql 集群结合使用,可实现数据同步,提升系统稳定性。例如,在网站架构中优先从 Redis 获取数据,故障时回退至 Mysql,确保服务不中断。
104 0
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 介绍
|
1月前
|
缓存 NoSQL Java
基于SpringBoot的Redis开发实战教程
Redis在Spring Boot中的应用非常广泛,其高性能和灵活性使其成为构建高效分布式系统的理想选择。通过深入理解本文的内容,您可以更好地利用Redis的特性,为应用程序提供高效的缓存和消息处理能力。
161 79
|
2月前
|
NoSQL Java Redis
Springboot使用Redis实现分布式锁
通过这些步骤和示例,您可以系统地了解如何在Spring Boot中使用Redis实现分布式锁,并在实际项目中应用。希望这些内容对您的学习和工作有所帮助。
222 83
|
29天前
|
存储 人工智能 NoSQL
SpringBoot整合Redis、ApacheSolr和SpringSession
本文介绍了如何使用SpringBoot整合Redis、ApacheSolr和SpringSession。SpringBoot以其便捷的配置方式受到开发者青睐,通过引入对应的starter依赖,可轻松实现功能整合。对于Redis,可通过配置RedisSentinel实现高可用;SpringSession则提供集群Session管理,支持多种存储方式如Redis;整合ApacheSolr时,借助Zookeeper搭建SolrCloud提高可用性。文中详细说明了各组件的配置步骤与代码示例,方便开发者快速上手。
56 11
|
1月前
|
SQL Java 中间件
【YashanDB知识库】yasdb jdbc驱动集成BeetISQL中间件,业务(java)报autoAssignKey failure异常
在BeetISQL 2.13.8版本中,客户使用batch insert向yashandb表插入数据并尝试获取自动生成的sequence id时,出现类型转换异常。原因是beetlsql在prepareStatement时未指定返回列,导致yashan JDBC驱动返回rowid(字符串),与Java Bean中的数字类型tid不匹配。此问题影响业务流程,使无法正确获取sequence id。解决方法包括:1) 在batchInsert时不返回自动生成的sequence id;2) 升级至BeetISQL 3,其已修正该问题。
【YashanDB知识库】yasdb jdbc驱动集成BeetISQL中间件,业务(java)报autoAssignKey failure异常
|
1月前
|
SQL druid Oracle
【YashanDB知识库】yasdb jdbc驱动集成druid连接池,业务(java)日志中有token IDENTIFIER start异常
客户Java日志中出现异常,影响Druid的merge SQL功能(将SQL字面量替换为绑定变量以统计性能),但不影响正常业务流程。原因是Druid在merge SQL时传入null作为dbType,导致无法解析递归查询中的`start`关键字。
|
1月前
|
NoSQL Java API
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Spring Boot 集成 Redis
本文介绍了在Spring Boot中集成Redis的方法,包括依赖导入、Redis配置及常用API的使用。通过导入`spring-boot-starter-data-redis`依赖和配置`application.yml`文件,可轻松实现Redis集成。文中详细讲解了StringRedisTemplate的使用,适用于字符串操作,并结合FastJSON将实体类转换为JSON存储。还展示了Redis的string、hash和list类型的操作示例。最后总结了Redis在缓存和高并发场景中的应用价值,并提供课程源代码下载链接。
75 0
|
1月前
|
NoSQL Java Redis
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 安装
本教程介绍在 VMware 虚拟机(CentOS 7)或阿里云服务器中安装 Redis 的过程,包括安装 gcc 编译环境、下载 Redis(官网或 wget)、解压安装、修改配置文件(如 bind、daemonize、requirepass 等设置)、启动 Redis 服务及测试客户端连接。通过 set 和 get 命令验证安装是否成功。适用于初学者快速上手 Redis 部署。
35 0
|
3月前
基于springboot+thymeleaf+Redis仿知乎网站问答项目源码
基于springboot+thymeleaf+Redis仿知乎网站问答项目源码
190 36