Java 异步调用失败导致系统崩溃?这份重试机制救了我

1.异步编程介绍

什么是异步编程

异步编程是一种非阻塞的编程模式,允许程序在等待某个操作完成时继续执行其他任务,而不是一直等待。

当操作完成后,通过回调函数、Future 或事件通知等方式获取结果。

同步 vs 异步对比:

  • 同步:顺序执行,每一步必须等待前一步完成
  • 异步:非阻塞执行,可以同时处理多个任务

Java 中的异步实现方式

1. CompletableFuture (Java 8+)

java 复制代码
// 创建异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 模拟耗时操作
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    return "异步任务结果";
});

// 处理结果
future.thenAccept(result -> System.out.println("结果: " + result));

2. @Async 注解 (Spring Framework)

java 复制代码
@Service
public class AsyncService {
    
    @Async
    public CompletableFuture<String> asyncMethod() {
        // 异步执行的方法
        return CompletableFuture.completedFuture("执行结果");
    }
}

3. 回调函数

java 复制代码
public interface Callback {
    void onSuccess(String result);
    void onError(Exception e);
}

public void asyncOperation(Callback callback) {
    new Thread(() -> {
        try {
            // 模拟操作
            String result = doSomething();
            callback.onSuccess(result);
        } catch (Exception e) {
            callback.onError(e);
        }
    }).start();
}

2.异步编程中的常见错误

2.1 网络相关错误

  • 连接超时
  • 读取超时
  • DNS 解析失败
  • 网络不可达

2.2 资源相关错误

  • 内存不足
  • 线程池耗尽
  • 数据库连接超时

2.3 业务逻辑错误

  • 远程服务返回错误码
  • 数据格式异常
  • 业务规则校验失败

2.4 示例:可能出错的异步方法

java 复制代码
public class UnreliableService {
    
    // 模拟不可靠的远程服务调用
    public CompletableFuture<String> callExternalService(String data) {
        return CompletableFuture.supplyAsync(() -> {
            // 模拟随机错误
            double random = Math.random();
            if (random < 0.3) {
                throw new RuntimeException("网络超时");
            } else if (random < 0.6) {
                throw new RuntimeException("服务端错误");
            }
            return "处理结果: " + data;
        });
    }
}

3. 异步重试机制实现

3.1 手动重试实现

3.1.1 基础重试逻辑

java 复制代码
public class SimpleRetry {
    
    public static <T> CompletableFuture<T> retryAsync(
            Supplier<CompletableFuture<T>> task, 
            int maxAttempts, 
            long delayMs) {
        
        CompletableFuture<T> result = new CompletableFuture<>();
        retryAsync(task, maxAttempts, delayMs, 1, result);
        return result;
    }
    
    private static <T> void retryAsync(
            Supplier<CompletableFuture<T>> task, 
            int maxAttempts, 
            long delayMs, 
            int attempt, 
            CompletableFuture<T> result) {
        
        task.get().whenComplete((response, throwable) -> {
            if (throwable == null) {
                result.complete(response);
            } else if (attempt >= maxAttempts) {
                result.completeExceptionally(throwable);
            } else {
                // 延迟后重试
                CompletableFuture.delayedExecutor(delayMs, TimeUnit.MILLISECONDS)
                    .execute(() -> retryAsync(task, maxAttempts, delayMs, attempt + 1, result));
            }
        });
    }
}

3.2 使用 Spring Retry

3.2.1 添加依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>2.0.0</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.0</version>
</dependency>

3.2.2 配置重试模板

java 复制代码
@Configuration
@EnableRetry
public class RetryConfig {
    
    @Bean
    public RetryTemplate retryTemplate() {
        RetryTemplate template = new RetryTemplate();
        
        // 重试策略:最多重试3次,遇到特定异常时重试
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(3, 
            Collections.singletonMap(RuntimeException.class, true));
        template.setRetryPolicy(retryPolicy);
        
        // 退避策略:每次重试间隔1秒
        FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
        backOffPolicy.setBackOffPeriod(1000);
        template.setBackOffPolicy(backOffPolicy);
        
        return template;
    }
}

3.2.3 使用 @Retryable 注解

java 复制代码
@Service
public class RetryableService {
    
    @Retryable(
        value = {RuntimeException.class},
        maxAttempts = 3,
        backoff = @Backoff(delay = 1000)
    )
    @Async
    public CompletableFuture<String> retryableAsyncMethod() {
        return CompletableFuture.supplyAsync(() -> {
            // 模拟可能失败的操作
            if (Math.random() < 0.7) {
                throw new RuntimeException("临时错误");
            }
            return "成功结果";
        });
    }
    
