专栏首页网络小说作家的编程技术沉思录Springboot2.x集成lettuce连接redis集群报超时异常Command timed out after 6 second(s)

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

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring Boot 2.3.0 新特性Redis 拓扑动态感应

    本篇是 spring boot v2.3 系列第三篇,来分享一下 v2.3 关于 spring data redis 的故障转移优化。

    冷冷
  • redis架构演变与redis-cluster群集读写方案

    redis-cluster是近年来redis架构不断改进中的相对较好的redis高可用方案。本文涉及到近年来redis多实例架构的演变过程,包括普通主从架构(M...

    用户1263954
  • 2018年终总结

    整体而言,今年技术层面稍微有点拓宽,跨入了外表看上去高大上的流式计算领域,打开了另外一扇窗;而基于java的分布式/微服务领域,今年变化比较大,spring c...

    codecraft
  • 扛不住 1W+ 并发流量请求,SpringCache 缓存注解真的那么弱?

    最近做 API 接口压测时,TPS(要求至少 7000/s)始终上不去,究其原因发现很多接口是直接连库查询。

    猿芯
  • Redis高级客户端Lettuce详解

    Lettuce是一个Redis的Java驱动包,初识她的时候是使用RedisTemplate的时候遇到点问题Debug到底层的一些源码,发现spring-dat...

    猿天地
  • ​高性能分布式锁的另一种实现:Redisson

    以往在项目中涉及到分布式锁时,都是结合redisTemplate采用类原生的方式编写,代码量不少,还容易出现锁死的情况,近来无意间在看到某篇文章中发现了redi...

    MavenTalker
  • 剖析更高级的Redis客户端Lettuce

    Lettuce是一个Redis的Java驱动包,初识她的时候是使用RedisTemplate的时候遇到点问题Debug到底层的一些源码,发现spring-dat...

    Bug开发工程师
  • 【Redis破障之路】四:Jedis基本使用

    在前面我们已经学习了Redis命令行客户端redis-cli的使用,接下来我们了解一下Redis基于Java编程语言的客户端。

    三分恶
  • Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理

    execute()是 java.util.concurrent.Executor接口中唯一的方法,JDK注释中的描述是“在未来的某一时刻执行命令command”...

    Janti
  • Spring Boot 2.X(六):Spring Boot 集成 Redis

    Redis 是目前使用的非常广泛的免费开源内存数据库,是一个高性能的 key-value 数据库。

    朝雾轻寒
  • 通过了解RejectedExecutionException来分析ThreadPoolExecutor源码

    观看本文章之前,最好看一下这篇文章熟悉下ThreadPoolExecutor基础知识。 1.关于Java多线程的一些常考知识点 2.看ThreadPoolE...

    用户2032165
  • Redis客户端常见异常分析

    在Redis客户端的使用过程中,无论是客户端使用不当或者Redis服务端出现问题,客户端会反应出一些异常,下面分析一下Jedis使用过程中常见的异常情况:

    九州暮云
  • 《Redis入门这一篇就够了》

    通过Nginx代理到某一个服务器上时,会造成在8080或者8081的Tomcat服务器上登录,Tomcat服务器会给客户端返回一个JSessionID的Cook...

  • SpringBoot2.x整合Redis数据库

    1、Redis是当下最流行的用于实现缓存机制的NoSQL数据库,其主要通过key-value存储,支持高并发访问。在实际工作中,Redis结合SpringDat...

    别先生
  • Redis5.x哨兵搭建手记

    Redis5.x之后,单机、哨兵、集群搭建的难度已经简化。鉴于目前看到太多文章都是复制粘贴以往一些3.x版本的一些内容,所以打算基于当前Redis的最新版本做一...

    Throwable
  • SpringCloud 妹子图之 Redis 高可用集群

    一般的小项目,比如几百人左右访问的项目,访问量几万的项目,如果想用缓存,单机实例完全够用。小黄图就是用的阿里云256MB配置的Redis缓存,日几千的访问量是妥...

    小柒2012
  • Redis进阶-5.x 单节点 及Redis Cluster 3主3从集群部署

    之前装过4.0.11 ----> Redis-02Redis在linux下的安装及常见问题

    小小工匠
  • Redis3 集群功能配置初尝

    又很久很久没有更新博客了,也很久很久没有做云计算了,前期做了一段时间的游戏,现在又回归到常规运维的岗位,最近接触到redis这玩意,也就简单配置了下,至少要熟悉...

    py3study
  • 从0到1部署一套TiDB本地集群

    适用场景:利用本地 Mac 或者单机 Linux 环境快速部署 TiDB 集群。可以体验 TiDB 集群的基本架构,以及 TiDB、TiKV、PD、监控等基础组...

    jinjunzhu

扫码关注云+社区

领取腾讯云代金券