目 录CONTENT

文章目录

Spring重试机制详解:从入门到精通

在等晚風吹
2025-12-17 / 0 评论 / 0 点赞 / 1 阅读 / 0 字 / 正在检测是否收录...

Spring重试机制详解:从入门到精通

📱 在分布式系统中,网络故障、服务超时是常见问题。Spring Retry通过优雅的重试机制,帮助我们构建更加稳健的应用系统。本文将深入探讨Spring重试机制的原理、使用方法和最佳实践。


一、概述

1.1 什么是重试机制

重试机制是指当某个操作失败时,系统会根据预定义的策略自动再次尝试执行该操作。在分布式系统中,这是必不可少的容错手段。

重试适用场景:

  • 🌐 网络波动导致的临时故障(如超时、连接被重置)
  • 🔄 远程服务的间歇性故障(毛刺故障)
  • 💾 数据库连接池耗尽的临时情况
  • ⚡ 竞态条件导致的写冲突
  • 📦 消息队列不可用的短暂时间
  • 🔐 分布式锁争夺导致的冲突

1.2 重试机制的核心要素

一个完整的重试机制通常包含以下要素:

要素说明示例
重试次数最多尝试执行多少次3次、5次
延迟策略重试之间的等待时间固定延迟、指数退避
异常判断判断异常是否可重试IOException可重试,业务异常不可重试
超时控制单次重试和总重试的超时单次10秒,总计60秒
回调处理重试成功/失败时的处理记录日志、发送告警
幂等性保证重试操作可以安全重复执行使用业务ID去重

1.3 为什么需要Spring Retry

在微服务架构中,服务间调用会面临各种故障:

┌─────────────┐
│  客户端      │
└──────┬──────┘
       │ 网络故障、超时、限流
       │ ↓
┌─────────────────────────────┐
│   Spring Retry 重试框架      │
│  ┌─────────────────────────┐ │
│  │ 1. 判断是否需要重试      │ │
│  │ 2. 计算延迟时间         │ │
│  │ 3. 执行重试             │ │
│  │ 4. 监控重试过程         │ │
│  └─────────────────────────┘ │
└─────────────────────────────┘
       │ 恢复 或 最终失败
       ↓
  业务逻辑继续

Spring Retry通过AOP代理和模板方法模式,为我们提供了:

  • ✅ 声明式编程(注解方式)
  • ✅ 编程式方式(模板类)
  • ✅ 灵活的重试策略
  • ✅ 丰富的延迟算法
  • ✅ 完整的监听器支持

二、Spring Retry框架核心概念

2.1 Spring Retry简介

Spring Retry是Spring框架中提供的重试支持库。它通过AOP代理和模板方法设计模式,为应用程序提供了强大的重试功能。

依赖配置:

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>2.0.0</version>
</dependency>

<!-- AOP支持(必需) -->
<dependency>
    <groupId>org.springframework.aspects</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>6.0.0</version>
</dependency>

2.2 核心接口和类

RetryTemplate

Spring Retry的核心类,用于执行重试逻辑。它是线程安全的,可以在多线程环境中使用。

// 声明式使用示例
public class RetryTemplateExample {
    private RetryTemplate retryTemplate;
    
    public String execute() {
        return retryTemplate.execute(
            context -> callExternalService(),  // RetryCallback
            context -> fallbackLogic()          // RecoveryCallback
        );
    }
}

特点:

  • 线程安全
  • 可配置的重试策略
  • 支持监听器
  • 提供恢复机制

RetryPolicy

定义重试策略,包括:

  • 最多重试次数
  • 哪些异常可以重试
  • 什么时候停止重试
// 常见实现
SimpleRetryPolicy              // 最多重试N次
TimeoutRetryPolicy             // 在指定时间内重试
ExceptionClassifierRetryPolicy // 根据异常类型判断
CompositeRetryPolicy           // 组合多个策略

BackOffPolicy

定义重试之间的延迟策略,是防止雪崩的关键:

// 常见实现
NoBackOffPolicy                // 立即重试
FixedBackOffPolicy             // 固定延迟(如1秒)
LinearBackOffPolicy            // 线性增加延迟
ExponentialBackOffPolicy       // 指数增长延迟(推荐)
ExponentialRandomBackOffPolicy // 指数增长+随机(防止雷鸣羊群)

RecoveryCallback

所有重试都失败后的恢复处理逻辑。这是降级方案的入口点:

// 恢复回调示例
(context) -> {
    log.error("重试全部失败,尝试降级处理");
    return defaultValue;  // 返回默认值
}

三、@Retryable注解方式

3.1 基本使用

@Retryable注解是最简洁的声明式重试方式。

第一步:启用重试机制

@SpringBootApplication
@EnableRetry  // ⚠️ 必须添加!
public class RetryDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(RetryDemoApplication.class, args);
    }
}

第二步:在方法上添加@Retryable注解

@Service
public class UserService {
    
    @Retryable(
        maxAttempts = 3,                    // 最多重试3次
        backoff = @Backoff(delay = 1000)   // 延迟1秒
    )
    public User getUser(String userId) {
        // 调用外部API,可能失败
        return remoteUserApi.fetchUser(userId);
    }
    
    // ⚠️ 注意:恢复方法的参数必须匹配,异常类型要对应
    @Recover
    public User recoverGetUser(Exception e, String userId) {
        log.error("重试全部失败,userId: {}", userId, e);
        // 返回默认用户或从本地缓存获取
        return new User(userId, "Unknown User", "default@email.com");
    }
}

3.2 重要参数详解

参数默认值说明示例
value{}指定重试的异常类型(数组){IOException.class, TimeoutException.class}
maxAttempts3最大重试次数(包括第一次)5表示1次+4次重试
delay0ms重试间隔时间(毫秒)1000 = 1秒
multiplier0延迟时间的乘数(用于指数退避)1.5表示每次乘以1.5
maxDelay0最大延迟时间(毫秒)30000 = 30秒
statefulfalse是否有状态(同一异常实例才重试)true用于复杂场景
listeners{}重试监听器自定义监听器

