JedisConnectionException: java.net.SocketTimeoutException: connect timed out

生产近期出现redis服务异常的问题,今天抽时间对该问题进行解决。

异常复现

先贴出报错信息:

java.util.concurrent.CompletionException: org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out
        at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
        at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
        at java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1629)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.data.redis.RedisConnectionFailureException: Cannot get Jedis connection; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out
        at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:286)
        at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:469)
        at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:132)
        at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:95)
        at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:82)
        at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:211)
        at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:184)
        at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:95)
        at org.springframework.data.redis.core.DefaultValueOperations.get(DefaultValueOperations.java:48)
        at com.haohuo.util.RedisUtils.get(RedisUtils.java:176)
        at com.haohuo.compose.PayOutCompose.lambda$asyncPay$1(PayOutCompose.java:949)
        at java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1626)
        ... 3 common frames omitted
Caused by: redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out
        at redis.clients.jedis.Connection.connect(Connection.java:207)
        at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:93)
        at redis.clients.jedis.BinaryJedis.connect(BinaryJedis.java:1767)
        at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:28

该报错信息非常明显,连接超时,那么顺着思路解决超时的问题就可以了,接着找到项目中关于redis的配置:

spring.redis.host=192.168.11.231
spring.redis.port=6379
spring.redis.password=
spring.redis.lettuce.pool.max-active=-1
spring.redis.lettuce.pool.max-idle=100
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.min-idle=10

从这边来看的话,spring.redis.lettuce.pool.max-wait=-1ms这种负数的配置给出现在springboot中,第一印象就是没有限制的意思,但是这从报错信息上来看是完全解释不通,相矛盾的,当然这段配置是前辈们留下来的,也在生产跑的没有出过什么大问题,那到底是为什么呢?

带着这个疑问,那么只能在本地模拟一下并发来看是否可以复现一样的异常信息,模拟接口如下:

	@Autowired
    private RedisUtils redisUtils;

    @RequestMapping(value = "test", method = RequestMethod.GET)
    public ComResult test(int count){
        redisUtils.set("zhangpk.test", "countdownexcep", 60 *60);
        ExecutorService pool = Executors.newCachedThreadPool();
        CountDownLatch cdl = new CountDownLatch(count);
        for(int i=0;i<count;i++){
            pool.execute(() -> {
                try {
                    /*** 不加锁也可以支持*/
                    synchronized (cdl) {
                        /*** 每次减少一个容量*/
                        cdl.countDown();
                    }
                    /*** 阻塞线程,直到countDown至0*/
                    cdl.await();
                    System.out.println(redisUtils.get("zhangpk.test"));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        return success("");
    }

可以入参直接调用我们吧count值从100逐渐往上加,发现count=500后会偶尔出现上述异常,1000后比较频繁,这样看来生产出问题是理所应当的呀,因为某些服务的使用并发是绝对有的。

解决思路

当前项目使用的springboot版本是2.x,从网上随便一查都可以了解到,1.x和2.x在使用redis连接池的默认值是不一样的,1.x默认使用jedis连接池,2.x默认使用的是lettuce,我们项目很明显这里配置是针对lettuce连接池的,这里配置如果没问题的话,那是不是说在注入bean实例时出现问题了呢?顺着这个思路,找到项目中redistemplate这个bean,如下:

@Bean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
        template.setConnectionFactory(redisConnectionFactory);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setStringSerializer(stringRedisSerializer);
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        return template;
    }

这段代码就是一个配置,支持更好的序列化方式,使用了RedisConnectionFactory工厂,当前接口获取连接的实现类有两个,就是上面提到的两种连接池,源码也比较清晰;但是这里并没有找到对当前工厂进行单独设置的bean,这里有一个疑问,就是说lettuce在配置后会自动配置到RedisConnectionFactory内吗?

带着这个疑问,开始debug该bean方法,发现RedisConnectionFactory在项目初始化时构造如下图:
在这里插入图片描述

很明显没有lettuce的任何信息呀,这。。。

然后去科普了一些springboot集成lettuce连接redis的案例,详细不在此赘述了,总之最后得出结论:当前redis配置绝对有问题!

由于当前项目内使用分布式锁是通过jedis来实现的,所以更倾向于将连接池直接改为jedis,

# redis参数
spring.redis.host=192.168.11.231
spring.redis.port=6379
spring.redis.password=
spring.redis.jedis.pool.max-active=50
spring.redis.jedis.pool.max-idle=50
spring.redis.jedis.pool.max-wait=3s
spring.redis.jedis.pool.min-idle=10

经过多次验证spring.redis.jedis.pool.max-active=-1是绝对要设置指定值的,给一个适合的值能保证在项目初始化时创建出这些连接数,从而提高资源利用率,这个和druid连接mysql数据库同样的道理;同时spring.redis.jedis.pool.max-wait=3s当前设置为3s,是因为当前项目使用dubbo来实现分布式服务之间的通信,dubbo的线程数是有限且是有超时时间的,为了防止由于redis造成的服务阻塞、宕机,该值还是设置的合理一些比较好,这段配置后,在本地运行一下测试方法,是可以让当前单机redis达到万级的,没有任何异常,后续如果项目发生问题再更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值