跳到主要内容

降级与熔断

在微服务架构中,服务之间的调用非常频繁。当某个服务出现故障或响应缓慢时,如果不做处理,可能会导致级联故障,最终拖垮整个系统。OpenFeign 提供了降级和熔断机制来应对这种情况。

为什么需要降级与熔断?

雪崩效应

假设一个请求链路如下:

用户请求 → 网关 → 订单服务 → 用户服务 → 积分服务

如果积分服务响应缓慢或不可用,会发生什么?

  1. 订单服务调用积分服务时线程阻塞等待
  2. 订单服务的线程池逐渐被占满
  3. 订单服务无法处理新请求
  4. 网关调用订单服务也开始阻塞
  5. 整个系统瘫痪

这就是典型的雪崩效应。

解决方案

降级和熔断是应对雪崩效应的两种重要手段:

降级(Fallback):当服务调用失败时,返回一个预设的默认值或执行备选逻辑,而不是直接抛出异常。

熔断(Circuit Breaker):当服务调用失败率达到一定阈值时,自动"熔断",后续请求直接走降级逻辑,不再真正调用远程服务。一段时间后尝试"半开"状态,检测服务是否恢复。

OpenFeign 降级机制

启用降级支持

首先需要添加 Spring Cloud Circuit Breaker 依赖:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>

在配置文件中启用降级:

spring:
cloud:
openfeign:
circuitbreaker:
enabled: true

实现 Fallback

方式一:直接实现接口

// Feign 客户端接口
@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient {

@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Long id);

@GetMapping("/users")
List<User> getAllUsers();
}

// 降级实现类
@Component
public class UserClientFallback implements UserClient {

private static final Logger log = LoggerFactory.getLogger(UserClientFallback.class);

@Override
public User getUserById(Long id) {
log.warn("Fallback: getUserById called with id={}", id);
// 返回默认用户
return new User(id, "默认用户", "[email protected]");
}

@Override
public List<User> getAllUsers() {
log.warn("Fallback: getAllUsers called");
// 返回空列表
return Collections.emptyList();
}
}

方式二:使用 FallbackFactory

FallbackFactory 可以获取到触发降级的异常信息,便于问题排查:

// Feign 客户端接口
@FeignClient(name = "user-service", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {

@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Long id);
}

// 降级工厂类
@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {

private static final Logger log = LoggerFactory.getLogger(UserClientFallbackFactory.class);

@Override
public UserClient create(Throwable cause) {
// 记录异常信息
log.error("UserClient fallback triggered", cause);

return new UserClient() {
@Override
public User getUserById(Long id) {
// 根据异常类型返回不同的降级结果
if (cause instanceof FeignException.NotFound) {
return null; // 用户不存在,返回 null
}
if (cause instanceof FeignException.Unauthorized) {
throw new AuthenticationException("认证失败"); // 认证失败,抛出业务异常
}
// 其他异常,返回默认用户
return new User(id, "默认用户", "[email protected]");
}
};
}
}

Fallback 与 FallbackFactory 的选择

特性FallbackFallbackFactory
实现复杂度简单稍复杂
获取异常信息不支持支持
适用场景简单降级逻辑需要根据异常类型做不同处理

建议优先使用 FallbackFactory,它提供了更强大的异常处理能力。

与 Resilience4j 集成

Resilience4j 是一个轻量级的容错库,提供了熔断、限流、重试等功能。

添加依赖

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>

配置熔断器

resilience4j:
circuitbreaker:
configs:
default:
slidingWindowSize: 10 # 滑动窗口大小
slidingWindowType: COUNT_BASED # 滑动窗口类型:基于计数
failureRateThreshold: 50 # 失败率阈值(百分比)
waitDurationInOpenState: 10s # 熔断器打开后等待时间
permittedNumberOfCallsInHalfOpenState: 3 # 半开状态允许的调用次数
slowCallDurationThreshold: 2s # 慢调用时间阈值
slowCallRateThreshold: 60 # 慢调用率阈值(百分比)
instances:
user-service:
baseConfig: default
failureRateThreshold: 30 # 针对 user-service 的特殊配置

熔断器状态

熔断器有三种状态:

        失败率超过阈值
┌──────────────────────┐
│ │
▼ │
┌────────┐ 等待时间到 ┌────────┐
│ 关闭 │ ──────────▶ │ 打开 │
└────────┘ └────────┘
▲ │
│ │
│ ┌────────┐ │
└────│ 半开 │◀───────┘
失败 └────────┘ 时间到

│ 成功率达标
└──────────────▶ 关闭

关闭(Closed):正常状态,请求正常调用远程服务。

打开(Open):熔断状态,请求直接走降级逻辑,不调用远程服务。

半开(Half-Open):探测状态,允许少量请求通过,检测服务是否恢复。

配置示例

spring:
cloud:
openfeign:
circuitbreaker:
enabled: true
alphanumeric-ids:
enabled: true # 使用字母数字 ID

