在分布式系统开发中,网络抖动、服务短暂不可用、数据库连接超时等问题几乎无法避免。我经历过一个支付系统对接第三方银行的案例:在交易高峰期,大约每100次调用会有3-5次因网络波动失败,但其中80%的失败请求在50毫秒后重试即可成功。这就是重试机制存在的意义——通过智能的重复尝试,将临时性故障对业务的影响降到最低。
Spring Retry作为Spring生态中的重试抽象层,相比手动编写while循环实现重试,提供了三大核心优势:
java复制@Retryable(
value = {SQLException.class, IOException.class},
maxAttempts = 4,
backoff = @Backoff(delay = 1000, multiplier = 2)
)
public void processPayment(PaymentRequest request) {
// 支付处理逻辑
}
value参数:不是所有异常都值得重试。像IllegalArgumentException这种业务逻辑错误,重试毫无意义。建议只对网络超时、数据库死锁等临时性异常配置重试。
maxAttempts陷阱:实际重试次数=初始调用+重试次数。设置maxAttempts=3意味着最多执行1次初始调用+2次重试。这个细节在监控统计时需要特别注意。
backoff算法:指数退避是避免雪崩的利器。上述配置表示:第一次重试等待1秒,第二次2秒,第三次4秒。对于调用外部API的场景,建议初始延迟不低于500ms。
Spring Retry通过RetryContext保存重试过程中的状态信息,开发人员可以通过RetrySynchronizationManager获取当前上下文:
java复制RetryContext context = RetrySynchronizationManager.getContext();
if(context != null) {
log.info("当前重试次数: {}", context.getRetryCount());
}
这个机制特别适合以下场景:
Spring Retry可以与Spring Circuit Breaker模式结合使用。当失败率达到阈值时,直接熔断不再重试:
java复制@Retryable(
include = RemoteAccessException.class,
maxAttempts = 3,
backoff = @Backoff(delay = 1000),
stateful = true
)
@CircuitBreaker(
maxAttempts = 5,
resetTimeout = 30000
)
public String callExternalService() {
// 外部服务调用
}
在@Configuration类中定义全局策略,避免每个方法重复配置:
java复制@Bean
public RetryTemplate retryTemplate() {
RetryTemplate template = new RetryTemplate();
// 重试策略
SimpleRetryPolicy policy = new SimpleRetryPolicy();
policy.setMaxAttempts(3);
// 退避策略
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(500);
backOffPolicy.setMultiplier(2.0);
backOffPolicy.setMaxInterval(5000);
template.setRetryPolicy(policy);
template.setBackOffPolicy(backOffPolicy);
return template;
}
实现RetryPolicy接口可以完全控制重试行为:
java复制public class CustomRetryPolicy extends SimpleRetryPolicy {
@Override
public boolean canRetry(RetryContext context) {
if(context.getLastThrowable() instanceof RateLimitExceededException) {
// 当遇到限流异常时不再重试
return false;
}
return super.canRetry(context);
}
}
RetryListener接口允许我们在重试生命周期插入自定义逻辑:
java复制public class MetricsRetryListener implements RetryListener {
@Override
public <T, E extends Throwable> boolean open(RetryContext context,
RetryCallback<T, E> callback) {
// 记录开始时间等初始化操作
return true;
}
@Override
public <T, E extends Throwable> void onError(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
// 收集失败指标
Metrics.counter("retry.errors", "exception", throwable.getClass().getName())
.increment();
}
}
同步重试在以下场景会导致严重问题:
解决方案:
java复制@Retryable(
maxAttempts = 3,
backoff = @Backoff(delay = 1000),
async = true // 使用异步重试
)
public CompletableFuture<Void> asyncOperation() {
// 异步操作逻辑
}
重试机制必须与幂等设计配合使用。一个典型的订单支付流程:
sql复制CREATE TABLE payments (
id BIGINT PRIMARY KEY,
order_id VARCHAR(32) UNIQUE, -- 业务唯一标识
status VARCHAR(20)
);
重试与事务的交互需要特别注意:
通过Micrometer暴露关键指标:
java复制MeterRegistry registry = new PrometheusMeterRegistry();
RetryMetrics.retry("service.call", registry)
.bindTo(RetryTemplate.builder()
.maxAttempts(3)
.build());
核心指标包括:
使用MDC实现请求链路的追踪:
java复制@Retryable(listeners = "mdcRetryListener")
public void process() {
MDC.put("traceId", UUID.randomUUID().toString());
// 业务逻辑
}
在logback.xml中配置:
xml复制<pattern>%d{ISO8601} [%X{traceId}] %-5level %logger{36} - %msg%n</pattern>
根据指标设置合理告警:
根据系统负载自动调整重试参数:
java复制@Scheduled(fixedRate = 60000)
public void adjustRetryPolicy() {
double load = SystemLoadCalculator.getCurrentLoad();
RetryPolicy policy = new SimpleRetryPolicy();
policy.setMaxAttempts(load > 0.8 ? 2 : 3);
retryTemplate.setRetryPolicy(policy);
}
在微服务架构中,使用Saga模式管理分布式事务的重试:
使用Spring Boot Test验证重试行为:
java复制@Test
public void testRetry() {
given(remoteService.call())
.willThrow(new RuntimeException())
.willThrow(new RuntimeException())
.willReturn("success");
String result = testService.process();
then(remoteService).should(times(3)).call();
assertThat(result).isEqualTo("success");
}