最近考虑redis异常处理,网上很多资料都是实现了CacheErrorHandler进行处理的,但是忽略了一个性能问题,下面介绍一下。
其他配置忽略,只关心下面配置:
# 连接超时时间(毫秒)
spring.redis.timeout=400
如下:https://www.cnblogs.com/zhizhao/p/10151287.html
/**
* redis数据操作异常处理 这里的处理:在日志中打印出错误信息,但是放行
* 保证redis服务器出现连接等问题的时候不影响程序的正常运行,使得能够出问题时不用缓存
*
* @return
*/
@Bean
@Override
public CacheErrorHandler errorHandler() {
CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {
@Override
public void handleCachePutError(RuntimeException exception, Cache cache,
Object key, Object value) {
RedisErrorException(exception, key);
}
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache,
Object key) {
RedisErrorException(exception, key);
}
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache,
Object key) {
RedisErrorException(exception, key);
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
RedisErrorException(exception, null);
}
};
return cacheErrorHandler;
}
protected void RedisErrorException(Exception exception,Object key){
log.error("redis异常:key=[{}]", key, exception);
}
当redis出现异常时,CacheErrorHandler捕获异常进行处理:
[DEBUG] [i.l.c.c.PooledClusterConnectionProvider] getConnection(READ, 14016)
[DEBUG] [i.l.c.protocol.DefaultEndpoint] [channel=0x2cfb4178, /10.4.10.13:62530 -> /10.106.157.104:9012, epid=0x8] writeToDisconnectedBuffer() buffering (disconnected) command ClusterCommand [command=AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command], redirections=0, maxRedirections=5]
[DEBUG] [i.l.c.protocol.DefaultEndpoint] [channel=0x2cfb4178, /10.4.10.13:62530 -> /10.106.157.104:9012, epid=0x8] write() done
[ERROR] [c.l.n.m.c.JedisClusterConfig] redis异常:key=[....], exception=Redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException: io.lettuce.core.RedisCommandTimeoutException: Command timed out after 400 millisecond(s)
....
读取db
....
[DEBUG] [i.l.c.c.PooledClusterConnectionProvider] getConnection(WRITE, 14016)
[DEBUG] [i.l.c.protocol.DefaultEndpoint] [channel=0x2cfb4178, /10.4.10.13:62530 -> /10.106.157.104:9012, epid=0x8] writeToDisconnectedBuffer() buffering (disconnected) command ClusterCommand [command=AsyncCommand [type=SET, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command], redirections=0, maxRedirections=5]
[DEBUG] [i.l.c.protocol.DefaultEndpoint] [channel=0x2cfb4178, /10.4.10.13:62530 -> /10.106.157.104:9012, epid=0x8] write() done
[ERROR] [c.l.n.m.c.JedisClusterConfig] redis异常:key=[....], exception=Redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException: Command timed out after 400 millisecond(s)
可以发现,当redis异常时,虽然使用CacheErrorHandler类捕获了异常,让程序正常运行获取数据库信息。但是会出现getConnection(READ, 14016)和getConnection(WRITE, 14016)两个获取链接等待超时。
一般情况是,redis出现异常后,getConnection(WRITE, 14016)几乎也会失败,所以用CacheErrorHandler类捕获异常会导致多等待getConnection(WRITE, 14016)的400 millisecond(s)。
不使用CacheErrorHandler类捕获异常,再函数外部使用try{} catch(Exception e) {}自己捕获异常,避免多等待getConnection(WRITE, 14016)的400 millisecond(s)。
try {
//获取redis数据
data = getRedisData();
} catch (Exception e) {
if (e instanceof QueryTimeoutException ||
e instanceof RedisCommandTimeoutException || e instanceof RedisCommandExecutionException) {
//redis 异常处理
} else {
return error;
}
}
缺点就是不清楚有多少Exception,可能遗漏。
参考:https://my.oschina.net/JasonZhang/blog/994028
public class AppCacheErrorHandler implements CacheErrorHandler{
@Override
public void handleCacheGetError(RuntimeException e, Cache cache, Object o) {
if(e instanceof JedisConnectionException || e instanceof RedisConnectionFailureException){
logger.warn("redis has lose connection:",e);
return;
}
throw e;
}
@Override
public void handleCachePutError(RuntimeException e, Cache cache, Object o, Object o1) {
if(e instanceof JedisConnectionException || e instanceof RedisConnectionFailureException){
logger.warn("redis has lose connection:",e);
return;
}
throw e;
}
@Override
public void handleCacheEvictError(RuntimeException e, Cache cache, Object o) {
throw e;
}
@Override
public void handleCacheClearError(RuntimeException e, Cache cache) {
throw e;
}
}
@Override
public CacheErrorHandler errorHandler() {
return new AppCacheErrorHandler();
}
作用就是将RuntimeException(注意,必须是RuntimeException,不能是Exception) 抛出去。
添加自定义MyRedisException:
public class MyRedisException extends RuntimeException {
public MyRedisException(){
super();
}
}
/**
* redis数据操作异常处理 这里的处理:在日志中打印出错误信息,但是放行
* 保证redis服务器出现连接等问题的时候不影响程序的正常运行,使得能够出问题时不用缓存
*
* @return
*/
@Bean
@Override
public CacheErrorHandler errorHandler() {
CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {
@Override
public void handleCachePutError(RuntimeException exception, Cache cache,
Object key, Object value) {
RedisErrorException(exception, key);
}
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache,
Object key) {
RedisErrorException(exception, key);
}
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache,
Object key) {
RedisErrorException(exception, key);
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
RedisErrorException(exception, null);
}
};
return cacheErrorHandler;
}
protected void RedisErrorException(Exception exception,Object key){
log.error("redis异常:key=[{}]", key, exception);
thow new MyRedisException();
}
捕获MyRedisException:
try {
//获取redis数据
data = getRedisData();
} catch (Exception e) {
if (e instanceof MyRedisException) {
//redis 异常处理
} else {
return error;
}
}
这样,所有redis异常都能捕获了,不用再多等待getConnection(WRITE, 14016)的400 millisecond(s)。