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

本文探讨了Redis异常处理中常见的性能问题,特别是在使用CacheErrorHandler时,如何避免不必要的连接等待时间,提出了两种解决方案,一种是在函数外部捕获异常,另一种是通过自定义异常处理类并抛出特定异常来解决。

这篇文章资料来自于网络,对部分知识整理,这里只是记录一下,仅供参考。

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

redis配置

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

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

CacheErrorHandler实现

如下:Cacheable redis 宕机 - 支照 - 博客园

/**
     * 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

参考:****************** - OSCHINA - 中文开源技术交流社区

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)。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值