3.3 异常处理详解

@Service
public class PaymentService {
    
    // ✅ 只重试IOException和TimeoutException
    // ❌ 不重试其他异常(如业务异常)
    @Retryable(
        value = {
            IOException.class,
            SocketTimeoutException.class
        },
        maxAttempts = 3,
        backoff = @Backoff(delay = 1000, multiplier = 1.5)
    )
    public PaymentResult processPayment(Order order) {
        log.info("尝试处理支付,订单ID: {}", order.getId());
        
        // 可能抛出IOException - 会重试
        // 可能抛出SocketTimeoutException - 会重试
        // 可能抛出BusinessException - 不会重试,直接抛出
        return paymentGateway.charge(order);
    }
    
    @Recover
    public PaymentResult recoverPayment(
            IOException e, Order order) {
        log.error("支付失败,订单号: {}, 异常: {}", 
                  order.getId(), e.getMessage());
        
        // 记录待处理交易,人工处理
        pendingPaymentService.save(
            new PendingPayment(order.getId()));
        
        throw new PaymentException(
            "支付处理中,请稍候再试");
    }
}

3.4 实战示例:远程API调用

@Service
public class WeatherService {
    
    @Autowired
    private RestTemplate restTemplate;
    
    // 场景:调用第三方天气API
    @Retryable(
        value = {
            ConnectException.class,
            SocketTimeoutException.class,
            HttpClientErrorException.class
        },
        maxAttempts = 3,
        backoff = @Backoff(
            delay = 2000,          // 初始延迟2秒
            multiplier = 1.5,      // 每次增加1.5倍
            maxDelay = 10000       // 最多延迟10秒
        ),
        listeners = {"retryListener"}
    )
    public WeatherData getWeather(String city) {
        log.info("第{}次调用天气API,城市: {}", 
                 getCurrentRetryCount(), city);
        
        String url = String.format(
            "https://api.weather.com/city/%s", city);
        return restTemplate.getForObject(
            url, WeatherData.class);
    }
    
    @Recover
    public WeatherData recoverGetWeather(
            Exception e, String city) {
        log.warn("天气API调用失败,使用本地缓存数据");
        return weatherCache.getLastWeather(city);
    }
    
    private int getCurrentRetryCount() {
        RetryContext context = 
            RetryContextHolder.getContext();
        return context != null ? 
            context.getRetryCount() : 0;
    }
}

四、RetryTemplate编程方式

4.1 基本使用

RetryTemplate提供了更灵活的编程式重试方式,相比@Retryable,它提供了对重试过程的更细粒度控制。

配置重试模板:

@Configuration
public class RetryConfiguration {
    
    @Bean
    public RetryTemplate retryTemplate() {
        RetryTemplate retryTemplate = 
            new RetryTemplate();
        
        // 设置重试策略:最多重试3次
        SimpleRetryPolicy retryPolicy = 
            new SimpleRetryPolicy();
        retryPolicy.setMaxAttempts(3);
        retryTemplate.setRetryPolicy(retryPolicy);
        
        // 设置延迟策略:固定延迟2秒
        FixedBackOffPolicy backOffPolicy = 
            new FixedBackOffPolicy();
        backOffPolicy.setBackOffPeriod(2000);
        retryTemplate.setBackOffPolicy(backOffPolicy);
        
        return retryTemplate;
    }
}

在业务代码中使用:

@Service
public class OrderService {
    
    @Autowired
    private RetryTemplate retryTemplate;
    
    @Autowired
    private OrderRepository orderRepository;
    
    public Order createOrder(Order order) 
            throws Exception {
        
        // 使用RetryTemplate执行业务逻辑
        return retryTemplate.execute(
            // RetryCallback:重试的业务逻辑
            context -> {
                log.info("第{}次尝试创建订单", 
                         context.getRetryCount() + 1);
                return orderRepository.save(order);
            },
            
            // RecoveryCallback:恢复逻辑
            context -> {
                log.error("订单创建失败,重试{}次", 
                    context.getRetryCount());
                
                // 记录失败的订单
                failedOrderLog.save(order);
                
                // 返回默认值或抛出异常
                return null;
            }
        );
    }
}

4.2 重试策略详解

SimpleRetryPolicy - 简单次数限制

@Bean
public RetryTemplate simpleRetryTemplate() {
    RetryTemplate template = new RetryTemplate();
    
    SimpleRetryPolicy policy = 
        new SimpleRetryPolicy();
    policy.setMaxAttempts(3);  // 最多3次
    template.setRetryPolicy(policy);
    
    return template;
}

TimeoutRetryPolicy - 时间限制

@Bean
public RetryTemplate timeoutRetryTemplate() {
    RetryTemplate template = new RetryTemplate();
    
    TimeoutRetryPolicy policy = 
        new TimeoutRetryPolicy();
    policy.setTimeout(60000);  // 60秒内重试
    template.setRetryPolicy(policy);
    
    return template;
}

ExceptionClassifierRetryPolicy - 异常分类

@Bean
public RetryTemplate classifierRetryTemplate() {
    RetryTemplate template = new RetryTemplate();
    
    // 定义哪些异常可重试
    Map<Class<? extends Throwable>, Boolean> 
        retryableMap = new HashMap<>();
    retryableMap.put(IOException.class, true);      // 重试
    retryableMap.put(SocketTimeoutException.class, 
                     true);                          // 重试
    retryableMap.put(BusinessException.class, 
                     false);                         // 不重试
    
    SimpleClassifierRetryPolicy policy = 
        new SimpleClassifierRetryPolicy(
            retryableMap);
    template.setRetryPolicy(policy);
    
    return template;
}

CompositeRetryPolicy - 组合策略

