sprintboot redis异常处理CacheErrorHandler详解以及性能问题分析

最近考虑redis异常处理,网上很多资料都是实现了CacheErrorHandler进行处理的,但是忽略了一个性能问题,下面介绍一下。

redis配置

其他配置忽略,只关心下面配置:

# 连接超时时间(毫秒)
spring.redis.timeout=400

CacheErrorHandler实现

如下: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)。

解决方法

方法1

    不使用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,可能遗漏

 

方法2

参考: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)。

你可能感兴趣的:(Java,SpringBoot,Redis/Codis)