Skip to main content

第10章 熔断

第9章讲了微服务的全链路日志问题,本章就来谈谈所有微服务会遇到的第二个问题——熔断

你可能有个疑问:熔断不是流量大时才会出现吗?其实不是的,这里存在一定误区。

熔断问题

10.1 业务场景:如何预防一个服务故障影响整个系统

在一个新零售架构系统中,有一个通用用户服务(很多页面都会使用),它包含两个接口。

问题一:请求慢

服务调用关系:User API → Basic Data Service → 3rd Location API

在 Basic Data Service 中,接口 /currentCarLocation 需要调用第三方系统的数据,但第三方响应速度很慢,而且有时还会发生故障。

问题表现

  • 用户反馈 App 整体运行速度慢到无法接受
  • 通过 Thread Dump 发现 User API 的线程请求数接近极限值
  • 所有线程都在访问第三方接口
  • 因为连接数满了,其他页面不再受理 User API 的请求

问题二:流量洪峰缓存超时

服务调用关系:APP → User API → Basic Data Service → Redis/数据库

问题表现

  1. 流量高峰时 Redis 中的通用权限列表超时
  2. 那一瞬间所有线程都需要去数据库读取数据
  3. 数据库 CPU 使用率升到 100%
  4. Basic Data Service 停止工作
  5. User API 所有线程都堵塞
  6. App 上的所有操作都不能使用

10.2 需要解决的问题

线程隔离

假设 User API 的最大连接数是 1000,每次调用 /currentCarLocation 时速度很慢:

  • 可能 1000 个连接线程全部都在调用这个慢接口
  • 希望控制 /currentCarLocation 的调用请求数不超过 50 条
  • 保证至少还有 950 条连接可用于处理常规请求
  • 如果超过 50 条,设计备用逻辑进行处理

熔断

当数据库压力太大时:

  • 发现近期某个接口的请求经常出现异常时,先不访问接口的服务
  • 发现某个接口的请求总是超时时,先别访问它

10.3 Sentinel 和 Hystrix

线程隔离

特性HystrixSentinel
隔离策略线程池隔离/信号量隔离信号量隔离
熔断降级策略基于失败比率基于响应时间或失败比率
实时指标滑动窗口滑动窗口
规则配置多种数据源多种数据源
社区活跃度停止开发新功能活跃

最终选择 Hystrix 的原因:

  1. 满足需求
  2. 团队里有人用过 Hystrix,并通读了它的源代码
  3. 它是 Spring Cloud 默认自带的

10.4 Hystrix 的设计思路

10.4.1 线程隔离机制

当前服务与其他接口存在强依赖关系,且每个依赖都有一个隔离的线程池

例如:

  • 调用接口 A 时,并发线程的最大个数是 10
  • 调用接口 M 时,并发线程的最大个数是 5

这样就不会出现一个慢接口把所有连接线程都卷入的问题。

两种隔离方式

隔离方式原理优点缺点
线程池每个依赖接口维护一个线程池可中断调用切换线程资源损耗大
信号量使用计数器控制并发数切换快接口开始调用就无法中断

10.4.2 熔断机制

Hystrix状态

1. 在哪种条件下会触发熔断

在一个滚动统计时间窗口中,若:

  • 调用接口的总数量达到 circuitBreakerRequestVolumeThreshold
  • 超时或异常的调用次数与总调用次数之比超过 circuitBreakerErrorThresholdPercentage

就会触发熔断。

2. 熔断了会怎么样

circuitBreakerSleepWindowInMilliseconds 时间内,不再对外调用接口,而是直接调用本地的降级方法

@HystrixCommand(fallbackMethod = "fallbackMethod")
public String callService() {
return restTemplate.getForObject(url, String.class);
}

public String fallbackMethod() {
return "服务暂时不可用,请稍后重试";
}

3. 熔断后怎么恢复

到达熔断休眠时间后:

  1. 放开对接口的限制(断路器状态为 HALF-OPEN
  2. 尝试使用一个请求去调用接口
  3. 如果调用成功,恢复正常(状态为 CLOSED
  4. 如果调用失败或超时,重新等待熔断休眠时间

10.4.3 滚动(滑动)时间窗口

滑动窗口

假设时间窗口设置为 10 秒,numBuckets 设置为 10:

  • 时间窗口划分为 10 小份,每份是 1 秒
  • 每隔 1 秒都有一个时间窗口
  • 1分0秒~1分10秒统计一次
  • 1分1秒~1分11秒统计一次
  • 以此类推...

在每个桶中,Hystrix 会统计:

  • 成功数
  • 失败数
  • 超时数
  • 拒绝数

10.4.4 Hystrix 调用接口的请求处理流程

成功流程

  1. 发起请求
  2. 检查断路器状态
  3. 检查线程池/信号量
  4. 执行 HystrixCommand
  5. 计算健康状态
  6. 返回成功

失败流程

  1. 发起请求
  2. 检查断路器状态(若熔断则直接降级)
  3. 检查线程池/信号量(若满则直接降级)
  4. 执行 HystrixCommand(若失败/超时则降级)
  5. 执行 Fallback 方法
  6. 返回降级结果

10.5 注意事项

10.5.1 数据一致性

假设服务 A 更新了数据库,在调用服务 B 时直接降级了:

  • 服务 A 的数据库更新是否需要回滚?
  • 服务 B 向服务 A 返回成功还是失败?

关于这个问题没有固定的设计标准,需要结合具体需求进行设计。

10.5.2 超时降级

服务 A 调用服务 B 时超时了:

  • 服务 A 调用了降级方法
  • 但服务 B 已经在执行工作并且没有中断
  • 服务 B 处理成功后,会返回成功结果给服务 A
  • 服务 A 已经使用了降级方法
  • 导致服务 B 中的数据出现异常

10.5.3 用户体验

请求触发熔断后的 3 种情况:

情况处理方式
读数据请求降级在界面上给用户提示,或隐藏缺失的数据
写数据请求改为异步根据实际情况判断是否给用户提示
写数据请求回滚必须提示用户重新操作

10.5.4 熔断监控

熔断功能上线后只是完成了第一步:

  • Hystrix 是事前配置的熔断框架
  • 配置对不对、效果好不好,只有实际使用后才知道
  • 需要从 Hystrix 的监控面板查看各个服务的熔断数据
  • 根据实际情况再做调整

10.6 小结

熔断总结

解决的问题

引入 Hystrix 的项目方案一周就上线了,下面两个问题很快解决:

  1. 下游接口慢导致当前服务所有连接池的线程被占满
  2. 下游接口慢导致所有上游的接口雪崩

之后系统就没有再出现相关错误了。

Hystrix 的不足

Hystrix 的设计思想是事前配置熔断机制:

  • 要事先预见流量情况、系统负载能力
  • 预先配置好熔断机制
  • 如果实际情况与预测不一样,预先配置好的机制达不到预期效果

所以项目上线后,项目组又根据监控情况调整了几次参数。

2018年后,Netflix 官方已不再为 Hystrix 开发新功能,转向开发 Resilience4j,对于 Hystrix 原有功能只做简单维护。

面试常见问题

熔断和限流都是高并发场景中面试官最喜欢问的问题,接下来将讲解限流