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项目后,总结出这套稳定高效的架构设计

相关推荐
Hx_Ma163 小时前
SpringMVC框架提供的转发和重定向
java·开发语言·servlet
期待のcode4 小时前
原子操作类LongAdder
java·开发语言
舟舟亢亢4 小时前
Java集合笔记总结
java·笔记
小酒窝.5 小时前
【多线程】多线程打印ABC
java
乡野码圣5 小时前
【RK3588 Android12】RCU机制
java·jvm·数据库
JAVA+C语言5 小时前
如何优化 Java 多主机通信的性能?
java·开发语言·php
编程彩机6 小时前
互联网大厂Java面试:从分布式架构到大数据场景解析
java·大数据·微服务·spark·kafka·分布式事务·分布式架构
小酒窝.6 小时前
【多线程】多线程打印1~100
java·多线程
君爱学习7 小时前
基于SpringBoot的选课调查系统
java
APIshop7 小时前
Java 实战:调用 item_search_tmall 按关键词搜索天猫商品
java·开发语言·数据库