断路器与容错
在微服务架构中,服务之间的依赖关系复杂,一个服务的故障可能引发连锁反应,导致整个系统崩溃。断路器模式是解决这一问题的核心方案。
为什么需要断路器
理解断路器的价值,需要先看看没有断路器时的系统行为。
雪崩效应
假设系统中有服务 A 调用服务 B,服务 B 调用服务 C。如果服务 C 出现故障,响应变慢或超时,会发生什么?
服务 B 调用服务 C 时需要等待响应。如果大量请求在等待,服务 B 的线程池逐渐耗尽。线程池耗尽后,服务 B 无法接收新请求,也无法响应其他调用。服务 A 调用服务 B 超时,也开始堆积请求。最终,整个系统因为一个服务的故障而瘫痪。
这就是雪崩效应:一个服务的故障像滚雪球一样,逐渐影响到整个系统。
故障隔离
断路器通过故障隔离机制防止雪崩。当断路器检测到下游服务故障时,直接返回错误或降级结果,不再发起实际调用。这样可以快速失败,释放线程资源,防止故障蔓延。
断路器状态机
断路器有三个状态:
关闭状态(Closed)是正常状态,请求正常通过。断路器统计请求的成功率和响应时间。
打开状态(Open)是熔断状态,请求直接返回错误或降级结果,不再调用下游服务。断路器等待超时后进入半开状态。
半开状态(Half-Open)是探测状态,允许少量请求通过测试下游服务是否恢复。如果成功,进入关闭状态;如果失败,回到打开状态。
状态转换规则:关闭状态下,失败率超过阈值时进入打开状态;打开状态下,超时后进入半开状态;半开状态下,成功率恢复正常则进入关闭状态,失败则回到打开状态。
Sentinel
Sentinel 是阿里巴巴开源的流量控制和熔断降级组件。它功能丰富、性能优异,是国内微服务架构的首选容错方案。
Sentinel 核心概念
Sentinel 提供了多个维度的流量控制能力:
流量控制限制 QPS 或并发线程数,防止系统被突发流量冲垮。
熔断降级根据响应时间或异常比例熔断服务,防止故障蔓延。
系统保护根据系统整体负载(CPU、内存、QPS)进行保护。
热点参数限流针对请求参数进行细粒度限流。
来源访问控制根据调用来源进行黑白名单控制。
快速开始
添加依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置 Sentinel Dashboard 地址:
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
port: 8719
eager: true # 立即初始化,不等待首次访问
启动 Sentinel Dashboard:
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
访问 http://localhost:8080,默认用户名密码都是 sentinel。
流量控制
流量控制通过限制 QPS 或并发线程数来保护系统。
QPS 限流:
@GetMapping("/user/{id}")
@SentinelResource(value = "getUser", blockHandler = "getUserBlockHandler")
public User getUser(@PathVariable Long id) {
return userService.getById(id);
}
// 限流处理方法,方法签名需要和原方法一致,最后加 BlockException 参数
public User getUserBlockHandler(Long id, BlockException ex) {
User user = new User();
user.setId(id);
user.setName("限流降级用户");
return user;
}
在 Sentinel Dashboard 中配置流控规则:
- 资源名:getUser
- 阈值类型:QPS
- 单机阈值:10
当 QPS 超过 10 时,多余的请求会触发 blockHandler 方法。
并发线程数限流:
@GetMapping("/order/{id}")
@SentinelResource(value = "getOrder", blockHandler = "getOrderBlockHandler")
public Order getOrder(@PathVariable Long id) {
return orderService.getById(id);
}
配置:
- 资源名:getOrder
- 阈值类型:并发线程数
- 单机阈值:5
当处理该请求的线程数超过 5 时,新请求会被拒绝。
熔断降级
Sentinel 支持三种熔断策略:慢调用比例、异常比例、异常数。
慢调用比例熔断:
当响应时间超过阈值的调用比例过高时触发熔断。
@GetMapping("/product/{id}")
@SentinelResource(value = "getProduct", fallback = "getProductFallback")
public Product getProduct(@PathVariable Long id) {
return productService.getById(id);
}
public Product getProductFallback(Long id, Throwable ex) {
Product product = new Product();
product.setId(id);
product.setName("熔断降级商品");
return product;
}
配置熔断规则:
- 资源名:getProduct
- 熔断策略:慢调用比例
- 最大 RT:500ms(响应时间超过 500ms 为慢调用)
- 比例阈值:0.5(慢调用比例超过 50% 触发熔断)
- 熔断时长:10s
- 最小请求数:5
异常比例熔断:
当异常比例超过阈值时触发熔断。
@GetMapping("/payment/{id}")
@SentinelResource(value = "getPayment", fallback = "getPaymentFallback")
public Payment getPayment(@PathVariable Long id) {
return paymentService.getById(id);
}
配置:
- 资源名:getPayment
- 熔断策略:异常比例
- 比例阈值:0.3(异常比例超过 30% 触发熔断)
- 熔断时长:10s
- 最小请求数:5
异常数熔断:
当异常数量超过阈值时触发熔断。
热点参数限流
热点参数限流可以对请求参数进行细粒度控制。例如,限制对某个热门商品的访问频率。
@GetMapping("/product/detail")
@SentinelResource(value = "getProductDetail", blockHandler = "getProductDetailBlockHandler")
public Product getProductDetail(@RequestParam Long id) {
return productService.getById(id);
}
public Product getProductDetailBlockHandler(Long id, BlockException ex) {
return new Product(id, "热点参数限流");
}
通过代码配置热点规则:
@Configuration
public class SentinelConfig {
@PostConstruct
public void init() {
ParamFlowRule rule = new ParamFlowRule();
rule.setResource("getProductDetail");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(5); // 每秒最多 5 次
rule.setParamIdx(0); // 第一个参数(id)
// 特定参数值例外
ParamFlowItem item = new ParamFlowItem();
item.setObject("1001"); // 商品 ID 为 1001
item.setClassType(Long.class.getName());
item.setCount(100); // 每秒最多 100 次
rule.setParamFlowItemList(Collections.singletonList(item));
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
}
}
系统自适应保护
系统自适应保护根据系统整体负载进行保护,防止系统过载。
@Configuration
public class SystemRuleConfig {
@PostConstruct
public void init() {
List<SystemRule> rules = new ArrayList<>();
// CPU 使用率保护
SystemRule cpuRule = new SystemRule();
cpuRule.setHighestCpuUsage(0.8); // CPU 使用率超过 80%
rules.add(cpuRule);
// 系统负载保护
SystemRule loadRule = new SystemRule();
loadRule.setHighestSystemLoad(10); // 系统负载超过 10
rules.add(loadRule);
// 入口 QPS 保护
SystemRule qpsRule = new SystemRule();
qpsRule.setQps(1000); // 入口总 QPS 超过 1000
rules.add(qpsRule);
// 平均响应时间保护
SystemRule rtRule = new SystemRule();
rtRule.setAvgRt(500); // 平均响应时间超过 500ms
rules.add(rtRule);
SystemRuleManager.loadRules(rules);
}
}
规则持久化
默认情况下,Sentinel 规则存储在内存中,重启后丢失。可以将规则持久化到 Nacos。
添加依赖:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
配置:
spring:
cloud:
sentinel:
datasource:
flow:
nacos:
server-addr: localhost:8848
data-id: ${spring.application.name}-flow-rules
group-id: SENTINEL_GROUP
rule-type: flow
degrade:
nacos:
server-addr: localhost:8848
data-id: ${spring.application.name}-degrade-rules
group-id: SENTINEL_GROUP
rule-type: degrade
blockHandler 与 fallback 的区别
blockHandler 处理 Sentinel 规则触发的限流、熔断,方法签名需要添加 BlockException 参数。
fallback 处理业务异常,方法签名需要添加 Throwable 参数。
两者可以同时使用:
@GetMapping("/user/{id}")
@SentinelResource(
value = "getUser",
blockHandler = "getUserBlockHandler",
fallback = "getUserFallback"
)
public User getUser(@PathVariable Long id) {
return userService.getById(id);
}
// 处理限流、熔断
public User getUserBlockHandler(Long id, BlockException ex) {
return new User(id, "限流降级用户");
}
// 处理业务异常
public User getUserFallback(Long id, Throwable ex) {
return new User(id, "异常降级用户");
}
Resilience4j
Resilience4j 是一个轻量级的容错库,是 Hystrix 的替代方案。相比 Sentinel,Resilience4j 更加轻量,模块化程度更高。
核心模块
| 模块 | 说明 |
|---|---|
| CircuitBreaker | 断路器 |
| RateLimiter | 限流器 |
| Retry | 重试 |
| TimeLimiter | 超时限制 |
| Bulkhead | 舱壁隔离 |
| Cache | 缓存 |
添加依赖
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
</dependency>
如果使用 AOP 方式:
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-reactor</artifactId>
</dependency>
断路器配置
resilience4j:
circuitbreaker:
configs:
default:
slidingWindowType: COUNT_BASED # 滑动窗口类型
slidingWindowSize: 10 # 滑动窗口大小
failureRateThreshold: 50 # 失败率阈值(%)
waitDurationInOpenState: 10s # 打开状态等待时间
permittedNumberOfCallsInHalfOpenState: 3 # 半开状态允许的调用数
slowCallDurationThreshold: 2s # 慢调用阈值
slowCallRateThreshold: 50 # 慢调用比例阈值(%)
instances:
userService:
baseConfig: default
orderService:
slidingWindowSize: 20
failureRateThreshold: 60
使用断路器
@Service
public class OrderService {
@Autowired
private UserClient userClient;
@CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
public User getUser(Long userId) {
return userClient.getUser(userId);
}
private User getUserFallback(Long userId, Exception ex) {
log.warn("获取用户信息失败,使用降级数据: {}", ex.getMessage());
User user = new User();
user.setId(userId);
user.setName("降级用户");
return user;
}
}
限流器配置
resilience4j:
ratelimiter:
configs:
default:
limitForPeriod: 10 # 每个周期允许的请求数
limitRefreshPeriod: 1s # 周期
timeoutDuration: 0 # 等待许可的超时时间
instances:
orderApi:
limitForPeriod: 100
limitRefreshPeriod: 1s
使用限流器
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@RateLimiter(name = "orderApi", fallbackMethod = "createOrderFallback")
@PostMapping("/order")
public Order createOrder(@RequestBody CreateOrderRequest request) {
return orderService.create(request);
}
private Order createOrderFallback(CreateOrderRequest request, Exception ex) {
throw new BusinessException("系统繁忙,请稍后重试");
}
}
重试配置
resilience4j:
retry:
configs:
default:
maxAttempts: 3 # 最大尝试次数(包括首次调用)
waitDuration: 500ms # 重试间隔
enableExponentialBackoff: true # 启用指数退避
exponentialBackoffMultiplier: 2
retryExceptions:
- java.io.IOException
- java.net.SocketTimeoutException
ignoreExceptions:
- com.example.BusinessException
instances:
paymentService:
maxAttempts: 5
waitDuration: 1s
使用重试
@Service
public class PaymentService {
@Autowired
private PaymentClient paymentClient;
@Retry(name = "paymentService", fallbackMethod = "processFallback")
public PaymentResult process(PaymentRequest request) {
return paymentClient.process(request);
}
private PaymentResult processFallback(PaymentRequest request, Exception ex) {
log.error("支付处理失败: {}", ex.getMessage());
return PaymentResult.fail("支付处理失败");
}
}
舱壁隔离
舱壁隔离限制对下游服务的并发调用数,防止线程池耗尽。
resilience4j:
bulkhead:
configs:
default:
maxConcurrentCalls: 10 # 最大并发调用数
maxWaitDuration: 0 # 等待许可的最长时间
instances:
userService:
maxConcurrentCalls: 20
maxWaitDuration: 500ms
@Service
public class UserService {
@Bulkhead(name = "userService", fallbackMethod = "getUserFallback")
public User getUser(Long id) {
return userRepository.findById(id);
}
private User getUserFallback(Long id, Exception ex) {
return new User(id, "舱壁隔离降级");
}
}
组合使用
多个注解可以组合使用:
@Service
public class OrderService {
@CircuitBreaker(name = "orderService", fallbackMethod = "fallback")
@RateLimiter(name = "orderService")
@Retry(name = "orderService")
@Bulkhead(name = "orderService")
@TimeLimiter(name = "orderService")
public CompletableFuture<Order> createOrder(OrderRequest request) {
return CompletableFuture.supplyAsync(() -> {
return orderClient.create(request);
});
}
private CompletableFuture<Order> fallback(OrderRequest request, Exception ex) {
return CompletableFuture.completedFuture(
new Order(null, "服务降级订单")
);
}
}
Sentinel vs Resilience4j
| 特性 | Sentinel | Resilience4j |
|---|---|---|
| 流量控制 | 支持多种维度 | 仅支持 QPS |
| 熔断策略 | 慢调用、异常比例、异常数 | 慢调用、异常比例、异常数 |
| 控制台 | 完善 | 需额外配置 |
| 社区活跃度 | 高(国内) | 高(国际) |
| 学习成本 | 中 | 低 |
| 侵入性 | 较高(注解) | 较低(AOP) |
对于国内团队,推荐使用 Sentinel,社区活跃,文档完善。对于追求轻量级的团队,推荐使用 Resilience4j。
最佳实践
合理设置阈值
阈值设置需要根据业务特点和系统容量来确定。核心服务设置较宽松的阈值,非核心服务设置较严格的阈值。通过压测确定系统的容量边界。初期可以设置较宽松,逐步收紧。
区分降级策略
核心服务降级时返回缓存数据或默认值,保证基本可用。非核心服务降级时返回空数据或错误提示,快速失败。根据业务场景选择合适的降级策略。
监控告警
监控断路器的状态变化,打开时及时告警。监控限流的触发次数,频繁触发时考虑扩容。监控服务的响应时间和错误率,设置合理的熔断阈值。
日志记录
记录熔断、限流触发时的上下文信息,便于问题排查。记录降级调用的详细信息,评估降级影响。
测试验证
在测试环境模拟故障,验证熔断、降级是否正常工作。压测验证限流配置是否合理。
小结
本章详细介绍了断路器与容错:
断路器通过状态机实现故障隔离,防止雪崩效应。Sentinel 是阿里巴巴的流量控制和熔断降级组件,功能丰富。Resilience4j 是轻量级容错库,模块化设计。合理设置阈值、区分降级策略、监控告警是最佳实践。