首页 » java

RetryTemplate 实战场景示例 -- 结合文档

   发表于:java评论 (0)   热度:2

RetryTemplate 实战场景示例(结合文档)

 

以下实战示例结合文档查看


场景一:数据库瞬时故障重试(最常用)

痛点:批量导入10万条数据,中间某条数据插入时数据库连接闪断,导致整个任务失败。

代码实现

@Component
@Slf4j
public class DatabaseRetryExample {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    /**
     * 配置针对数据库异常的重试模板
     */
    private RetryTemplate createDatabaseRetryTemplate() {
        RetryTemplate template = new RetryTemplate();
        
        // 1. 重试策略:只重试数据库相关异常
        Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>();
        retryableExceptions.put(SQLException.class, true);          // SQL异常
        retryableExceptions.put(DataAccessException.class, true);  // Spring数据访问异常
        retryableExceptions.put(TransientDataAccessResourceException.class, true); // 瞬时数据访问异常
        
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(4, retryableExceptions); // 最多重试3次
        template.setRetryPolicy(retryPolicy);
        
        // 2. 退避策略:指数退避,避免压垮数据库
        ExponentialBackOffPolicy backOff = new ExponentialBackOffPolicy();
        backOff.setInitialInterval(1000L);   // 初始1秒
        backOff.setMultiplier(2.0);          // 每次翻倍
        backOff.setMaxInterval(10000L);      // 最大10秒
        template.setBackOffPolicy(backOff);
        
        return template;
    }
    
    /**
     * 批量保存数据(带重试)
     */
    public BatchTaskResult saveBatchWithRetry(List<Map<String, String>> chunk, String taskCode) {
        RetryTemplate retryTemplate = createDatabaseRetryTemplate();
        
        return retryTemplate.execute(
            // 重试逻辑
            context -> {
                if (context.getRetryCount() > 0) {
                    log.warn("数据库操作重试: taskCode={}, 重试次数={}", 
                            taskCode, context.getRetryCount());
                }
                
                // 执行数据库插入
                int savedCount = batchInsert(chunk);
                return BatchTaskResult.ok(chunk.size(), savedCount);
            },
            // 兜底逻辑(全失败)
            context -> {
                Throwable lastException = context.getLastThrowable();
                log.error("数据库操作重试耗尽: taskCode={}", taskCode, lastException);
                return BatchTaskResult.fail(
                    "数据库保存失败,已重试3次", 
                    lastException  // 保留完整异常链(符合设计约束)
                );
            }
        );
    }
    
    private int batchInsert(List<Map<String, String>> chunk) {
        // 批量插入逻辑
        String sql = "INSERT INTO orders (order_id, amount) VALUES (?, ?)";
        // ... 执行插入
        return chunk.size();
    }
}

场景二:远程API调用重试(分布式场景)

痛点:向第三方支付系统推送批量订单,网络不稳定导致调用超时,需要重试。

代码实现

@Component
@Slf4j
public class RemoteApiRetryExample {
    
    @Autowired
    private RestTemplate restTemplate;
    
    /**
     * 配置针对远程调用的重试模板
     */
    private RetryTemplate createRemoteCallRetryTemplate() {
        RetryTemplate template = new RetryTemplate();
        
        // 1. 重试策略:只重试网络相关异常
        Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>();
        retryableExceptions.put(SocketTimeoutException.class, true);    // 连接超时
        retryableExceptions.put(ConnectException.class, true);           // 连接失败
        retryableExceptions.put(HttpServerErrorException.class, true);   // 5xx服务器错误
        retryableExceptions.put(HttpClientErrorException.class, false);  // 4xx客户端错误(不重试)
        
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(3, retryableExceptions);
        template.setRetryPolicy(retryPolicy);
        
        // 2. 退避策略:指数退避 + 随机抖动
        ExponentialRandomBackOffPolicy backOff = new ExponentialRandomBackOffPolicy();
        backOff.setInitialInterval(2000L);   // 初始2秒
        backOff.setMultiplier(2.0);          // 每次翻倍
        backOff.setMaxInterval(30000L);      // 最大30秒
        template.setBackOffPolicy(backOff);
        
        return template;
    }
    
