Springboot2.x集成lettuce连接redis集群报超时异常怎么解决
Springboot2.x集成lettuce连接redis集群报超时异常怎么解决
本篇内容主要讲解“Springboot2.x集成lettuce连接redis集群报超时异常怎么解决”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Springboot2.x集成lettuce连接redis集群报超时异常怎么解决”吧!
背景:最近在对一新开发Springboot系统做压测,发现刚开始压测时,可以正常对redis集群进行数据存取,但是暂停几分钟后,接着继续用jmeter进行压测时,发现redis就开始突然疯狂爆出异常提示:Command timed out after 6 second(s)……
1Causedby:io.lettuce.core.RedisCommandTimeoutException:Commandtimedoutafter6second(s)2atio.lettuce.core.ExceptionFactory.createTimeoutException(ExceptionFactory.java:51)3atio.lettuce.core.LettuceFutures.awaitOrCancel(LettuceFutures.java:114)4atio.lettuce.core.cluster.ClusterFutureSyncInvocationHandler.handleInvocation(ClusterFutureSyncInvocationHandler.java:123)5atio.lettuce.core.internal.AbstractInvocationHandler.invoke(AbstractInvocationHandler.java:80)6atcom.sun.proxy.$Proxy134.mget(UnknownSource)7atorg.springframework.data.redis.connection.lettuce.LettuceStringCommands.mGet(LettuceStringCommands.java:119)8...15commonframesomitted
我急忙检查redis集群,发现集群里的各节点都一切正常,且cpu和内存使用率还不到百分之二十,看着这一切,我突然陷入漫长的沉思,到底是哪里出现问题……百度一番,发现不少人都出现过类似情况的,有人说把超时timeout设置更大一些就可以解决了。我按照这样的解决方法,把超时timeout的值设置到更大后,依然没有解决该超时问题。
其中,springboot操作redis的依赖包是——
1<dependency>2<groupId>org.springframework.boot</groupId>3<artifactId>spring-boot-starter-data-redis</artifactId>4</dependency>
集群配置——
1redis:2timeout:6000ms3cluster:4nodes:5-xxx.xxx.x.xxx:63796-xxx.xxx.x.xxx:63797-xxx.xxx.x.xxx:63798jedis:9pool:10max-active:100011max-idle:1012min-idle:513max-wait:-1
点进spring-boot-starter-data-redis进去,发现里面包含了lettuce的依赖:
springboot1.x默认使用的是jedis,到了Springboot2.x就默认使用了lettuce。我们可以简单验证一下,在redis驱动加载配置类里,输出一下RedisConnectionFactory信息:
1@Configuration2@AutoConfigureAfter(RedisAutoConfiguration.class)3publicclassConfiguration{4@Bean5publicStringRedisTemplateredisTemplate(RedisConnectionFactoryfactory){6log.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(2proxyBeanMethods=false3)4@ConditionalOnClass({RedisOperations.class})5@EnableConfigurationProperties({RedisProperties.class})6@Import({LettuceConnectionConfiguration.class,JedisConnectionConfiguration.class})7publicclassRedisAutoConfiguration{8publicRedisAutoConfiguration(){9}10......省略11}
这里只需要关注里面的一行注解:
12@Import({LettuceConnectionConfiguration.class,JedisConnectionConfiguration.class})3
这就意味着使用spring-boot-starter-data-redis依赖时,可自动导入lettuce和jedis两种驱动,按理来说,不会同时存在两种驱动,这样没有太大意义,因此,这里的先后顺序就很重要了,为什么这么说呢?
分别进入到LettuceConnectionConfiguration.class与JedisConnectionConfiguration.class当中,各自展示本文需要涉及到的核心代码:
1//LettuceConnectionConfiguration2@ConditionalOnClass({RedisClient.class})3classLettuceConnectionConfigurationextendsRedisConnectionConfiguration{4......省略5@Bean6@ConditionalOnMissingBean({RedisConnectionFactory.class})7LettuceConnectionFactoryredisConnectionFactory(ObjectProvider<LettuceClientConfigurationBuilderCustomizer>builderCustomizers,ClientResourcesclientResources)throwsUnknownHostException{8LettuceClientConfigurationclientConfig=this.getLettuceClientConfiguration(builderCustomizers,clientResources,this.getProperties().getLettuce().getPool());9returnthis.createLettuceConnectionFactory(clientConfig);10}11}12//JedisConnectionConfiguration13@ConditionalOnClass({GenericObjectPool.class,JedisConnection.class,Jedis.class})14classJedisConnectionConfigurationextendsRedisConnectionConfiguration{15......省略16@Bean17@ConditionalOnMissingBean({RedisConnectionFactory.class})18JedisConnectionFactoryredisConnectionFactory(ObjectProvider<JedisClientConfigurationBuilderCustomizer>builderCustomizers)throwsUnknownHostException{19returnthis.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))——
1publicBooleanzAdd(byte[]key,doublescore,byte[]value){2Assert.notNull(key,"Keymustnotbenull!");3Assert.notNull(value,"Valuemustnotbenull!");45try{6if(this.isPipelined()){7this.pipeline(this.connection.newLettuceResult(this.getAsyncConnection().zadd(key,score,value),LettuceConverters.longToBoolean()));8returnnull;9}elseif(this.isQueueing()){10this.transaction(this.connection.newLettuceResult(this.getAsyncConnection().zadd(key,score,value),LettuceConverters.longToBoolean()));11returnnull;12}else{13returnLettuceConverters.toBoolean(this.getConnection().zadd(key,score,value));14}15}catch(Exceptionvar6){16throwthis.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连接失败的时候。
到此,相信大家对“Springboot2.x集成lettuce连接redis集群报超时异常怎么解决”有了更深的了解,不妨来实际操作一番吧!这里是恰卡编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
推荐阅读
-
vue动态添加删除输入框(springboot vue怎么让数据库显示出来)
springbootvue怎么让数据库显示出来?一般情况下是前端调阅后端接口,来获取到数据库的数据,后端哪里会把数据库的数据整理...
-
php如何让Swoole/Pool进程池实现Redis持久连接
php如何让Swoole/Pool进程池实现Redis持久连接本篇...
-
php操作redis大全记录
php连接redis测试˂?php$redis=newRedis();$redis-˃conne...
-
PHP经典高级工程师面试题
1.PHP如何实现不用自带的cookie函数为客户端下发cookie。对于分布式系统,如何来保存session值...
-
PHP操作Redis数据库
-
php利用redis防止商品超发来限制抢购,简单又实用
-
php如何实现秒杀功能?php+redis模拟简单抢购场景,快来看看吧
-
PHP高级工程师面试题
-
Laravel结合Redis发送邮箱验证码
-
使用redis缓存实现多服务器PHP sessions共享