@Bean
public RetryTemplate compositeRetryTemplate() {
    RetryTemplate template = new RetryTemplate();
    
    // 组合多个策略:次数限制 + 时间限制
    CompositeRetryPolicy policy = 
        new CompositeRetryPolicy();
    
    SimpleRetryPolicy maxAttemptsPolicy = 
        new SimpleRetryPolicy();
    maxAttemptsPolicy.setMaxAttempts(5);
    
    TimeoutRetryPolicy timeoutPolicy = 
        new TimeoutRetryPolicy();
    timeoutPolicy.setTimeout(120000);  // 2分钟
    
    policy.setPolicies(new RetryPolicy[]{
        maxAttemptsPolicy,
        timeoutPolicy
    });
    
    template.setRetryPolicy(policy);
    return template;
}

4.3 高级配置示例

@Configuration
public class AdvancedRetryConfig {
    
    @Bean("fastRetryTemplate")
    public RetryTemplate fastRetryTemplate() {
        RetryTemplate template = 
            new RetryTemplate();
        
        // 快速重试(网络故障)
        SimpleRetryPolicy policy = 
            new SimpleRetryPolicy();
        policy.setMaxAttempts(3);
        template.setRetryPolicy(policy);
        
        // 无延迟
        template.setBackOffPolicy(
            new NoBackOffPolicy());
        
        return template;
    }
    
    @Bean("standardRetryTemplate")
    public RetryTemplate standardRetryTemplate() {
        RetryTemplate template = 
            new RetryTemplate();
        
        // 标准重试(RPC调用)
        SimpleRetryPolicy policy = 
            new SimpleRetryPolicy();
        policy.setMaxAttempts(3);
        template.setRetryPolicy(policy);
        
        // 指数退避
        ExponentialBackOffPolicy backoff = 
            new ExponentialBackOffPolicy();
        backoff.setInitialInterval(1000);      // 1秒
        backoff.setMultiplier(1.5);            // 乘以1.5
        backoff.setMaxInterval(10000);         // 最多10秒
        template.setBackOffPolicy(backoff);
        
        return template;
    }
    
    @Bean("slowRetryTemplate")
    public RetryTemplate slowRetryTemplate() {
        RetryTemplate template = 
            new RetryTemplate();
        
        // 缓慢重试(数据库操作)
        SimpleRetryPolicy policy = 
            new SimpleRetryPolicy();
        policy.setMaxAttempts(5);
        template.setRetryPolicy(policy);
        
        // 缓慢的指数退避
        ExponentialBackOffPolicy backoff = 
            new ExponentialBackOffPolicy();
        backoff.setInitialInterval(2000);      // 2秒
        backoff.setMultiplier(2.0);            // 乘以2
        backoff.setMaxInterval(60000);         // 最多1分钟
        template.setBackOffPolicy(backoff);
        
        return template;
    }
}

五、延迟策略详解

5.1 常见延迟策略对比

选择合适的延迟策略对系统性能至关重要。

策略延迟行为算法适用场景风险
NoBackOff立即重试,无延迟-瞬时故障、毛刺可能加重已有负担
FixedBackOff固定延迟(1秒、2秒等)delay = D普通网络故障可能不够灵活
LinearBackOff线性增加延迟delay = D × i中等故障恢复增长缓慢
Exponential指数增长延迟(推荐)delay = D × multiplier^n防止雪崩延迟可能过长
ExponentialRandom指数增长+随机delay = random × exponential防止雷鸣羊群最复杂

5.2 指数退避详解(最佳实践)

指数退避是处理分布式故障的最佳实践。它通过逐步增加延迟时间,给被调用方充足的恢复时间。

问题场景:

客户端不断重试
     ↓
服务端压力增加
     ↓
服务端处理更慢
     ↓
客户端超时更多,重试更频繁
     ↓
形成雪崩!❌

指数退避解决方案:

第1次:立即失败
第2次:等待1秒后重试
第3次:等待1.5秒后重试  (1 × 1.5)
第4次:等待2.25秒后重试 (1.5 × 1.5)
第5次:等待3.375秒后重试 (2.25 × 1.5)
...
总延迟时间逐步增加,给服务恢复时间 ✅

代码实现:

// 方式1:注解方式
@Retryable(
    value = {RemoteServiceException.class},
    maxAttempts = 5,
    backoff = @Backoff(
        delay = 1000,      // 初始延迟:1秒
        multiplier = 2.0,  // 每次乘以2倍
        maxDelay = 30000   // 最多延迟30秒
    )
)
public String callRemoteService() {
    // 重试延迟序列: 1s -> 2s -> 4s -> 8s -> 16s
    // (后面会被maxDelay限制在30s以内)
    return remoteApi.fetch();
}

// 方式2:编程方式
@Bean
public RetryTemplate exponentialRetryTemplate() {
    RetryTemplate template = 
        new RetryTemplate();
    
    SimpleRetryPolicy policy = 
        new SimpleRetryPolicy();
    policy.setMaxAttempts(5);
    template.setRetryPolicy(policy);
    
    ExponentialBackOffPolicy backoff = 
        new ExponentialBackOffPolicy();
    backoff.setInitialInterval(1000);      // 1秒
    backoff.setMultiplier(2.0);            // 2倍增长
    backoff.setMaxInterval(30000);         // 最多30秒
    template.setBackOffPolicy(backoff);
    
    return template;
}

参数选择建议:

场景:调用支付API(关键业务)
- delay: 2000(2秒,留出足够时间)
- multiplier: 1.5(平衡的增长速度)
- maxDelay: 30000(最多等30秒)
- maxAttempts: 5(共5次)

场景:调用缓存服务(非关键)
- delay: 100(100毫秒,快速重试)
- multiplier: 2.0(快速增长)
- maxDelay: 5000(最多5秒)
- maxAttempts: 3(共3次)

场景:调用数据库(最关键)
- delay: 500(500毫秒)
- multiplier: 1.5(温和增长)
- maxDelay: 60000(最多60秒)
- maxAttempts: 3(共3次)

5.3 防止雷鸣羊群现象

当大量客户端同时重试时,可能在同一时间点都发起请求,形成"雷鸣羊群"现象。

