Java 中优雅的进行重试
本文介绍 Java 中如何优雅的进行重试,首先介绍重试的意义,其次介绍 Spring Retry 和 Guava Retry 两大重试工具包的使用

# Java 中优雅的进行重试

# 重试的意义

  1. 为什么需要重试

    在软件开发过程中,有时会遇到方法调用异常的偶发性问题,绝大多数是因为网络不稳定导致的,这种情况需要进行重试

    重试可以使处理更加健壮,减少失败的可能性

  2. 重试的一般思路

    // 最大重试次数
    int maxAttempts = 4;
    for (int i = 0; i < maxAttempts; i++) {
        try {
            // 业务代码
            // 成功执行完毕 break
        } catch (Exception e) {
            // 判断是否需要重试
            // 是否需要休眠
        }
    }
    // 判断最终是否成功,进行通知、告警等其他兜底方案的处理
    
  3. 如何优雅的进行重试

    重试是一种通用的代码逻辑,可以使用模板模式进行封装

    目前已有可靠的重试工具包以供使用,如 Spring Retry 和 Guava Retry

# Spring Retry

  1. Spring Retry 原是 Spring Batch 批处理组件的重试工具部分,后独立为 Spring 的重试工具包

  2. Spring Retry 提供了声明式的注解,方便在 Spring AOP 中使用,也提供了 RetryTemplate 编程式的调用方式

  3. Spring Retry 声明式

    @Retryable 注解

    value       指定需要重试的异常类型
    maxAttempts 指定最大重试次数
    backoff     定义重试时的延迟规则
    

    @Backoff 注解

    delay      重试时间间隔
    multiplier 每次重试的时间间隔要乘以该数
    

    使用示例

    /**
     * 声明式
     * 遇到 IllegalArgumentException 时重试
     * 重试 4 次
     * 初次重试间隔 1 秒,后面每次乘以 2
     */
    @Retryable(value = IllegalArgumentException.class, maxAttempts = 4,
               backoff = @Backoff(delay = 1000L, multiplier = 2))
    public long testRetryableAnnotation() {
        long round = Math.round(Math.random() * 5);
        if (round <= 1) {
            throw new UnsupportedOperationException("随机数小于等于 1");
        }
        if (round != 4) {
            throw new IllegalArgumentException("随机数不等于 4");
        }
        return round;
    }
    
  4. Spring Retry 编程式

    RetryTemplate

    Spring Retry 提供的重试模板,需要指定 RetryPolicy 和 BackOffPolicy 
    

    RetryPolicy 重试策略

    SimpleRetryPolicy              最大次数策略
    ExceptionClassifierRetryPolicy 不同异常设置不同策略
    

    BackOffPolicy 延迟策略

    FixedBackOffPolicy         固定时间重试
    ExponentialBackOffPolicy   指数退避策略
    

    使用示例

    public long testRetryTemplate() throws Throwable {
        RetryTemplate retryTemplate = new RetryTemplate();
        // 重试规则
        // 针对不同异常定制重试策略
        ExceptionClassifierRetryPolicy exceptionRetryPolicy = new ExceptionClassifierRetryPolicy();
        HashMap<Class<? extends Throwable>, RetryPolicy> policyMap = new HashMap<>();
        // 定制 IllegalArgumentException 的策略
        SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
        simpleRetryPolicy.setMaxAttempts(4);
        policyMap.put(IllegalArgumentException.class, simpleRetryPolicy);
        exceptionRetryPolicy.setPolicyMap(policyMap);
        retryTemplate.setRetryPolicy(exceptionRetryPolicy);
        // 延迟规则
        // 初次重试间隔 1 秒,后面每次乘以 2
        ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
        backOffPolicy.setInitialInterval(1000L);
        backOffPolicy.setMultiplier(2);
        retryTemplate.setBackOffPolicy(backOffPolicy);
        
        Long res = retryTemplate.execute((RetryCallback<Long, Throwable>) context -> {
            long round = Math.round(Math.random() * 5);
            if (round <= 1) {
                throw new UnsupportedOperationException("随机数小于等于 1");
            }
            if (round != 4) {
                throw new IllegalArgumentException("随机数不等于 4");
            }
            return round;
        });
        return res;
    }
    
  5. Spring Retry 重试失败补偿

    Spring Retry 提供了 @Recover 方法,用于在重试逻辑抛出异常后进行补偿

    在旧版本中需要建一个有两个方法的类,一个方法使用 @Retryable 注解,另一个方法使用 @Recover 注解

    在 1.3.0 版中 @Retryable 增加了 recover 属性,可以直接指定 @Recover 标注的方法,该方法的第一个参数需要是 Throwable 或其子类

    @Retryable(value = IllegalArgumentException.class, maxAttempts = 4,
               backoff = @Backoff(delay = 1000L, multiplier = 2), recover = "testRecover")
    public long testRetryableAnnotation(String str) {
       //
    }
    
    @Recover
    public long testRecover(Throwable t, String str) {
        log.error("recover", t);
        return 9999;
    }
    

# Guava Retry

  1. Guava Retry 是基于 Guava 核心库实现的重试工具包

  2. 引入 Guava Retry

    <dependency>
        <groupId>com.github.rholder</groupId>
        <artifactId>guava-retrying</artifactId>
        <version>2.0.0</version>
    </dependency>
    
  3. 使用 Guava Retry

    使用 RetryerBuilder 创建 Retryer 重试器

    retryIfExceptionOfType 可以定义重试的异常种类

    withWaitStrategy 定义等待策略

    withStopStrategy 定义停止策略

    需要重试的业务逻辑使用 Callable 包装,使用 Retryer call 执行

    public long testGuavaRetry() throws Throwable {
        // 定义重试器
        Retryer<Long> retryer = RetryerBuilder.<Long>newBuilder()
            .retryIfExceptionOfType(IllegalArgumentException.class)
            .withWaitStrategy(WaitStrategies.fixedWait( 1L, TimeUnit.SECONDS))
            .withStopStrategy(StopStrategies.stopAfterAttempt(4))
            .build();
    
        // 定义需要重试的逻辑
        Callable<Long> callable = () -> {
            long round = Math.round(Math.random() * 5);
            if (round <= 1) {
                throw new UnsupportedOperationException("随机数小于等于 1");
            }
            if (round != 4) {
                throw new IllegalArgumentException("随机数不等于 4");
            }
            return round;
        };
        // 利用重试器调用请求
        return retryer.call(callable);
    }
    
Comment here, be cool~

Copyright © 2020 CadeCode

Theme 2zh powered by VuePress

本页访问次数 0

Loading