你需要在 Spring Cloud 中实现服务调用(通常是 Feign/OpenFeign 调用)时,遇到超时或失败后自动重试指定次数的机制 ------ 核心是通过成熟的重试组件(Spring Retry 或 Resilience4j)结合服务调用组件,实现 "失败重试 + 指定次数 + 超时触发" 的需求。下面提供两种主流、可直接落地的方案,优先推荐 Spring Cloud 官方推荐的 Resilience4j 方案。
一、方案一:Spring Retry + OpenFeign(传统常用方案)
Spring Retry 是轻量级重试框架,可与 OpenFeign 无缝整合,通过注解快速配置重试规则,适合简单场景(仅需重试 + 超时控制)。
1. 前置依赖(pom.xml)
需引入 OpenFeign 核心依赖 + Spring Retry 依赖(Spring Retry 需 AOP 支持):
xml
<!-- OpenFeign 核心依赖(服务调用基础) --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- Spring Retry 依赖(重试核心) --> <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> <!-- AOP 依赖(Spring Retry 必须,用于切面拦截重试) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
2. 核心配置(application.yml)
需配置两部分:① Feign 调用超时时间(触发重试的前提);② Spring Retry 全局默认规则(可选,也可在注解中单独配置):
yaml
# 1. Feign 调用超时配置(必须,否则默认超时可能过长/过短) feign: client: config: default: # 全局配置(也可指定单个服务名,如 service-order) connect-timeout: 3000 # 连接超时时间(3秒,单位:毫秒) read-timeout: 5000 # 读取超时时间(5秒,单位:毫秒) # 2. Spring Retry 全局默认配置(可选,注解中配置会覆盖此值) spring: retry: retryable-exceptions: # 触发重试的异常类型(默认包含 IOException、TimeoutException 等) - java.net.SocketTimeoutException # 超时异常 - java.net.ConnectException # 连接异常 - org.springframework.web.client.HttpServerErrorException # 服务端5xx异常 max-attempts: 3 # 最大重试次数(默认3次:1次原始调用 + 2次重试) backoff: initial-interval: 1000 # 首次重试间隔(1秒) multiplier: 1.5 # 重试间隔倍数(下次间隔 = 上次 * 1.5,如1秒→1.5秒→2.25秒)
3. 代码实现(3 步搞定)
步骤 1:启动类开启 Feign 和 Retry 支持
java
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.retry.annotation.EnableRetry; // 开启 Feign 客户端 @EnableFeignClients // 开启 Retry 重试机制 @EnableRetry @SpringBootApplication public class ServiceConsumerApplication { public static void main(String[] args) { SpringApplication.run(ServiceConsumerApplication.class, args); } }
步骤 2:Feign 客户端接口(定义服务调用)
java
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; // 调用目标服务:service-order(服务名) @FeignClient(name = "service-order") public interface OrderFeignClient { // 调用目标服务的接口:根据订单ID查询订单 @GetMapping("/order/{orderId}") String getOrderById(@PathVariable("orderId") Long orderId); }
步骤 3:服务层添加重试注解(@Retryable)
在调用 Feign 客户端的方法上添加 @Retryable,指定重试规则(优先级:注解配置 > 全局配置):
java
import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service public class OrderService { @Resource private OrderFeignClient orderFeignClient; /** * 调用订单服务,失败/超时后重试 * @param orderId 订单ID * @return 订单信息 */ @Retryable( value = {SocketTimeoutException.class, ConnectException.class, HttpServerErrorException.class}, // 触发重试的异常(精准控制) maxAttempts = 3, // 最大重试次数:1次原始调用 + 2次重试 backoff = @Backoff(initialDelay = 1000, multiplier = 1.5) // 重试间隔:1秒→1.5秒→2.25秒 ) public String queryOrder(Long orderId) { // 调用 Feign 客户端(底层会触发超时/失败检测) return orderFeignClient.getOrderById(orderId); } /** * 重试失败后的兜底方法(可选,无此方法则重试失败后抛出原异常) * 注意:方法参数、返回值必须与重试方法一致,且需抛出 Throwable */ @Recover public String queryOrderRecover(Long orderId, Throwable e) { // 兜底逻辑:返回默认值、记录日志、降级处理等 System.err.println("订单服务调用失败,触发降级:" + e.getMessage()); return "订单查询暂时不可用,请稍后重试"; } }
4. 关键说明
- 重试触发条件 :仅当 Feign 调用抛出
@Retryable中指定的异常(超时、连接失败、服务端 5xx)时才重试;客户端异常(4xx,如参数错误)不会重试(符合业务逻辑)。 - @Recover 兜底方法:可选,当重试次数耗尽仍失败时,会执行该方法返回兜底结果,避免直接抛出异常影响用户。
- 幂等性要求:重试机制仅适用于「幂等接口」(多次调用结果一致,如查询、删除);非幂等接口(如创建订单、扣减库存)需谨慎使用,避免重复操作(可通过唯一标识防重)。