// 使用随机化的指数退避
@Bean
public RetryTemplate randomExponentialTemplate() {
    RetryTemplate template = 
        new RetryTemplate();
    
    SimpleRetryPolicy policy = 
        new SimpleRetryPolicy();
    policy.setMaxAttempts(4);
    template.setRetryPolicy(policy);
    
    // 指数退避基础上加随机化
    ExponentialRandomBackOffPolicy backoff = 
        new ExponentialRandomBackOffPolicy();
    backoff.setInitialInterval(1000);
    backoff.setMultiplier(2.0);
    backoff.setMaxInterval(30000);
    template.setBackOffPolicy(backoff);
    
    return template;
}

// 延迟时间 = Random(0, exponential)
// 例如:随机(0, 1s), 随机(0, 2s), 随机(0, 4s)...
// 这样可以分散客户端的重试时间点

六、高级特性与最佳实践

6.1 自定义重试策略

有时需要实现复杂的业务逻辑重试。

// 自定义重试策略:支持特殊的业务逻辑
public class BusinessRetryPolicy 
        implements RetryPolicy {
    
    private static final int MAX_ATTEMPTS = 3;
    private static final long TIMEOUT = 60000; // 60秒
    
    @Override
    public boolean canRetry(RetryContext context) {
        Throwable lastException = 
            context.getLastThrowable();
        
        // 1. 检查重试次数
        if (context.getRetryCount() >= MAX_ATTEMPTS) {
            return false;
        }
        
        // 2. 检查超时时间
        long elapsed = System.currentTimeMillis() - 
                      context.getStartTime();
        if (elapsed > TIMEOUT) {
            return false;
        }
        
        // 3. 检查异常类型
        if (lastException instanceof IOException) {
            return true;  // 网络异常可重试
        }
        
        if (lastException instanceof 
                SocketTimeoutException) {
            return true;  // 超时异常可重试
        }
        
        if (lastException instanceof 
                BusinessException) {
            // 业务异常根据具体情况判断
            BusinessException be = 
                (BusinessException) lastException;
            return be.isRetryable();
        }
        
        return false;  // 其他异常不重试
    }
    
    @Override
    public RetryContext open(
            RetryContext parentContext) {
        return new RetryContextSupport(
            parentContext);
    }
    
    @Override
    public void close(RetryContext context) {
        // 清理资源
    }
    
    @Override
    public void registerThrowable(
            RetryContext context, 
            Throwable throwable) {
        context.registerThrowable(throwable);
    }
}

// 使用自定义策略
@Bean
public RetryTemplate customRetryTemplate() {
    RetryTemplate template = 
        new RetryTemplate();
    template.setRetryPolicy(
        new BusinessRetryPolicy());
    return template;
}

6.2 重试监听器

通过RetryListener监听重试过程中的关键事件,用于监控、日志、告警等。

@Component
public class RetryMonitorListener 
        implements RetryListener {
    
    @Override
    public <T, E extends Throwable> void open(
            RetryContext context,
            RetryCallback<T, E> callback) {
        log.info("开始重试: {}", 
            callback.getClass().getSimpleName());
        context.setAttribute("startTime", 
            System.currentTimeMillis());
    }
    
    @Override
    public <T, E extends Throwable> void onSuccess(
            RetryContext context,
            RetryCallback<T, E> callback,
            T result) {
        long duration = 
            System.currentTimeMillis() - 
            (Long)context.getAttribute("startTime");
        
        log.info(
            "重试成功! 耗时: {}ms, 重试次数: {}",
            duration, context.getRetryCount());
        
        // 上报成功指标
        metrics.recordRetrySuccess(
            duration, context.getRetryCount());
    }
    
    @Override
    public <T, E extends Throwable> void onError(
            RetryContext context,
            RetryCallback<T, E> callback,
            Throwable throwable) {
        log.warn(
            "第{}次重试失败: {}",
            context.getRetryCount(),
            throwable.getMessage());
        
        // 上报失败指标
        metrics.recordRetryFailure(
            throwable.getClass().getSimpleName());
    }
    
    @Override
    public void close(RetryContext context) {
        long duration = 
            System.currentTimeMillis() - 
            (Long)context.getAttribute("startTime");
        
        log.info(
            "重试过程结束,总耗时: {}ms",
            duration);
    }
}

// 注册监听器
@Bean
public RetryTemplate 
        retryTemplateWithListener(
            RetryMonitorListener listener) {
    RetryTemplate template = 
        new RetryTemplate();
    template.registerListener(listener);
    // ... 其他配置
    return template;
}

6.3 与熔断器结合

在生产环境中,通常需要将重试与熔断器模式结合使用。

@Service
public class ResilientService {
    
    @Autowired
    private RetryTemplate retryTemplate;
    
    @Autowired
    private CircuitBreaker circuitBreaker;
    
    /**
     * 执行带有容错能力的业务逻辑
     * 
     * 工作流程:
     * 1. 熔断器检查(快速失败)
     * 2. 进入重试逻辑
     * 3. 重试次数内恢复 → 成功
     * 4. 超出重试次数 → 执行降级逻辑
     */
    public String executeWithResilience(
            String userId) {
        
        // 外层:熔断器保护
        return circuitBreaker.execute(() -> 
            // 中层:重试机制
            retryTemplate.execute(
                context -> {
                    log.info("尝试调用服务,用户: {}", 
                             userId);
                    return callRemoteService(userId);
                },
                
                context -> {
                    // 降级逻辑
                    log.warn("重试失败,执行降级方案");
                    return getDefaultResponse(userId);
                }
            )
        );
    }
    
    private String callRemoteService(
            String userId) {
        // 可能失败的业务逻辑
        return remoteServiceClient.getUser(userId)
            .getName();
    }
    
    private String getDefaultResponse(
            String userId) {
        // 降级方案:从缓存或数据库获取
        return userCache.get(userId)
            .orElse("Default User");
    }
}