    /**
     * 推送订单到第三方系统(带重试)
     */
    public BatchTaskResult pushOrdersWithRetry(List<String> orderIds, String taskCode) {
        RetryTemplate retryTemplate = createRemoteCallRetryTemplate();
        
        return retryTemplate.execute(
            context -> {
                if (context.getRetryCount() > 0) {
                    log.warn("远程API调用重试: taskCode={}, 重试次数={}", 
                            taskCode, context.getRetryCount());
                }
                
                // 调用第三方API
                String url = "https://api.payment.com/push-orders";
                ResponseEntity<String> response = restTemplate.postForEntity(url, orderIds, String.class);
                
                if (response.getStatusCode().is2xxSuccessful()) {
                    return BatchTaskResult.ok(orderIds.size(), orderIds.size());
                } else {
                    throw new RuntimeException("API调用失败: " + response.getStatusCode());
                }
            },
            context -> {
                Throwable lastException = context.getLastThrowable();
                log.error("远程API调用重试耗尽: taskCode={}", taskCode, lastException);
                return BatchTaskResult.fail(
                    "第三方API调用失败", 
                    lastException
                );
            }
        );
    }
}

场景三:文件处理重试(大文件场景)

痛点:批量导出大文件时,磁盘IO异常导致写入失败,需要重试。

代码实现

@Component
@Slf4j
public class FileProcessRetryExample {
    
    /**
     * 配置针对文件操作的重试模板
     */
    private RetryTemplate createFileRetryTemplate() {
        RetryTemplate template = new RetryTemplate();
        
        // 1. 重试策略:只重试IO异常
        Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>();
        retryableExceptions.put(IOException.class, true);           // IO异常
        retryableExceptions.put(FileNotFoundException.class, true); // 文件未找到
        retryableExceptions.put(AccessDeniedException.class, false); // 权限错误(不重试)
        
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(2, retryableExceptions);
        template.setRetryPolicy(retryPolicy);
        
        // 2. 退避策略:固定间隔(文件操作不适合指数退避)
        FixedBackOffPolicy backOff = new FixedBackOffPolicy();
        backOff.setBackOffPeriod(3000L);  // 固定3秒
        template.setBackOffPolicy(backOff);
        
        return template;
    }
    
    /**
     * 导出文件(带重试)
     */
    public BatchTaskResult exportFileWithRetry(List<Map<String, String>> data, String filePath, String taskCode) {
        RetryTemplate retryTemplate = createFileRetryTemplate();
        
        return retryTemplate.execute(
            context -> {
                if (context.getRetryCount() > 0) {
                    log.warn("文件导出重试: taskCode={}, 重试次数={}", 
                            taskCode, context.getRetryCount());
                }
                
                // 写入文件
                writeToFile(data, filePath);
                return BatchTaskResult.ok(data.size(), data.size());
            },
            context -> {
                Throwable lastException = context.getLastThrowable();
                log.error("文件导出重试耗尽: taskCode={}", taskCode, lastException);
                return BatchTaskResult.fail(
                    "文件导出失败", 
                    lastException
                );
            }
        );
    }
    
    private void writeToFile(List<Map<String, String>> data, String filePath) throws IOException {
        // 文件写入逻辑
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
            for (Map<String, String> row : data) {
                writer.write(row.toString());
                writer.newLine();
            }
        }
    }
}

场景四:组合重试策略(复杂业务场景)

痛点:一个任务既可能遇到数据库问题,也可能遇到网络问题,需要不同的重试策略。

代码实现

@Component
@Slf4j
public class CompositeRetryExample {
    
    /**
     * 配置组合重试策略
     */
    private RetryTemplate createCompositeRetryTemplate() {
        RetryTemplate template = new RetryTemplate();
        
        // 1. 组合重试策略:数据库异常重试3次,网络异常重试2次
        ExceptionClassifierRetryPolicy compositePolicy = new ExceptionClassifierRetryPolicy();
        
        // 数据库异常策略
        SimpleRetryPolicy dbPolicy = new SimpleRetryPolicy(4, 
            Map.of(SQLException.class, true));
        
        // 网络异常策略
        SimpleRetryPolicy networkPolicy = new SimpleRetryPolicy(3, 
            Map.of(SocketTimeoutException.class, true, ConnectException.class, true));
        
        compositePolicy.setPolicyMap(Map.of(
            SQLException.class, dbPolicy,
            SocketTimeoutException.class, networkPolicy,
            ConnectException.class, networkPolicy
        ));
        
        template.setRetryPolicy(compositePolicy);
        
        // 2. 退避策略:根据异常类型动态调整
        ExponentialBackOffPolicy backOff = new ExponentialBackOffPolicy();
        backOff.setInitialInterval(1000L);
        backOff.setMultiplier(2.0);
        backOff.setMaxInterval(60000L);
        template.setBackOffPolicy(backOff);
        
        return template;
    }
    
