• Java重试机制


    重试作用:

    对于重试是有场景限制的,不是什么场景都适合重试,比如参数校验不合法、写操作等(要考虑写是否幂等)都不适合重试。

    远程调用超时、网络突然中断可以重试。在微服务治理框架中,通常都有自己的重试与超时配置,比如dubbo可以设置retries=1,timeout=500调用失败只重试1次,超过500ms调用仍未返回则调用失败。

    比如外部 RPC 调用,或者数据入库等操作,如果一次操作失败,可以进行多次重试,提高调用成功的可能性

    优雅的重试机制要具备几点:

    • 无侵入:这个好理解,不改动当前的业务逻辑,对于需要重试的地方,可以很简单的实现
    • 可配置:包括重试次数,重试的间隔时间,是否使用异步方式等
    • 通用性:最好是无改动(或者很小改动)的支持绝大部分的场景,拿过来直接可用

    优雅重试共性和原理:

    • 正常和重试优雅解耦,重试断言条件实例或逻辑异常实例是两者沟通的媒介。
    • 约定重试间隔,差异性重试策略,设置重试超时时间,进一步保证重试有效性以及重试流程稳定性。
    • 都使用了命令设计模式,通过委托重试对象完成相应的逻辑操作,同时内部封装实现重试逻辑。
    • Spring-tryer和guava-tryer工具都是线程安全的重试,能够支持并发业务场景的重试逻辑正确性。

    优雅重试适用场景:

    • 功能逻辑中存在不稳定依赖场景,需要使用重试获取预期结果或者尝试重新执行逻辑不立即结束。比如远程接口访问,数据加载访问,数据上传校验等等。
    • 对于异常场景存在需要重试场景,同时希望把正常逻辑和重试逻辑解耦。
    • 对于需要基于数据媒介交互,希望通过重试轮询检测执行逻辑场景也可以考虑重试方案。 

    优雅重试解决思路:

    切面方式

    这个思路比较清晰,在需要添加重试的方法上添加一个用于重试的自定义注解,然后在切面中实现重试的逻辑,主要的配置参数则根据注解中的选项来初始化

    优点:

      • 真正的无侵入

    缺点:

      • 某些方法无法被切面拦截的场景无法覆盖(如spring-aop无法切私有方法,final方法)
      • 直接使用aspecj则有些小复杂;如果用spring-aop,则只能切被spring容器管理的bean

    消息总线方式

    这个也比较容易理解,在需要重试的方法中,发送一个消息,并将业务逻辑作为回调方法传入;由一个订阅了重试消息的consumer来执行重试的业务逻辑

    优点:

      • 重试机制不受任何限制,即在任何地方你都可以使用
      • 利用EventBus框架,可以非常容易把框架搭起来

    缺点:

      • 业务侵入,需要在重试的业务处,主动发起一条重试消息
      • 调试理解复杂(消息总线方式的最大优点和缺点,就是过于灵活了,你可能都不知道什么地方处理这个消息,特别是新的童鞋来维护这段代码时)
      • 如果要获取返回结果,不太好处理, 上下文参数不好处理

    模板方式

    优点:

      • 简单(依赖简单:引入一个类就可以了; 使用简单:实现抽象类,讲业务逻辑填充即可;)
      • 灵活(这个是真正的灵活了,你想怎么干都可以,完全由你控制)

    缺点:

      • 强侵入
      • 代码臃肿

    把这个单独捞出来,主要是某些时候我就一两个地方要用到重试,简单的实现下就好了,也没有必用用到上面这么重的方式;而且我希望可以针对代码快进行重试

    这个的设计还是非常简单的,基本上代码都可以直接贴出来,一目了然:

    public abstract class RetryTemplate {
    
        private static final int DEFAULT_RETRY_TIME = 1;
        private int retryTime = DEFAULT_RETRY_TIME; 
        private int sleepTime = 0;// 重试的睡眠时间
    
        public int getSleepTime() {
            return sleepTime;
        }
    
        public RetryTemplate setSleepTime(int sleepTime) {
            if(sleepTime < 0) {
                throw new IllegalArgumentException("sleepTime should equal or bigger than 0");
            }
            this.sleepTime = sleepTime;
            return this;
        }
    
        public int getRetryTime() {
            return retryTime;
        }
    
        public RetryTemplate setRetryTime(int retryTime) {
            if (retryTime <= 0) {
                throw new IllegalArgumentException("retryTime should bigger than 0");
            }
            this.retryTime = retryTime;
            return this;
        }
    
        /**
         * 重试的业务执行代码
         * 失败时请抛出一个异常
         *
         * todo 确定返回的封装类,根据返回结果的状态来判定是否需要重试
         *
         * @return
         */
        protected abstract Object doBiz() throws Exception; //预留一个doBiz方法由业务方来实现,在其中书写需要重试的业务代码,然后执行即可
    
        public Object execute() throws InterruptedException {
            for (int i = 0; i < retryTime; i++) {
                try {
                    return doBiz();
                } catch (Exception e) {
                    log.error("业务执行出现异常,e: {}", e);
                    Thread.sleep(sleepTime);
                }
            }
            return null;
        }
    
        public Object submit(ExecutorService executorService) {
            if (executorService == null) {
                throw new IllegalArgumentException("please choose executorService!");
            }
            return executorService.submit((Callable) () -> execute());
        }
    }

    使用示例:

    public void retryDemo() throws InterruptedException {
        Object ans = new RetryTemplate() {
            @Override
            protected Object doBiz() throws Exception {
                int temp = (int) (Math.random() * 10);
                System.out.println(temp);
                if (temp > 3) {
                    throw new Exception("generate value bigger then 3! need retry");
                }
                return temp;
            }
        }.setRetryTime(10).setSleepTime(10).execute();
        System.out.println(ans);
    }
  • 相关阅读:
    Kafka如何保证读写的跨分区与会话
    Kafka topic中的partition的leader选举
    Kafka为什么这么快
    sqoop导入导出
    为什么要用redis去重
    bypass SortShuffleManager的bypass运行机制
    大数据常用端口号
    vector基础
    【拓扑排序】
    【POJ】Crazy Search(hash)
  • 原文地址:https://www.cnblogs.com/panchanggui/p/11232915.html
Copyright © 2020-2023  润新知