6.4 常见最佳实践

1. 合理选择重试异常

// ✅ 应该重试的异常
IOException.class                    // 网络异常
SocketTimeoutException.class         // 连接超时
ConnectException.class               // 连接失败
HttpServerErrorException.class       // 5xx错误
TimeoutException.class               // 超时

// ❌ 不应该重试的异常
BusinessException.class              // 业务异常
ValidationException.class            // 验证异常
NotFoundException.class               // 资源不存在
AccessDeniedException.class          // 权限异常
IllegalArgumentException.class       // 参数错误

2. 确保幂等性

重试的操作必须是幂等的,避免重复提交。

/**
 * ✅ 幂等的操作:多次执行结果相同
 */
@Retryable(maxAttempts = 3)
public void updateUserStatus(
        String userId, String status) {
    
    // 使用UPDATE语句 + WHERE条件
    // 多次执行结果相同
    userRepository.updateStatus(
        userId, status);
}

/**
 * ❌ 非幂等的操作:多次执行结果不同
 */
@Retryable(maxAttempts = 3)  // ⚠️ 危险!
public void transferMoney(
        String fromId, String toId, 
        BigDecimal amount) {
    
    // 多次执行会多次转账!
    accountService.deduct(fromId, amount);
    accountService.add(toId, amount);
}

/**
 * ✅ 改为幂等操作
 */
@Retryable(maxAttempts = 3)
public void transferMoney(
        String transferId,           // 业务ID
        String fromId, String toId,
        BigDecimal amount) {
    
    // 检查转账是否已处理
    if (transferHistoryService.exists(
            transferId)) {
        return;  // 已处理,直接返回
    }
    
    // 执行转账
    accountService.deduct(fromId, amount);
    accountService.add(toId, amount);
    
    // 记录转账历史
    transferHistoryService.save(
        new TransferHistory(
            transferId, fromId, toId, 
            amount));
}

3. 设置合理的超时和重试次数

// 根据业务重要性设置不同的策略

// 关键业务:支付
@Retryable(
    maxAttempts = 5,
    backoff = @Backoff(
        delay = 2000,
        multiplier = 1.5,
        maxDelay = 30000
    )
)
public void paymentProcess(Order order) { }

// 普通业务:用户信息更新
@Retryable(
    maxAttempts = 3,
    backoff = @Backoff(
        delay = 500,
        multiplier = 1.5,
        maxDelay = 10000
    )
)
public void updateUserInfo(User user) { }

// 非关键业务:日志同步
@Retryable(
    maxAttempts = 2,
    backoff = @Backoff(delay = 100)
)
public void syncAuditLog(Log log) { }

4. 记录重试指标

@Component
public class RetryMetricsListener 
        implements RetryListener {
    
    @Autowired
    private MeterRegistry meterRegistry;
    
    @Override
    public <T, E extends Throwable> void onError(
            RetryContext context,
            RetryCallback<T, E> callback,
            Throwable throwable) {
        
        // 记录重试指标
        String methodName = 
            callback.getClass().getSimpleName();
        String exceptionName = 
            throwable.getClass().getSimpleName();
        
        // 记录计数器
        meterRegistry.counter(
            "retry.failure",
            "method", methodName,
            "exception", exceptionName
        ).increment();
        
        // 记录重试次数
        meterRegistry.gauge(
            "retry.attempts",
            () -> context.getRetryCount(),
            "method", methodName
        );
    }
}

七、常见问题与排查

7.1 重试不生效的问题

症状: 添加了@Retryable但不生效

原因排查清单:

// ❌ 问题1:启动类没加@EnableRetry
@SpringBootApplication
// 缺少这一行!
public class Application { }

// ✅ 修复
@SpringBootApplication
@EnableRetry
public class Application { }

// ❌ 问题2:方法是private(AOP无法代理)
@Service
public class UserService {
    @Retryable(maxAttempts = 3)
    private User getUser() { }  // ❌ 不生效
}

// ✅ 修复:改为public
@Service
public class UserService {
    @Retryable(maxAttempts = 3)
    public User getUser() { }  // ✅ 生效
}

// ❌ 问题3:异常类型不匹配
@Service
public class UserService {
    @Retryable(value = IOException.class)
    public User getUser() {
        throw new RuntimeException();  // 不是IOException
    }
}

// ✅ 修复:异常类型要对应
@Service
public class UserService {
    @Retryable(
        value = {IOException.class, 
                 RuntimeException.class}
    )
    public User getUser() {
        throw new RuntimeException();  // 会重试
    }
}

// ❌ 问题4:没有引入spring-aspects依赖
// pom.xml中缺少spring-aspects

// ✅ 修复:添加依赖
<dependency>
    <groupId>org.springframework.aspects</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>6.0.0</version>
</dependency>

// ❌ 问题5:@Recover方法签名错误
@Service
public class UserService {
    @Retryable(maxAttempts = 3)
    public User getUser(String userId) { }
    
    @Recover
    public User recover(Exception e) { }  // ❌ 少了userId参数
}

// ✅ 修复:@Recover方法参数要和@Retryable一致
@Service
public class UserService {
    @Retryable(maxAttempts = 3)
    public User getUser(String userId) { }
    
    @Recover
    public User recover(Exception e, 
                       String userId) { }  // ✅ 包含userId
}

调试技巧:

// 1. 查看是否生成了代理
ApplicationContext context = 
    SpringApplication.run(Application.class);
UserService userService = 
    context.getBean(UserService.class);

log.info("Bean类型: {}", 
    userService.getClass().getName());
// 输出:com.example.UserService$$CGLIB$...
// 说明已生成代理,@Retryable应该生效

// 2. 打印重试日志
@Retryable(maxAttempts = 3)
public void myMethod() {
    log.info("执行业务逻辑");
    throw new IOException();
}

// 3. 使用RetryListener监听
@Bean
public RetryListener debugListener() {
    return new RetryListener() {
        @Override
        public <T, E extends Throwable> 
            void onError(RetryContext context,
                        RetryCallback<T, E> cb,
                        Throwable e) {
            log.error("重试失败: 第{}次", 
                context.getRetryCount());
        }
    };
}

