Feign - 降级选 fallback 还是 fallbackFactory
01前言
什么时候用 fallback,什么时候用 fallbackFactory,以及怎样写才不会踩坑。
02 5 行对比表,一眼看懂差异
03代码实战
3.1 fallback 极简示例
@FeignClient(name = "payment-svc", fallback = PaymentFallback.class)
public interface PaymentClient {@PostMapping("/pay")PaymentResult pay(@RequestBody PaymentRequest req);
}@Component
public class PaymentFallback implements PaymentClient {@Overridepublic PaymentResult pay(PaymentRequest req) {// 固定返回失败,简单粗暴return PaymentResult.error("支付服务开小差了~");}
}
注解:
- @FeignClient 直接指向降级类 PaymentFallback。
- 整个容器里只有一个 PaymentFallback Bean,高并发时别忘了保证无状态。
- 无法感知异常类型,适合提示类兜底文案。
3.2 fallbackFactory 进阶示例
@FeignClient(name = "user-svc", fallbackFactory = UserFallbackFactory.class)
public interface UserClient {@GetMapping("/user/{id}")User getUser(@PathVariable Long id);
}@Component
public class UserFallbackFactory implements FallbackFactory<UserClient> {private static final Logger log = LoggerFactory.getLogger(UserFallbackFactory.class);@Overridepublic UserClient create(Throwable cause) {// 这里可以拿到异常,做各种骚操作return new UserClient() {@Overridepublic User getUser(Long id) {log.warn("用户服务跪了, 原因: {}", cause.getMessage());if (cause instanceof FeignException.NotFound) {return User.unknown(id); // 404 → 未知用户} else if (cause instanceof TimeoutException) {return User.temporary(id); // 超时 → 临时用户}return User.defaultUser(id); // 其他 → 默认用户}};}
}
注解:
- 每次远程调用失败都会触发 create(Throwable),异常对象随便玩。
- 返回的是匿名内部类,天然线程封闭,再也不用加锁。
- 根据异常类型返回不同策略,让降级更智能。
04线程安全对比
为什么 fallbackFactory 更省心?
// ❌ 错误示范:fallback 单例里带状态
@Component
public class UnsafeFallback implements SomeClient {private int counter = 0;public String count() {counter++; // 多线程并发会乱return "Count: " + counter;}
}// ✅ 正确姿势:fallbackFactory 每次都 new
public class SafeFallbackFactory implements FallbackFactory<SomeClient> {public SomeClient create(Throwable cause) {return new SomeClient() {private int counter = 0; // 线程私有,安全!public String count() {counter++;return "Count: " + counter;}};}
}
05混合打法
又想打日志又想高性能?
public class HybridFallbackFactory implements FallbackFactory<SomeClient> {// 预先 new 好一个单例,避免重复创建private static final SomeClient STATIC_FALLBACK = new StaticFallback();@Overridepublic SomeClient create(Throwable cause) {// 只做日志记录,然后返回同一个单例log.error("远程调用失败", cause);return STATIC_FALLBACK;}private static class StaticFallback implements SomeClient {public String getData() {return "static-fallback";}}
}
06性能小贴士
高并发如何减轻 fallbackFactory 的 GC 压力?
public class CachingFallbackFactory implements FallbackFactory<SomeClient> {private final Map<Class<? extends Throwable>, SomeClient> cache = new ConcurrentHashMap<>();@Overridepublic SomeClient create(Throwable cause) {return cache.computeIfAbsent(cause.getClass(),k -> new SomeClient() {public String get() {return "Error: " + k.getSimpleName();}});}
}
注解:
- 用异常类型做 key,同一个异常类只 new 一次。
- ConcurrentHashMap 线程安全,并发无锁。
- 适合异常类型有限且 QPS 极高的场景。
总结
• 业务简单、返回固定文案 → 用 fallback,写完收工。
• 需要看异常、想玩策略、还要线程安全 → 直接上 fallbackFactory。