RetryTemplate 实战场景示例 -- 结合文档
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
);
}
);
}
}
面试要点总结
每个场景对应的问题
-
数据库重试:如何避免重试导致的数据重复插入?(结合幂等性)
-
远程调用重试:如何防止重试风暴?(指数退避 + 熔断)
-
文件操作重试:为什么文件操作不适合指数退避?(IO操作特性)
-
组合策略:如何根据异常类型动态调整重试策略?
-
业务集成:如何将重试模板与现有业务框架集成?
实战
"在批量任务中,我使用 Spring Retry 的 RetryTemplate 实现了分层重试策略。针对数据库操作,我配置了指数退避策略避免压垮数据库;针对远程API调用,我使用随机抖动退避防止重试共振;针对文件操作,我使用固定间隔重试。同时,我通过
ExceptionClassifierRetryPolicy实现了异常分类重试,确保只有瞬时故障才会被重试。"
这些场景直接对应你批量任务中台的真实需求,建议优先实现场景一(数据库重试)和场景五(业务集成),这是最核心的应用场景。
(。・v・。)
喜欢这篇文章吗?欢迎分享到你的微博、QQ群,并关注我们的微博,谢谢支持。