7.2 内存泄漏问题

症状: 重试过程中堆内存不断增长

原因: 大量的Throwable对象积累在内存中

// ❌ 可能导致内存泄漏的代码
@Service
public class BadRetryService {
    
    @Retryable(maxAttempts = 100)  // 过多重试
    public void riskyOperation() {
        // 每次失败都会创建Throwable对象
        throw new RuntimeException();
    }
}

// ✅ 修复
@Service
public class GoodRetryService {
    
    @Retryable(
        maxAttempts = 3,            // 限制次数
        backoff = @Backoff(delay = 1000)
    )
    public void safeOperation() {
        throw new RuntimeException();
    }
}

// 自定义策略清理异常
public class MemorySafeRetryPolicy 
        implements RetryPolicy {
    
    private int maxAttempts = 3;
    
    @Override
    public boolean canRetry(
            RetryContext context) {
        if (context.getRetryCount() >= 
                maxAttempts) {
            // 清理异常引用,防止内存泄漏
            context.setLastThrowable(null);
            return false;
        }
        return true;
    }
    
    // 其他方法...
}

7.3 性能问题

症状: 重试导致响应时间过长

优化方案:

// ❌ 性能问题的配置
@Retryable(
    maxAttempts = 10,      // 太多
    backoff = @Backoff(
        delay = 5000,      // 延迟太长
        multiplier = 3.0   // 增长太快
    )
)
public void slowRetry() { }

// ✅ 性能优化的配置
@Retryable(
    maxAttempts = 3,       // 3次足够
    backoff = @Backoff(
        delay = 100,       // 快速重试
        multiplier = 1.5,
        maxDelay = 5000
    )
)
public void fastRetry() { }

// 方案1:区分关键和非关键操作
@Service
public class HybridRetryService {
    
    // 关键操作:更多重试
    @Retryable(
        maxAttempts = 5,
        backoff = @Backoff(delay = 2000)
    )
    public void criticalOperation() { }
    
    // 非关键操作:快速失败
    @Retryable(
        maxAttempts = 2,
        backoff = @Backoff(delay = 100)
    )
    public void normalOperation() { }
}

// 方案2:异步重试
@Service
public class AsyncRetryService {
    
    @Async
    @Retryable(maxAttempts = 3)
    public CompletableFuture<String> 
            asyncOperation() {
        // 不阻塞主线程
        return CompletableFuture
            .completedFuture("result");
    }
}

// 方案3:使用超时控制
@Bean
public RetryTemplate 
        timeoutLimitedRetryTemplate() {
    RetryTemplate template = 
        new RetryTemplate();
    
    // 单次操作不超过30秒
    template.setRetryPolicy(
        new TimeoutRetryPolicy() {{
            setTimeout(30000);
        }}
    );
    
    return template;
}

八、综合实战案例

8.1 微服务订单系统

完整的电商订单处理流程,涉及多个微服务调用:

@Service
@Transactional
public class OrderServiceImpl 
        implements OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private InventoryService inventoryService;
    
    @Autowired
    private PaymentService paymentService;
    
    @Autowired
    private ShippingService shippingService;
    
    @Autowired
    private RetryTemplate retryTemplate;
    
    /**
     * 创建订单 - 完整流程
     * 
     * 流程图:
     * 预留库存 → 创建订单 → 支付 → 创建物流 → 成功
     *   ↓(失败)    ↓(失败)   ↓     ↓
     *   重试3x     回滚库存  重试5x  重试3x
     */
    @Override
    public OrderResponse createOrder(
            CreateOrderRequest request) {
        
        InventoryReservation reservation = null;
        
        try {
            // 1. 预留库存(可重试)
            reservation = reserveInventory(
                request.getItems());
            
            log.info("库存预留成功,预留ID: {}", 
                     reservation.getId());
            
            // 2. 创建订单记录
            Order order = new Order();
            order.setUserId(request.getUserId());
            order.setItems(request.getItems());
            order.setReservationId(
                reservation.getId());
            order.setStatus(OrderStatus.CREATED);
            order = orderRepository.save(order);
            
            log.info("订单已创建,订单ID: {}", 
                     order.getId());
            
            // 3. 处理支付(可重试)
            PaymentResult paymentResult = 
                processPayment(order);
            
            order.setPaymentId(
                paymentResult.getPaymentId());
            order.setStatus(OrderStatus.PAID);
            order = orderRepository.save(order);
            
            log.info("支付成功,支付ID: {}", 
                     paymentResult.getPaymentId());
            
            // 4. 创建物流单(可重试)
            ShippingOrder shipping = 
                createShipping(order);
            
            order.setShippingId(shipping.getId());
            order.setStatus(OrderStatus.SHIPPED);
            order = orderRepository.save(order);
            
            log.info("物流单已创建,物流ID: {}", 
                     shipping.getId());
            
            return new OrderResponse(
                order.getId(), 
                OrderStatus.SUCCESS.toString(),
                "订单创建成功");
            
        } catch (Exception e) {
            log.error("订单创建失败", e);
            
            // 回滚库存预留
            if (reservation != null) {
                try {
                    inventoryService
                        .releaseReservation(
                            reservation.getId());
                } catch (Exception ex) {
                    log.error("释放库存失败", ex);
                }
            }
            
            throw new OrderException(
                "订单创建失败:" + e.getMessage(), 
                e);
        }
    }
    
    /**
     * 预留库存 - 可重试
     */
    @Retryable(
        value = {
            InventoryException.class,
            ConnectException.class,
            SocketTimeoutException.class
        },
        maxAttempts = 3,
        backoff = @Backoff(
            delay = 500,
            multiplier = 1.5
        )
    )
    private InventoryReservation 
            reserveInventory(
                List<OrderItem> items) {
        
        log.info("正在预留库存,物品数: {}", 
                 items.size());
        return inventoryService
            .reserveItems(items);
    }
    
    /**
     * 预留库存失败的恢复逻辑
     */
    @Recover
    private InventoryReservation 
            recoverReserveInventory(
                InventoryException e,
                List<OrderItem> items) {
        
        log.error("库存预留失败,无法重试", e);
        throw new OrderException(
            "库存不足或系统繁忙,请稍后重试");
    }
    
    /**
     * 处理支付 - 可重试(最关键)
     */
    @Retryable(
        value = {
            PaymentException.class,
            SocketTimeoutException.class,
            ConnectException.class
        },
        maxAttempts = 5,
        backoff = @Backoff(
            delay = 1000,
            multiplier = 2.0,
            maxDelay = 30000
        )
    )
    private PaymentResult 
            processPayment(Order order) {
        
        log.info("正在处理支付,订单ID: {}, " +
                 "金额: {}", 
                 order.getId(), 
                 order.getTotal());
        
        return paymentService.charge(
            order);
    }
    
    /**
     * 支付失败的恢复逻辑
     */
    @Recover
    private PaymentResult 
            recoverProcessPayment(
                PaymentException e,
                Order order) {
        
        log.error("支付处理失败,订单ID: {}", 
                  order.getId(), e);
        
        // 记录待处理交易
        PendingPayment pending = 
            new PendingPayment();
        pending.setOrderId(order.getId());
        pending.setAmount(order.getTotal());
        pending.setReason(e.getMessage());
        pending.setStatus(
            PendingPaymentStatus.PENDING);
        pendingPaymentService.save(pending);
        
        // 抛出异常,让前端知道支付正在处理
        throw new PaymentException(
            "支付处理中,请勿关闭页面");
    }
    
    /**
     * 创建物流单 - 可重试
     */
    @Retryable(
        value = {
            ShippingException.class,
            ConnectException.class
        },
        maxAttempts = 3,
        backoff = @Backoff(delay = 2000)
    )
    private ShippingOrder 
            createShipping(Order order) {
        
        log.info("正在创建物流单,订单ID: {}", 
                 order.getId());
        
        return shippingService
            .createShipping(order);
    }
    
    /**
     * 创建物流单失败的恢复逻辑
     */
    @Recover
    private ShippingOrder 
            recoverCreateShipping(
                ShippingException e,
                Order order) {
        
        log.error("物流单创建失败,订单ID: {}", 
                  order.getId(), e);
        
        // 降级处理:记录未决物流单,稍后处理
        DeferredShipping deferred = 
            new DeferredShipping();
        deferred.setOrderId(order.getId());
        deferred.setReason(e.getMessage());
        deferredShippingService.save(deferred);
        
        // 返回空或临时物流单
        return null;
    }
}

