基于Reactor响应式流实现单机版本的异步非阻塞重试策略

160 阅读3分钟

使用场景

参考Soul网关Divide插件实现,向下游发送HTTP请求,对方服务不稳定需要失败重试,要求重试过程中不阻塞当前线程,直到重试完成获取重试结果。

eg:TimeoutException、ConnectTimeoutException、ReadTimeoutException、IllegalStateException等。

特点:

  • 异步非阻塞,不影响主线程

  • 支持自定义重试次数、重试间隔、重试上限

  • 支持按任务失败条件出发重试(eg: 指定异常重试)

  • 支持异步获取任务执行结果,直到重试成功或者重试超限

  • 轻量级,不依赖第三方重试调度中间件(如MQ,ScheduleX)

使用案例

public static void main(String[] args) {

    for (int i = 1; i <= 20; i++) {
        Mono<String> result = RetryUtils.execute(String.format("第【%s】次调用", i));
        // 订阅结果
        result.subscribe(
                // 成功时处理结果
                data -> System.out.println("Received: " + data),
                // 所有重试都失败时处理错误
                error -> System.err.println("Final error: " + error.getMessage()),
                // 流完成时调用
                () -> System.out.println("Completed")
        );
        try {
            Thread.sleep(10000); // 给异步任务一点时间完成
            System.out.println("===========================分隔符===========================");
        } catch (InterruptedException ignored) {
        }
    }
}

测试结果

使用默认重试策略时,命中IllegalStateException异常规则,重试间隔500ms,最失败大重试3次,如果重试过程中成功,则直接终止重试。

  • CASE1 第一次成功:

    Processing item: 执行成功: [第【1】次调用] Received: 执行成功: [第【1】次调用] Completed 重试结束,成功完成 ===========================分隔符===========================

  • CASE2 执行失败,触发重试,重试成功:

    Error occurred: 执行失败... [第【2】次调用] 执行重试,重试次数: 1 Error occurred: 执行失败... [第【2】次调用] 执行重试,重试次数: 2 Processing item: 执行成功: [第【2】次调用] Received: 执行成功: [第【2】次调用] Completed 重试结束,成功完成

  • CASE3 执行失败,触发重试,重试次数达到上限:

    Error occurred: 执行失败... [第【10】次调用] 执行重试,重试次数: 1 Error occurred: 执行失败... [第【10】次调用] 执行重试,重试次数: 2 Error occurred: 执行失败... [第【10】次调用] 执行重试,重试次数: 3 Error occurred: 执行失败... [第【10】次调用] Final error: 重试超限 重试结束,最终失败

具体实现

基于Reactor响应式流 + 自定义重试策略实现。

PS:如需要更灵活的重试策略和配置,建议自行封装~

/**
 * 重试工具类
 */
public class RetryUtils {

    /**
     * 记录重试次数
     */
    private static final AtomicInteger retryCount = new AtomicInteger(0);

    /**
     * 执行并返回响应流
     */
    public static Mono<String> execute(Object... param) {
        return execute(RetryBackoffSpecEnum.DEFAULT_BACKOFF, param);
    }

    /**
     * 执行并返回响应流(指定重试策略)
     */
    public static Mono<String> execute(
                  RetryBackoffSpecEnum backoffSpecEnum, Object... param) {
        return retryWithBackoff(doExecute(param), backoffSpecEnum);
    }

    private static Mono<String> doExecute(Object... param) {
        return Mono.defer(() -> {
            // 模拟50%的异常情况,指定异常重试
            if (Math.random() < 0.5) {
                return Mono.error(new IllegalStateException("执行失败... " + Arrays.toString(param)));
            } else {
                return Mono.just("执行成功: " + Arrays.toString(param));
            }
        });
    }

    /**
     * @param mono 需要重试的操作
     * @return 返回包含重试结果的Mono
     */
    public static <T> Mono<T> retryWithBackoff(Mono<T> mono, RetryBackoffSpecEnum backoffSpecEnum) {
        RetryBackoffSpec backoffSpec = holders.get(backoffSpecEnum);

        return mono.doOnNext(i -> System.out.println("Processing item: " + i))
                .doOnError(e -> System.err.println("Error occurred: " + e.getMessage()))
                .retryWhen(
                        backoffSpec.doAfterRetry(retrySignal -> doRetry())
                )
                .doFinally(signalType -> {
                    if (signalType == SignalType.ON_ERROR) {
                        System.err.println("重试结束,最终失败");
                    } else if (signalType == SignalType.ON_COMPLETE) {
                        System.out.println("重试结束,成功完成");
                    }
                    resetRetryCount();
                });
    }

    public enum RetryBackoffSpecEnum {
        /**
         * 默认重试
         */
        DEFAULT_BACKOFF,

        /**
         * 固定重试
         */
        FIXED_BACKOFF,

        /**
         * 自定义重试
         */
        CUSTOM_BACKOFF,
    }

    private static final Map<RetryBackoffSpecEnum, RetryBackoffSpec> holders = new HashMap<>();

    static {
        holders.put(RetryBackoffSpecEnum.DEFAULT_BACKOFF, initDefaultBackoff());
        holders.put(RetryBackoffSpecEnum.FIXED_BACKOFF, initFixedBackoff());
        holders.put(RetryBackoffSpecEnum.CUSTOM_BACKOFF, initCustomBackoff());
    }

    private static RetryBackoffSpec initCustomBackoff() {
        // TODO 自己实现
        return null;
    }

    private static RetryBackoffSpec initFixedBackoff() {
        return Retry.fixedDelay(5, Duration.ofSeconds(2));
    }

    private static RetryBackoffSpec initDefaultBackoff() {
        return Retry.backoff(3, Duration.ofMillis(500))
                .maxBackoff(Duration.ofSeconds(5))
                // 只对瞬时错误进行重试
                .transientErrors(true)
                // 添加 50% 的随机抖动到每次重试的延迟时间
                .jitter(0.5d)
                .filter(t -> t instanceof IllegalStateException)
                // 当达到最大重试次数后抛出一个指定的异常
                .onRetryExhaustedThrow((retryBackoffSpecErr, retrySignal) -> {
                    throw new IllegalStateException("重试超限");
                });
    }

    private static void doRetry() {
        retryCount.incrementAndGet();
        System.out.println("执行重试,重试次数: " + retryCount.get());
    }

    private static void resetRetryCount() {
        retryCount.set(0);
    }
}