Skip to content

🌱 Spring Retry:让应用更健壮的重试机制

引言:为什么需要重试机制?

在分布式系统和网络应用中,临时性故障(Transient Failures)是不可避免的。这些故障通常由网络抖动资源争用服务短暂不可用引起。Spring Retry通过自动重试机制,让您的应用在面对这些故障时更健壮、更容错

TIP

重试适用场景

  • 网络服务调用失败
  • 数据库死锁(DeadlockLoserDataAccessException
  • 资源临时不可用
  • 短暂的第三方服务中断

Spring Retry 核心概念

1️⃣ 重试策略(RetryPolicy)

决定哪些异常需要重试以及重试次数

kotlin
@Configuration
class RetryConfig {

    @Bean
    fun retryPolicy(): RetryPolicy {
        return SimpleRetryPolicy().apply {
            maxAttempts = 5 // 最大重试次数
            retryableExceptions = mapOf(
                IOException::class.java to true,
                DeadlockLoserDataAccessException::class.java to true
            )
        }
    }
}

2️⃣ 退避策略(BackOffPolicy)

控制重试之间的延迟间隔

kotlin
@Bean
fun backOffPolicy(): BackOffPolicy {
    return ExponentialBackOffPolicy().apply {
        initialInterval = 1000 // 初始延迟1秒
        multiplier = 2.0      // 每次延迟翻倍
        maxInterval = 10000   // 最大延迟10秒
    }
}

3️⃣ 重试模板(RetryTemplate)

组合策略的核心执行组件

kotlin
@Bean
fun retryTemplate(): RetryTemplate {
    return RetryTemplate.builder()
        .maxAttempts(3)
        .exponentialBackoff(1000, 2.0, 5000)
        .retryOn(IOException::class.java)
        .build()
}

✨ 实战:使用注解实现重试

方法级重试配置

kotlin
@Service
class PaymentService {

    @Retryable(
        value = [PaymentGatewayException::class],
        maxAttempts = 3,
        backoff = Backoff(delay = 1000, multiplier = 2.0)
    )
    fun processPayment(transaction: Transaction): PaymentResult {
        // 调用第三方支付网关
        val response = paymentGatewayClient.process(transaction)
        if (!response.isSuccess) {
            throw PaymentGatewayException("支付处理失败") // 触发重试的异常
        }
        return response.toPaymentResult()
    }

    @Recover
    fun recover(e: PaymentGatewayException, transaction: Transaction): PaymentResult {
        // 所有重试失败后的恢复逻辑
        logger.error("支付处理最终失败: ${transaction.id}")
        return PaymentResult.FAILED
    }
}

IMPORTANT

@Retryable@Recover配对使用

  • @Retryable标记需要重试的方法
  • @Recover定义重试全部失败后的回退逻辑
  • 两个方法必须具有相同参数列表(可额外添加Exception参数)

重试监听器

监控重试事件并执行自定义逻辑

kotlin
@Component
class PaymentRetryListener : RetryListener {

    override fun <T, E : Throwable?> open(context: RetryContext, callback: RetryCallback<T, E>): Boolean {
        println("开始重试操作: ${context.name}")
        return true
    }

    override fun <T, E : Throwable?> onError(
        context: RetryContext,
        callback: RetryCallback<T, E>,
        throwable: Throwable
    ) {
        println("重试失败 (${context.retryCount}/3): ${throwable.message}") // 注意:实际重试次数 = retryCount+1
    }

    override fun <T, E : Throwable?> close(
        context: RetryContext,
        callback: RetryCallback<T, E>,
        throwable: Throwable?
    ) {
        if (throwable == null) {
            println("重试成功!")
        } else {
            println("所有重试均失败")
        }
    }
}

🚨 重试机制的注意事项

WARNING

幂等性要求
所有可重试的操作必须是幂等的!确保多次执行不会产生副作用。

DANGER

重试风暴风险
在微服务架构中,不当的重试策略可能引发级联故障

解决方案:

  • 添加随机抖动(Jitter)
  • 设置合理的重试上限
  • 结合断路器模式

TIP

最佳实践

  1. 仅对临时性故障进行重试(网络超时、死锁)
  2. 避免对业务逻辑错误重试(如无效参数)
  3. 结合断路器模式防止系统过载

性能对比:重试策略选择

kotlin
// 每次重试固定等待1秒
RetryTemplate.builder()
    .fixedBackoff(1000)
    .maxAttempts(3)
    .build()
kotlin
// 重试间隔指数增长:1s, 2s, 4s...
RetryTemplate.builder()
    .exponentialBackoff(1000, 2.0, 5000)
    .maxAttempts(3)
    .build()
kotlin
// 添加随机性防止重试风暴
val backOff = ExponentialBackOffPolicy().apply {
    initialInterval = 500
    multiplier = 1.5
    setRandom(true) // 关键设置
}

总结与进阶学习

核心收获

  • Spring Retry 提供声明式的重试机制
  • 通过 @Retryable@Recover 简化实现
  • 灵活配置重试策略和退避算法

⚡️ 进阶方向

NOTE

Spring生态系统集成
Spring Retry 可以无缝集成到 Spring Boot、Spring Cloud 和 Spring Batch 中,
通过 spring-retry starter 即可快速启用:

gradle
implementation("org.springframework.retry:spring-retry")

💡 实际应用建议:在微服务架构中,结合 Spring Cloud Circuit Breaker 和分布式追踪系统,构建全链路可观测的重试策略。