// 辅助类
@Data
@Entity
public class Order {
    @Id
    private String id;
    private String userId;
    private List<OrderItem> items;
    private String reservationId;
    private String paymentId;
    private String shippingId;
    private OrderStatus status;
    private BigDecimal total;
    private LocalDateTime createTime;
}

@Data
public class OrderResponse {
    private String orderId;
    private String status;
    private String message;
}

public enum OrderStatus {
    CREATED,
    PAID,
    SHIPPED,
    SUCCESS,
    FAILED
}

8.2 第三方API集成示例

@Service
public class WeatherApiService {
    
    @Autowired
    private RestTemplate restTemplate;
    
    @Autowired
    private WeatherCache weatherCache;
    
    /**
     * 获取天气信息
     * 
     * 重试策略:
     * - 最多重试3次
     * - 延迟:1秒 → 1.5秒 → 2.25秒
     * - 最多延迟10秒
     */
    @Retryable(
        value = {
            ConnectException.class,
            SocketTimeoutException.class,
            HttpClientErrorException.class,
            HttpServerErrorException.class
        },
        maxAttempts = 3,
        backoff = @Backoff(
            delay = 1000,
            multiplier = 1.5,
            maxDelay = 10000
        )
    )
    public WeatherData getWeather(
            String city) {
        
        log.info("查询天气信息,城市: {}", city);
        
        String url = String.format(
            "https://api.weather.com/v1/" +
            "weather?city=%s&key=%s",
            city, 
            weatherApiKey);
        
        try {
            WeatherData data = 
                restTemplate.getForObject(
                    url, WeatherData.class);
            
            // 缓存结果
            weatherCache.put(city, data);
            
            return data;
            
        } catch (HttpClientErrorException e) {
            // 4xx错误(如无效的城市)
            // 不应该重试
            if (e.getStatusCode()
                    .is4xxClientError()) {
                throw e;
            }
            // 5xx错误可以重试
            throw new HttpServerErrorException(
                e.getStatusCode());
        }
    }
    
    /**
     * 恢复逻辑:返回缓存数据
     */
    @Recover
    public WeatherData recoverGetWeather(
            Exception e, String city) {
        
        log.warn("天气API调用失败,使用缓存数据," +
                 "城市: {}", city, e);
        
        // 返回缓存数据
        WeatherData cached = 
            weatherCache.get(city);
        
        if (cached != null) {
            cached.setFromCache(true);
            return cached;
        }
        
        // 返回默认数据
        return WeatherData.createDefault(city);
    }
}

@Data
public class WeatherData {
    private String city;
    private String weather;
    private int temperature;
    private int humidity;
    private LocalDateTime updateTime;
    private boolean fromCache;
    
    public static WeatherData 
            createDefault(String city) {
        WeatherData data = new WeatherData();
        data.setCity(city);
        data.setWeather("未知");
        data.setTemperature(0);
        data.setHumidity(0);
        data.setFromCache(false);
        return data;
    }
}

九、@Retryable vs RetryTemplate对比

选择维度对比

维度@RetryableRetryTemplate
学习难度低(注解即可)中等(需要理解配置)
代码简洁度中等
灵活性有限
代码量
性能开销AOP代理开销最小
动态性静态配置可运行时调整
适用场景简单业务复杂微服务