    @Recover
    public CompletableFuture<String> recover(RuntimeException e) {
        return CompletableFuture.completedFuture("降级结果");
    }
}

3.3 高级重试策略实现

3.3.1 指数退避重试

java 复制代码
public class ExponentialBackoffRetry {
    
    public static <T> CompletableFuture<T> retryWithExponentialBackoff(
            Supplier<CompletableFuture<T>> task,
            int maxAttempts,
            long initialDelay,
            long maxDelay) {
        
        return retryWithExponentialBackoff(task, maxAttempts, initialDelay, maxDelay, 1);
    }
    
    private static <T> CompletableFuture<T> retryWithExponentialBackoff(
            Supplier<CompletableFuture<T>> task,
            int maxAttempts,
            long initialDelay,
            long maxDelay,
            int attempt) {
        
        CompletableFuture<T> future = task.get();
        
        if (attempt >= maxAttempts) {
            return future;
        }
        
        return future.handle((result, throwable) -> {
            if (throwable == null) {
                return CompletableFuture.completedFuture(result);
            } else {
                // 计算退避时间
                long delay = Math.min(maxDelay, initialDelay * (long) Math.pow(2, attempt - 1));
                
                // 添加随机抖动避免惊群效应
                delay = (long) (delay * (0.5 + Math.random()));
                
                CompletableFuture<T> nextAttempt = CompletableFuture
                    .delayedExecutor(delay, TimeUnit.MILLISECONDS)
                    .supplyAsync(() -> 
                        retryWithExponentialBackoff(task, maxAttempts, initialDelay, maxDelay, attempt + 1)
                    )
                    .thenCompose(cf -> cf);
                
                return nextAttempt;
            }
        }).thenCompose(cf -> cf);
    }
}

3.3.2 基于条件的重试

java 复制代码
public class ConditionalRetry {
    
    public static <T> CompletableFuture<T> retryWithCondition(
            Supplier<CompletableFuture<T>> task,
            Predicate<Throwable> shouldRetry,
            int maxAttempts,
            Function<Integer, Long> delayCalculator) {
        
        CompletableFuture<T> result = new CompletableFuture<>();
        retryWithCondition(task, shouldRetry, maxAttempts, delayCalculator, 1, result);
        return result;
    }
    
    private static <T> void retryWithCondition(
            Supplier<CompletableFuture<T>> task,
            Predicate<Throwable> shouldRetry,
            int maxAttempts,
            Function<Integer, Long> delayCalculator,
            int attempt,
            CompletableFuture<T> result) {
        
        task.get().whenComplete((response, throwable) -> {
            if (throwable == null) {
                result.complete(response);
                return;
            }
            
            boolean canRetry = attempt < maxAttempts && shouldRetry.test(throwable);
            
            if (!canRetry) {
                result.completeExceptionally(throwable);
                return;
            }
            
            long delay = delayCalculator.apply(attempt);
            CompletableFuture.delayedExecutor(delay, TimeUnit.MILLISECONDS)
                .execute(() -> 
                    retryWithCondition(task, shouldRetry, maxAttempts, delayCalculator, attempt + 1, result)
                );
        });
    }
}

3.4 完整的重试工具类

java 复制代码
public class AsyncRetryUtil {
    
    /**
     * 异步重试执行器
     */
    public static class AsyncRetryBuilder<T> {
        private final Supplier<CompletableFuture<T>> task;
        private int maxAttempts = 3;
        private Predicate<Throwable> retryCondition = ex -> true;
        private Function<Integer, Long> delayStrategy = attempt -> 1000L * attempt;
        private Consumer<Integer> onRetry = attempt -> {};
        private Function<Throwable, T> fallback = null;
        
        private AsyncRetryBuilder(Supplier<CompletableFuture<T>> task) {
            this.task = task;
        }
        
        public static <T> AsyncRetryBuilder<T> of(Supplier<CompletableFuture<T>> task) {
            return new AsyncRetryBuilder<>(task);
        }
        
        public AsyncRetryBuilder<T> maxAttempts(int maxAttempts) {
            this.maxAttempts = maxAttempts;
            return this;
        }
        
        public AsyncRetryBuilder<T> retryIf(Predicate<Throwable> condition) {
            this.retryCondition = condition;
            return this;
        }
        
        public AsyncRetryBuilder<T> delayStrategy(Function<Integer, Long> strategy) {
            this.delayStrategy = strategy;
            return this;
        }
        
        public AsyncRetryBuilder<T> onRetry(Consumer<Integer> callback) {
            this.onRetry = callback;
            return this;
        }
        
        public AsyncRetryBuilder<T> fallback(Function<Throwable, T> fallback) {
            this.fallback = fallback;
            return this;
        }
        
