我是靠谱客的博主 现实小白菜,这篇文章主要介绍Redis client之Jedis在线程执行抛出异常无法恢复的情形和解决方案,现在分享给大家,希望可以做个参考。

环境概述

1. SpringBoot 1.5.9 注解方式返回单例Jedis对象作为client

2.JedisPool连接配置如下:

复制代码
1
2
3
4
max-total: 100 # 连接池最大连接数(使用负值表示没有限制) max-wait: 10 # 连接池最大阻塞等待时间(使用负值表示没有限制) min-idle: 10 # 连接池中的最小空闲连接 max-idle: 10 # 连接池中的最大空闲连接

问题描述

在执行reids操作过程中,一旦抛出异常,例如:RedisClient - java.net.SocketTimeoutException: Read timed out,接下来所有的redis操作都是失败的且无法恢复。

问题代码

复制代码
1
2
3
4
5
6
7
8
public String get(String key) { try { return jedisClient.get(key); } catch (Exception e) { logger.error("get:{} have exception:{}", key, e); } return null; }

原因分析

首先看获得和引用Jedis实例代码:

复制代码
1
2
3
4
5
6
@Bean(name= "jedisClient") @Autowired public Jedis singleJedis(@Value("${spring.redis.host}") String host, @Value("${spring.redis.port}") int port) { return new JedisPool(jedisPoolConfig, host, port).getResource(); }
复制代码
1
2
@Autowired private RedisClient redisClient;

很明显可以看到,整个context都是一个Jedis实例持有这些连接。那么这个实例持有的当前连接异常断开时,应该怎么办呢?应该close()掉。因为close的时候会判断连接的可用性,来决定是否采用新的连接,代码如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
public void close() { if (this.dataSource != null) { if (this.client.isBroken()) {//关键代码,判断是否broken this.dataSource.returnBrokenResource(this); #执行归还broken调用 } else { this.dataSource.returnResource(this);#执行归还调用 } } else { this.client.close(); } }
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/** 下面代码展示,broken的连接归还流程 */ protected void returnBrokenResourceObject(T resource) { try { this.internalPool.invalidateObject(resource);//调用无效对象方法 } catch (Exception var3) { throw new JedisException("Could not return the resource to the pool", var3); } } public void invalidateObject(T obj) throws Exception { PooledObject<T> p = (PooledObject)this.allObjects.get(new IdentityWrapper(obj)); if (p == null) { if (!this.isAbandonedConfig()) { throw new IllegalStateException("Invalidated object not currently part of this pool"); } } else { synchronized(p) { if (p.getState() != PooledObjectState.INVALID) { //这句代码关键,没有设置无效状态事进行destory操作 this.destroy(p); } } this.ensureIdle(1, false); } } //destory操作中对对象进行了删除和销毁操作 private void destroy(PooledObject<T> toDestroy) throws Exception { toDestroy.invalidate(); this.idleObjects.remove(toDestroy); this.allObjects.remove(new IdentityWrapper(toDestroy.getObject())); try { this.factory.destroyObject(toDestroy); } finally { this.destroyedCount.incrementAndGet(); this.createCount.decrementAndGet(); } }

 

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/** 下面的代码展示正常连接归还流程 */ public void returnResource(T resource) { if (resource != null) { this.returnResourceObject(resource); } } public void returnResourceObject(T resource) { if (resource != null) { try { this.internalPool.returnObject(resource);//关键代码,可以发现正常连接归还的流程最终并没有删除销毁,也就是说其实可以不归还,继续使用,更加高效 } catch (Exception var3) { throw new JedisException("Could not return the resource to the pool", var3); } } }

分析结论

在执行Redis操作异常时,catch到并执行close操作即可,代码如下:

复制代码
1
2
3
4
5
6
7
8
9
public String get(String key) { try { return jedisClient.get(key); } catch (Exception e) { logger.error("get:{} have exception:{}", key, e); jedisClient.close(); //添加此行代码 } return null; }

经过实际验证,可以解决异常无法自动恢复的问题,需要指出的是jedis始终是那一个实例,只是持有的连接不同了

 

更新:随着排查深入,发现在springboot 1.5.9中,是通过添加

复制代码
1
2
3
4
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>

依赖来引用jedis,进一步查看jedis版本是2.9.0,而2.9.0版本已经是是较高的稳定版且是使用占比最大的版本:

实际上,在使用如此广泛的引用中,应该由jedis库再发现异常时,直接进行close(),而不是由业务方来完成此事。

更多信息可参考:https://my.oschina.net/jrrx/blog/1634837

最后

以上就是现实小白菜最近收集整理的关于Redis client之Jedis在线程执行抛出异常无法恢复的情形和解决方案的全部内容,更多相关Redis内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(90)

评论列表共有 0 条评论

立即
投稿
返回
顶部