选择建议

选择@Retryable的场景:

// 1. 简单的业务方法,重试逻辑固定
@Service
public class SimpleService {
    @Retryable(maxAttempts = 3)
    public void simpleMethod() { }
}

// 2. Service层的方法,可以接受AOP代理
@Service  // ✅ 可以代理
public class MyService {
    @Retryable(maxAttempts = 3)
    public void myMethod() { }
}

// 3. 团队偏好声明式编程
// 3. 快速原型开发

选择RetryTemplate的场景:

// 1. 复杂的重试策略,需要运行时动态调整
public void complexRetry() {
    String strategy = configService
        .getRetryStrategy();
    RetryTemplate template = 
        retryTemplateFactory.create(
            strategy);
}

// 2. 对性能有严格要求
// 3. 需要精细控制重试过程
// 4. 在非Service类中使用

// 5. 需要不同的重试策略
@Component
public class MultiStrategyRetry {
    
    @Autowired
    @Qualifier("fastRetryTemplate")
    private RetryTemplate fastTemplate;
    
    @Autowired
    @Qualifier("slowRetryTemplate")
    private RetryTemplate slowTemplate;
    
    public void execute(String type) {
        if ("fast".equals(type)) {
            fastTemplate.execute(callback);
        } else {
            slowTemplate.execute(callback);
        }
    }
}

十、总结与展望

10.1 核心要点总结

┌─────────────────────────────────────┐
│     Spring重试机制核心架构          │
├─────────────────────────────────────┤
│                                     │
│  @Retryable @EnableRetry            │
│    ↓                                │
│  AOP代理 → 重试拦截器                │
│    ↓                                │
│  RetryTemplate 核心类                │
│    ↓                                │
│  ┌─────────┬──────────┬──────────┐  │
│  │Retry    │BackOff  │Recovery  │  │
│  │Policy   │Policy   │Callback  │  │
│  └─────────┴──────────┴──────────┘  │
│    ↓        ↓          ↓             │
│  检查是否   计算延迟  恢复处理       │
│  需要重试   时间                     │
│                                     │
└─────────────────────────────────────┘

10.2 关键最佳实践回顾

1. ✅ 重试是构建高可用系统的必要组件
   - 处理临时故障
   - 提高成功率
   - 改善用户体验

2. ✅ Spring Retry提供两种重试方式
   - 声明式:@Retryable(简单易用)
   - 编程式:RetryTemplate(灵活强大)

3. ✅ 指数退避是处理分布式故障的最佳策略
   - 防止雪崩
   - 给服务恢复时间
   - 参数:delay × multiplier^n

4. ✅ 必须确保被重试的操作是幂等的
   - 使用业务ID去重
   - 幂等检查
   - 事务隔离

5. ✅ 结合熔断器构建稳健系统
   - 熔断器:快速失败
   - 重试机制:恢复故障
   - 降级方案:保底策略

6. ✅ 监控和告警至关重要
   - 记录重试指标
   - 监控成功率
   - 设置告警阈值

7. ✅ 合理选择重试异常
   - 临时故障:重试
   - 业务异常:不重试
   - 权限异常:不重试

8. ✅ 设置合理的超时和重试次数
   - 根据业务重要性调整
   - 快速失败原则
   - 防止级联故障

10.3 生产环境检查清单

部署到生产环境前的检查清单:

□ 已添加@EnableRetry到启动类
□ 已添加spring-retry和spring-aspects依赖
□ 重试异常类型配置正确
□ @Recover方法签名与@Retryable匹配
□ 被重试的操作已确认是幂等的
□ 设置了合理的maxAttempts(通常3-5)
□ 配置了指数退避策略(delay+multiplier+maxDelay)
□ 实现了RetryListener进行监控
□ 设置了合理的超时时间
□ 结合了熔断器或限流器
□ 准备了降级方案
□ 添加了详细的日志记录
□ 监控重试指标(成功率、延迟等)
□ 设置了告警阈值
□ 进行了压测验证
□ 编写了重试相关的单元测试

10.4 进阶学习资源

📚 官方文档
  - Spring Retry官方文档
  - Spring Cloud Resilience4j指南

📖 相关论文和文章
  - "Retry Storms and How to Avoid Them"
  - AWS Lambda重试最佳实践
  - Google SRE Book(第二章:容错性)

🔨 相关开源项目
  - Resilience4j(现代化容错库)
  - Hystrix(Netflix容错框架)
  - Polly(.NET重试库参考)

🎓 推荐课程
  - 微服务架构设计
  - 分布式系统基础
  - 高可用系统设计

📊 相关话题
  - Circuit Breaker模式
  - Bulkhead模式
  - Rate Limiting限流
  - Timeout超时控制

10.5 常见陷阱避坑指南

❌ 陷阱1:所有异常都重试
   ✅ 解决:只重试临时故障

❌ 陷阱2:没有指数退避
   ✅ 解决:使用exponential backoff

❌ 陷阱3:重试非幂等操作
   ✅ 解决:检查幂等性或添加去重

❌ 陷阱4:重试次数过多
   ✅ 解决:通常3-5次足够

❌ 陷阱5:忽视监控
   ✅ 解决:添加完整的监控指标

❌ 陷阱6:没有降级方案
   ✅ 解决:实现@Recover和回调

❌ 陷阱7:与熔断器冲突
   ✅ 解决:正确组合两者

❌ 陷阱8:私有方法无法重试
   ✅ 解决:改为public方法

10.6 结语

重试机制是分布式系统的关键特性。Spring Framework通过简洁的注解和灵活的模板方法提供了企业级的重试支持。在实际应用中,根据具体场景选择合适的策略,并与其他容错机制结合使用,才能构建真正稳健的系统。

关键是要记住:

好的重试策略 = 正确的异常判断 + 合理的延迟策略 + 幂等性保证 + 监控告警

希望本文能帮助你在分布式系统设计中取得成功!


参考资源

0

评论区