        public CompletableFuture<T> execute() {
            CompletableFuture<T> result = new CompletableFuture<>();
            execute(1, result);
            return result;
        }
        
        private void execute(int attempt, CompletableFuture<T> result) {
            task.get().whenComplete((response, throwable) -> {
                if (throwable == null) {
                    result.complete(response);
                    return;
                }
                
                boolean shouldRetry = attempt < maxAttempts && retryCondition.test(throwable);
                
                if (!shouldRetry) {
                    if (fallback != null) {
                        try {
                            T fallbackResult = fallback.apply(throwable);
                            result.complete(fallbackResult);
                        } catch (Exception e) {
                            result.completeExceptionally(e);
                        }
                    } else {
                        result.completeExceptionally(throwable);
                    }
                    return;
                }
                
                onRetry.accept(attempt);
                long delay = delayStrategy.apply(attempt);
                
                CompletableFuture.delayedExecutor(delay, TimeUnit.MILLISECONDS)
                    .execute(() -> execute(attempt + 1, result));
            });
        }
    }
}

3.5 使用示例

java 复制代码
public class RetryExample {
    
    public static void main(String[] args) throws Exception {
        // 模拟不可靠的服务
        Supplier<CompletableFuture<String>> unreliableService = () -> 
            CompletableFuture.supplyAsync(() -> {
                double rand = Math.random();
                if (rand < 0.8) {
                    throw new RuntimeException("服务暂时不可用");
                }
                return "服务调用成功";
            });
        
        // 使用重试工具
        CompletableFuture<String> result = AsyncRetryUtil.AsyncRetryBuilder
            .of(unreliableService)
            .maxAttempts(5)
            .retryIf(ex -> ex.getMessage().contains("暂时不可用"))
            .delayStrategy(attempt -> 1000L * attempt) // 线性退避
            .onRetry(attempt -> 
                System.out.println("第 " + attempt + " 次重试..."))
            .fallback(ex -> "降级结果")
            .execute();
        
        result.whenComplete((response, error) -> {
            if (error != null) {
                System.out.println("最终失败: " + error.getMessage());
            } else {
                System.out.println("最终结果: " + response);
            }
        });
        
        // 等待异步操作完成
        Thread.sleep(10000);
    }
}

4. 注意事项

4.1 重试策略选择

  • 网络超时:使用指数退避 + 随机抖动
  • 服务限流:根据返回的等待时间重试
  • 业务错误:根据具体错误码决定是否重试

4.2 避免的问题

  1. 无限重试:设置最大重试次数
  2. 资源耗尽:合理控制重试频率
  3. 雪崩效应:使用断路器模式配合重试
  4. 重复操作:确保操作的幂等性

4.3 监控和日志

java 复制代码
// 添加重试监控
public class RetryMonitor {
    private static final MeterRegistry meterRegistry = new SimpleMeterRegistry();
    
    public static void recordRetry(String operation, int attempt) {
        Counter.builder("retry.attempts")
            .tag("operation", operation)
            .register(meterRegistry)
            .increment();
    }
    
    public static void recordSuccess(String operation, long duration) {
        Timer.builder("retry.duration")
            .tag("operation", operation)
            .tag("status", "success")
            .register(meterRegistry)
            .record(duration, TimeUnit.MILLISECONDS);
    }
}

具体业务场景选择合适的重试策略,提高系统的容错能力和稳定性。

本文首发于公众号:程序员大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期内容

写前端久了,我用 Node.js 给自己造了几个省力小工具

我也是写了很久 TypeScript,才意识到这些写法不对

ThreadLocal 在实际项目中的 6 大用法,原来可以这么简单

重构了20个SpringBoot项目后,总结出这套稳定高效的架构设计

相关推荐
Penge6668 分钟前
Go 接口编译期断言
后端
我是一颗柠檬17 分钟前
【MySQL全面教学】MySQL面试高频考点汇总Day15(2026年)
数据库·后端·mysql·面试
橙淮1 小时前
并发编程(六)
java·jvm
拽着尾巴的鱼儿1 小时前
springboot openfeign 自定义feign 接口重试机制
java·spring boot·后端
白露与泡影1 小时前
2026大厂Java面试题大全!牛客网最新版
java·开发语言
Ceelog1 小时前
久坐党自救指南:屏幕前 8 小时,身体到底在经历什么
前端·后端
EntyIU2 小时前
JVM内存与GC笔记
java·jvm·笔记
XS0301062 小时前
并发编程 六
java·后端
yaoxin5211232 小时前
419. 现代 Java IO 最佳实践 - 写入文本文件
java·windows·python
雪宫街道2 小时前
synchronized 锁的范围:对象锁、类锁与代码块锁
java·jvm·后端·面试