在 OpenFeign 声明式调用的基础上,重点讲解 `fallback` 与 `fallbackFactory` 两种兜底写法的配置、触发条件与生产实践,并补充 Sentinel / CircuitBreaker 两种启用方式。
一、OpenFeign 是什么
OpenFeign 是 Spring Cloud 的声明式 HTTP 客户端:定义一个接口并加上注解,就能像调用本地方法一样发起远程 HTTP 请求。
java
@FeignClient(value = "service-product")
public interface ProductFeignClient {
@GetMapping("/productId/{id}")
Product getProductById(@PathVariable("id") Long productId);
}
典型使用步骤:
- 引入
spring-cloud-starter-openfeign - 启动类添加
@EnableFeignClients - 编写
@FeignClient接口并注入使用
与 RestTemplate 相比,OpenFeign 是声明式调用;服务名可配合 Nacos / Eureka + LoadBalancer 实现负载均衡。
二、为什么需要 fallback 兜底
远程调用一定会失败:超时、下游 500、服务下线、网络抖动、熔断打开......
如果没有兜底,异常会直接抛到上层,可能导致:
- 页面或接口直接返回 500
- 整条调用链雪崩
- 用户体验差,问题难定位
fallback 的作用 :Feign 调用失败时,不直接抛出异常,而是执行预先写好的备用逻辑------返回默认值、读取缓存、给出友好提示,或包装成统一错误响应。
正常:OrderService → ProductFeignClient → service-product ✅
失败:OrderService → ProductFeignClient ✗ → ProductFeignClientFallback ✅
三、fallback 的两种写法
@FeignClient 上只能二选一:
| 属性 | 说明 |
|---|---|
fallback |
指定一个实现类,直接编写兜底逻辑 |
fallbackFactory |
指定一个工厂类,能拿到异常原因 Throwable |
java
// 二选一,不能同时配置
@FeignClient(value = "service-product", fallback = XxxFallback.class)
@FeignClient(value = "service-product", fallbackFactory = XxxFallbackFactory.class)
四、方式一:fallback(基础兜底)
4.1 写法
Feign 接口:
java
@FeignClient(value = "service-product", fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {
@GetMapping("/productId/{id}")
Product getProductById(@PathVariable("id") Long productId);
}
兜底实现类(必须实现同一接口,并注册为 Spring Bean):
java
@Component
@Slf4j
public class ProductFeignClientFallback implements ProductFeignClient {
@Override
public Product getProductById(Long productId) {
log.warn("ProductFeignClient 兜底回调,productId={}", productId);
Product product = new Product();
product.setId(productId);
product.setProductName("未知商品");
product.setPrice(new BigDecimal("0"));
product.setNum(0);
return product;
}
}
4.2 特点
- 写法简单,适合 Demo 或兜底逻辑固定、不需要区分异常类型的场景
- 拿不到失败原因 (没有
Throwable),日志只能记录「进入兜底」,不利于排障
4.3 适用场景
- 返回静态默认值即可
- 快速验证兜底链路是否打通
五、方式二:fallbackFactory(推荐,生产首选)
5.1 为什么更推荐
FallbackFactory 在创建兜底实例时会传入 Throwable cause,可以:
- 打印完整异常栈,方便排查
- 按异常类型做差异化降级(超时 vs 404 vs 熔断)
- 在返回体里带上可读的错误信息
5.2 写法
Feign 接口:
java
@FeignClient(value = "service-product", fallbackFactory = ProductFeignClientFallbackFactory.class)
public interface ProductFeignClient {
@GetMapping("/productId/{id}")
Product getProductById(@PathVariable("id") Long productId);
}
工厂类:
java
@Component
@Slf4j
public class ProductFeignClientFallbackFactory implements FallbackFactory<ProductFeignClient> {
@Override
public ProductFeignClient create(Throwable cause) {
log.error("ProductFeignClient 调用失败,进入 fallbackFactory", cause);
return new ProductFeignClient() {
@Override
public Product getProductById(Long productId) {
if (cause instanceof feign.RetryableException) {
log.warn("远程调用超时或可重试异常,productId={}", productId);
}
Product product = new Product();
product.setId(productId);
product.setProductName("商品暂不可用");
product.setPrice(BigDecimal.ZERO);
product.setNum(0);
return product;
}
};
}
}
Spring Cloud OpenFeign 3.x+ 使用
org.springframework.cloud.openfeign.FallbackFactory。老项目可能还有
feign.hystrix.FallbackFactory,Hystrix 已停止维护,新项目请勿使用。
5.3 fallback vs fallbackFactory 对比
| 维度 | fallback | fallbackFactory |
|---|---|---|
| 能否获取异常 | ❌ | ✅ Throwable cause |
| 差异化降级 | ❌ 难 | ✅ 可按异常类型分支 |
| 日志与排障 | ❌ 弱 | ✅ 可打印完整堆栈 |
| 代码量 | 少 | 略多(匿名内部类) |
| 生产推荐 | 不推荐 | 强烈推荐 |
六、启用兜底的前置条件(重要)
仅编写 fallback 类是不够的,必须开启熔断/容错集成,兜底才会生效。
6.1 方式 A:Sentinel
依赖:
xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置:
yaml
feign:
sentinel:
enabled: true
然后配置 @FeignClient(fallback = ...) 或 fallbackFactory = ... 即可。
java
@Configuration
public class FeignConfig {
@Bean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY; // 测试 fallback 时可临时使用
}
}
生产环境再按业务配置合理的重试策略,避免与兜底行为冲突。
6.2 方式 B:Spring Cloud CircuitBreaker + Resilience4j(新版推荐)
依赖:
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
配置:
yaml
spring:
cloud:
openfeign:
circuitbreaker:
enabled: true
同样配合 @FeignClient(fallbackFactory = ...) 使用。Feign 调用会被 Resilience4j 包装,失败或熔断打开时触发 fallback。
6.3 两种方式怎么选
| Sentinel | CircuitBreaker (Resilience4j) | |
|---|---|---|
| 生态 | Spring Cloud Alibaba | Spring Cloud 官方 |
| 流控规则 | Sentinel 控制台,功能丰富 | 需额外配置 |
| 与 Feign fallback | feign.sentinel.enabled=true |
spring.cloud.openfeign.circuitbreaker.enabled=true |
| 新项目 | 已用 Alibaba 全家桶可选 | Spring 系优先 |
七、fallback 什么时候会被触发
常见触发场景:
| 场景 | 是否走 fallback |
|---|---|
| 连接超时 | ✅ |
| 读超时 | ✅ |
| 下游 5xx | ✅(视熔断配置) |
| 下游 4xx | ⚠️ 默认可能不触发,需 ErrorDecoder 或额外配置 |
| 服务实例不可用 | ✅ |
| Sentinel / 熔断器打开 | ✅,直接回调,不再发远程请求 |
| 下游正常 200 | ❌ |
熔断策略(慢调用比例、异常比例、异常数)触发后,会直接走 OpenFeign 的 fallback,不再发起远程调用。这是 fallback 与熔断配合的典型价值。
八、fallback 与 Sentinel 其他机制的区别
在这里区分三类异常处理,容易混淆:
| 机制 | 作用位置 | 用途 |
|---|---|---|
BlockExceptionHandler |
Web 接口层 | 处理 HTTP 请求被 Sentinel 限流 |
@SentinelResource 的 blockHandler / fallback |
本地方法 | 保护本服务内的方法 |
@FeignClient 的 fallback / fallbackFactory |
远程调用 | 下游调用失败时的兜底 |
三者不要混用同一套方法。Feign 远程调用失败,应走 Feign 自己的 fallback ,而不是在调用方随便写一个 @SentinelResource。
方法签名要用 Throwable,不要用 BlockException,否则 fallback 可能不生效。
java
// ❌ 容易踩坑
public Order xxxFallback(Long id, BlockException e) { ... }
// ✅ 推荐
public Order xxxFallback(Long id, Throwable e) { ... }
九、生产级 fallback 写法建议
9.1 必须打日志,且优先用 fallbackFactory
java
log.error("调用 {} 失败, 参数={}, 原因={}",
"ProductFeignClient#getProductById", productId, cause.getMessage(), cause);
没有异常栈,线上很难查「为什么走了兜底」。
9.2 返回语义要明确
兜底返回值要让调用方能区分「真实数据」和「降级数据」:
java
// 方式 1:统一响应包装
return Result.fail("PRODUCT_SERVICE_UNAVAILABLE", "商品服务暂不可用");
// 方式 2:领域对象带降级标记
product.setDegraded(true);
避免把「未知商品、价格 0」当成真实业务数据继续往下走。
9.3 写操作要特别谨慎
| 操作类型 | 兜底策略 |
|---|---|
| 查询 | 返回空列表、默认值、缓存 |
| 创建/更新/删除 | 不要静默成功;应抛出业务异常或返回明确失败,防止脏数据 |
例如 insertOrUpdateByErpCode 这类写接口,fallback 里返回 void 且不报错,可能导致上游以为同步成功。
9.4 配合超时配置
兜底常因超时触发,应同时配置:
yaml
spring:
cloud:
openfeign:
client:
config:
default:
connectTimeout: 3000
readTimeout: 5000
service-product:
readTimeout: 8000
超时太长:用户等待久;太短:频繁误触发 fallback。
9.5 测试兜底链路
建议的验证方式:
- 停掉下游服务,或
- 在下游 Controller 人为抛出异常,或
- 在 Sentinel 控制台配置熔断规则
然后观察是否进入 fallback、日志是否完整。
十、进阶:统一兜底 vs 精细兜底
10.1 每个 Feign 客户端各自 fallback
java
@FeignClient(value = "service-product", fallbackFactory = ProductFallbackFactory.class)
@FeignClient(value = "service-user", fallbackFactory = UserFallbackFactory.class)
职责清晰,推荐。
10.2 抽象公共 fallback 逻辑
多个 Feign 客户端时,可抽取基类或工具方法:
java
protected <T> T degradeQuery(Throwable cause, Supplier<T> defaultSupplier) {
log.error("Feign 查询降级", cause);
return defaultSupplier.get();
}
10.3 与 ErrorDecoder 配合
- ErrorDecoder:把 HTTP 4xx/5xx 转成 Java 异常
- fallbackFactory:在异常或熔断后给出最终返回
两者配合,链路更完整:
远程调用 → ErrorDecoder 解析错误 → 仍失败 → fallbackFactory 兜底
十一、完整最小可运行示例(CircuitBreaker 方案)
1. 依赖
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
2. 配置
yaml
spring:
cloud:
openfeign:
circuitbreaker:
enabled: true
3. 接口 + fallbackFactory
4. 启动类
java
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
5. 调用
java
@Service
public class OrderService {
@Autowired
private ProductFeignClient productFeignClient;
public void createOrder(Long productId) {
Product product = productFeignClient.getProductById(productId);
// 若下游挂了,这里拿到的是 fallback 返回的降级对象
}
}
十二、常见问题
Q1:写了 fallback 类,为什么不生效?
检查清单:
- 是否开启
feign.sentinel.enabled=true或spring.cloud.openfeign.circuitbreaker.enabled=true - fallback / fallbackFactory 类是否加了
@Component -
@FeignClient的fallback和fallbackFactory是否只选了一个 - 是否
@EnableFeignClients扫描到了 Feign 接口包 - 测试时是否被 Retryer 不断重试「掩盖」了
Q2:fallback 和 fallbackFactory 能一起用吗?
不能。@FeignClient 上两者互斥。
Q3:fallback 里可以再调别的远程服务吗?
技术上可以,但不建议链式远程调用,容易级联失败。优先返回本地默认值或缓存。
Q4:返回 null 可以吗?
查询可以(调用方需判空),写操作不建议。
十三、小结
| 要点 | 说明 |
|---|---|
| fallback 是什么 | Feign 远程调用失败时的兜底实现 |
| fallback vs fallbackFactory | 后者能拿 Throwable,生产用 Factory |
| 启用条件 | 必须开启 Sentinel 或 CircuitBreaker,光写类不够 |
| 与 Sentinel 关系 | 熔断触发后直接走 fallback,不再请求下游 |
| 写操作兜底 | 切忌静默成功 |
| 测试技巧 | 关重试、停下游、配熔断规则 |
OpenFeign 让远程调用像本地方法一样简单;fallback / fallbackFactory 则让这种简单不会在故障时变成灾难。
延伸阅读