resilience4j:
circuitbreaker:
instances:
# 熔断器名称格式:FeignClient接口名#方法名(参数类型)
UserClient#getUserById(Long):
slidingWindowSize: 10
failureRateThreshold: 50
waitDurationInOpenState: 10s

与 Sentinel 集成

Sentinel 是阿里巴巴开源的流量控制和熔断降级组件。

添加依赖

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

配置 Sentinel

spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 # Sentinel 控制台地址
port: 8719 # 与控制台通信的端口
eager: true # 应用启动时立即初始化
openfeign:
sentinel:
enabled: true

实现 Sentinel 降级

@FeignClient(name = "user-service", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Long id);
}

@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {

@Override
public UserClient create(Throwable cause) {
return id -> {
// Sentinel 会传入 BlockException
if (cause instanceof BlockException) {
return new User(id, "服务繁忙,请稍后重试", null);
}
return new User(id, "默认用户", "[email protected]");
};
}
}

降级策略设计

返回默认值

适用于非核心功能,缺失不影响主流程:

@Override
public User getUserById(Long id) {
return new User(id, "默认用户", "[email protected]");
}

@Override
public List<User> getAllUsers() {
return Collections.emptyList();
}

返回缓存数据

适用于读多写少的场景:

@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {

private final UserCacheService cacheService;

@Override
public UserClient create(Throwable cause) {
return new UserClient() {
@Override
public User getUserById(Long id) {
// 尝试从缓存获取
User cached = cacheService.getFromCache(id);
if (cached != null) {
return cached;
}
return new User(id, "默认用户", null);
}
};
}
}

抛出业务异常

适用于核心功能,必须告知用户失败:

@Override
public Order createOrder(OrderRequest request) {
throw new ServiceException("订单服务暂时不可用,请稍后重试");
}

调用备用服务

适用于有备用服务的场景:

@Override
public User getUserById(Long id) {
// 尝试调用备用服务
try {
return backupUserClient.getUserById(id);
} catch (Exception e) {
return new User(id, "默认用户", null);
}
}

降级最佳实践

1. 区分核心与非核心功能

@Component
public class OrderClientFallbackFactory implements FallbackFactory<OrderClient> {

@Override
public OrderClient create(Throwable cause) {
return new OrderClient() {
// 核心功能:创建订单,抛出异常
@Override
public Order createOrder(OrderRequest request) {
throw new ServiceException("订单服务暂时不可用");
}

// 非核心功能:查询订单历史,返回空列表
@Override
public List<Order> getOrderHistory(Long userId) {
return Collections.emptyList();
}

// 非核心功能:推荐商品,返回默认推荐
@Override
public List<Product> getRecommendations(Long userId) {
return getDefaultRecommendations();
}
};
}
}

2. 记录降级日志

@Override
public UserClient create(Throwable cause) {
return new UserClient() {
@Override
public User getUserById(Long id) {
log.error("UserClient.getUserById fallback triggered, id={}, error={}",
id, cause.getMessage());
// 发送告警
alertService.sendAlert("user-service fallback");
return new User(id, "默认用户", null);
}
};
}

3. 避免降级逻辑过重

// 不好的做法:降级逻辑太重
@Override
public User getUserById(Long id) {
// 不要在降级逻辑中调用其他可能失败的远程服务
return backupService.getUser(id); // backupService 也可能失败!
}

// 好的做法:降级逻辑简单可靠
@Override
public User getUserById(Long id) {
return new User(id, "默认用户", null);
}

4. 合理设置超时时间

spring:
cloud:
openfeign:
client:
config:
user-service:
connectTimeout: 3000
readTimeout: 5000

超时时间设置原则:

  • 不要太短:避免正常请求被误判为超时
  • 不要太长:避免线程长时间阻塞

5. 监控熔断状态

@RestController
@RequestMapping("/monitor")
public class MonitorController {

private final CircuitBreakerRegistry circuitBreakerRegistry;

@GetMapping("/circuit-breakers")
public Map<String, Object> getCircuitBreakerStatus() {
Map<String, Object> status = new HashMap<>();
circuitBreakerRegistry.getAllCircuitBreakers().forEach(cb -> {
status.put(cb.getName(), Map.of(
"state", cb.getState(),
"metrics", Map.of(
"failureRate", cb.getMetrics().getFailureRate(),
"numberOfCalls", cb.getMetrics().getNumberOfCalls()
)
));
});
return status;
}
}

小结

本章详细介绍了 OpenFeign 的降级与熔断机制:

概念说明适用场景
Fallback服务调用失败时返回默认值非核心功能
FallbackFactory可获取异常信息的降级需要根据异常类型处理
Circuit Breaker失败率达到阈值时熔断防止雪崩效应

降级和熔断是微服务架构中保障系统稳定性的重要手段,合理使用可以大大提高系统的容错能力。

下一章将学习 OpenFeign 的高级特性,包括继承、压缩、缓存等功能。