    /**
     * 复杂业务逻辑(带组合重试)
     */
    public BatchTaskResult complexBusinessWithRetry(String taskCode) {
        RetryTemplate retryTemplate = createCompositeRetryTemplate();
        
        return retryTemplate.execute(
            context -> {
                log.info("执行复杂业务逻辑: taskCode={}, 重试次数={}", 
                        taskCode, context.getRetryCount());
                
                // 1. 查询数据库
                List<Map<String, String>> data = queryFromDatabase();
                
                // 2. 调用远程API
                String apiResult = callRemoteApi();
                
                // 3. 写入文件
                writeToFile(data, "output.txt");
                
                return BatchTaskResult.ok(data.size(), data.size());
            },
            context -> {
                Throwable lastException = context.getLastThrowable();
                log.error("复杂业务重试耗尽: taskCode={}", taskCode, lastException);
                return BatchTaskResult.fail(
                    "复杂业务执行失败", 
                    lastException
                );
            }
        );
    }
}

场景五:结合你的批量任务执行引擎

将重试模板集成到你现有的 BatchTaskRetryHandler

@Component
@Slf4j
public class BatchTaskRetryHandler {
    
    /**
     * 根据任务类型创建不同的重试模板
     */
    public RetryTemplate createRetryTemplate(BatchTaskDefinition definition) {
        RetryTemplate template = new RetryTemplate();
        
        // 1. 根据任务类型设置重试策略
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(definition.getRetry() + 1);
        template.setRetryPolicy(retryPolicy);
        
        // 2. 根据任务类型设置退避策略
        if (definition.getTaskType() == BatchTaskType.EXPORT) {
            // 导出任务:固定间隔
            FixedBackOffPolicy backOff = new FixedBackOffPolicy();
            backOff.setBackOffPeriod(2000L);
            template.setBackOffPolicy(backOff);
        } else {
            // 其他任务:指数退避
            ExponentialBackOffPolicy backOff = new ExponentialBackOffPolicy();
            backOff.setInitialInterval(1000L);
            backOff.setMultiplier(2.0);
            backOff.setMaxInterval(30000L);
            template.setBackOffPolicy(backOff);
        }
        
        return template;
    }
    
    /**
     * 执行带重试的任务
     */
    public BatchTaskResult executeWithRetry(
            Supplier<BatchTaskResult> action, 
            BatchTaskDefinition definition) {
        
        RetryTemplate retryTemplate = createRetryTemplate(definition);
        
        return retryTemplate.execute(
            context -> {
                if (context.getRetryCount() > 0) {
                    log.warn("任务重试: code={}, 重试次数={}", 
                            definition.getCode(), context.getRetryCount());
                }
                return action.get();
            },
            context -> {
                Throwable lastException = context.getLastThrowable();
                log.error("任务重试耗尽: code={}", definition.getCode(), lastException);
                return BatchTaskResult.fail(
                    "任务执行失败,已重试" + definition.getRetry() + "次",
                    lastException
                );
            }
        );
    }
}

面试要点总结

每个场景对应的问题

  1. 数据库重试:如何避免重试导致的数据重复插入?(结合幂等性)

  2. 远程调用重试:如何防止重试风暴?(指数退避 + 熔断)

  3. 文件操作重试:为什么文件操作不适合指数退避?(IO操作特性)

  4. 组合策略:如何根据异常类型动态调整重试策略?

  5. 业务集成:如何将重试模板与现有业务框架集成?

实战

"在批量任务中,我使用 Spring Retry 的 RetryTemplate 实现了分层重试策略。针对数据库操作,我配置了指数退避策略避免压垮数据库;针对远程API调用,我使用随机抖动退避防止重试共振;针对文件操作,我使用固定间隔重试。同时,我通过 ExceptionClassifierRetryPolicy实现了异常分类重试,确保只有瞬时故障才会被重试。"

这些场景直接对应你批量任务中台的真实需求,建议优先实现场景一(数据库重试)场景五(业务集成),这是最核心的应用场景。

(。・v・。)
喜欢这篇文章吗?欢迎分享到你的微博、QQ群,并关注我们的微博,谢谢支持。