在微服务架构中服务熔断和降级是保证系统高可用的重要手段。

当某个服务出现故障,或者响应过慢的时候,通过熔断可以快速失败避免故障蔓延导致雪崩。通过降级可以返回默认值,或者兜底数据保证核心功能可用。

但是很多人对熔断降级的理解只停留在概念层面。实际使用的时候,经常会遇到各种性能问题。

我在之前,的一个项目中就遇到了熔断降级导致性能下降的问题。最开始系统响应很慢延迟很高经过一系列优化才慢慢变快。

今天分享一下我在实际项目中做服务熔断降级性能优化的实战经验。

一、问题背景

先说说项目的背景。

我们的系统是一个微服务架构有几十个服务互相调用。为了保证系统的高可用我们引入了熔断降级框架用的是Hystrix。

最开始接入Hystrix的时候,很简单在需要熔断降级的方法上加一个注解配置一下参数就搞定了。

但是上线之后,我们发现系统的性能反而下降了。响应时间变长了吞吐量下降了CPU使用率也升高了。

我们很困惑熔断降级不是应该提升系统的可用性和性能吗?怎么反而下降了?

于是我们开始排查问题优化性能。

二、排查问题

我们用了各种工具来排查问题。

首先,看了监控发现接口的响应时间确实变长了。特别是一些本来很快的接口响应时间也变长了。

然后用JProfiler做了CPU采样发现很多CPU时间花在了Hystrix相关的代码上。

具体来说,主要花在这几个地方:

  1. HystrixCommand的创建和销毁:每次调用被Hystrix包装的方法都会创建一个HystrixCommand对象调用结束后销毁。这个创建和销毁的过程有一定的开销。
  2. 线程池的管理:Hystrix默认用线程池隔离每个命令都在独立的线程池里执行。线程池的管理线程切换都有开销。
  3. 熔断器的状态检查:每次调用都要检查熔断器的状态是关闭打开还是半开。这个检查也有开销。
  4. 指标的收集和统计:Hystrix会收集各种指标,比如成功失败超时等等,然后统计计算。这个也有开销。

发现了问题之后,我们就开始针对性地优化。

三、优化一:合理配置线程池

第一个优化是合理配置线程池。

Hystrix默认每个命令都用独立的线程池。这样隔离性好,但是线程池太多了线程切换的开销大内存占用也大。

我们最开始就是每个接口都配置了独立的线程池结果系统里有,几十个线程池几百个线程光线程切换就花了很多CPU。

优化方法是把相关的命令合并到同一个线程池。比如同一个服务的多个接口可以共用一个线程池。

这样线程池的数量大大减少线程数量也减少线程切换的开销降低内存占用也降低。

当然合并线程池会降低隔离性。如果一个接口出问题可能会影响同一个线程池里的其他接口。

所以要根据实际情况权衡。核心的重要的接口可以单独线程池。非核心的不重要的接口可以合并线程池。

我们最后把几十个线程池合并成了十几个性能提升了不少。

四、优化二:选择合适的隔离策略

第二个优化是选择合适的隔离策略。

Hystrix支持两种隔离策略:线程池隔离和信号量隔离。

线程池隔离是默认的隔离性好,但是开销大。信号量隔离开销小,但是隔离性差一些。

我们最开始所有的命令都用的线程池隔离。但是有些命令其实不需要线程池隔离。

比如一些很快的本地方法,或者一些不会阻塞的方法用信号量隔离就够了。

信号量隔离没有线程切换的开销性能更好。

所以我们把一些不需要线程池隔离的命令改成了信号量隔离。

比如一些纯计算的方法一些内存操作的方法都改成了信号量隔离。

这样又减少了一些线程池和线程性能又提升了一些。

五、优化三:合理配置超时时间

第三个优化是合理配置超时时间。

Hystrix默认的超时时间是1秒。我们最开始所有的命令都用的默认的1秒超时。

但是有些命令本来就需要比较长的时间,比如一些复杂的查询一些大文件的处理。1秒超时太短了经常超时触发降级。

而有些命令本来应该很快,比如一些简单的查询一些缓存操作。1秒超时又太长了出问题的时候,不能快速失败。

所以我们根据每个命令的实际情况调整了超时时间。

需要长时间的命令超时时间设长一点,比如3秒5秒。应该很快的命令超时时间设短一点,比如200毫秒500毫秒。

这样既避免了不必要的超时降级又保证了出问题的时候,能快速失败。

而且超时时间合理了之后,线程占用的时间也更合理线程池的利用率更高性能也更好。

六、优化四:优化降级逻辑

第四个优化是优化降级逻辑。

降级逻辑是当熔断触发,或者调用失败的时候,执行的兜底逻辑。

我们最开始的降级逻辑很简单就是返回一个默认值,或者空值。

但是后来发现有些降级逻辑本身就很慢,或者有问题。

比如有一个降级逻辑是查询缓存返回缓存数据。但是缓存查询本身也可能慢也可能失败。如果降级逻辑也慢,或者失败那就起不到降级的作用了。

所以我们优化了降级逻辑确保降级逻辑本身很快很可靠。

比如降级逻辑尽量不要做复杂的操作不要调用其他服务不要查询数据库。尽量返回静态的默认值,或者内存中的缓存数据。

而且降级逻辑也要有超时保护避免降级逻辑本身超时。

优化了降级逻辑之后,系统在降级的时候,响应也很快不会,因为降级而变慢。

七、优化五:合理配置熔断器参数

第五个优化是合理配置熔断器参数。

Hystrix的熔断器有几个重要的参数:

  • circuitBreaker.requestVolumeThreshold:触发熔断的最小请求数。默认20。
  • circuitBreaker.errorThresholdPercentage:触发熔断的错误率阈值。默认50%。
  • circuitBreaker.sleepWindowInMilliseconds:熔断打开后多久进入半开状态。默认5秒。

我们最开始都用的默认值。但是默认值不一定适合所有的场景。

比如有些接口请求量很小可能一分钟才几个请求。默认的20个请求阈值可能永远达不到熔断器永远不会触发。

比如有些接口对错误率很敏感50%的错误率太高了应该设低一点,比如30%。

比如有些接口恢复比较慢5秒的休眠时间太短了应该设长一点,比如10秒30秒。

所以我们根据每个接口的实际情况调整了熔断器参数。

这样熔断器能更及时地触发也能更合理地恢复性能和可用性都更好。

八、优化六:减少不必要的熔断

第六个优化是减少不必要的熔断。

最开始我们为了保险几乎所有的接口都加了熔断降级。

但是后来发现有些接口其实不需要熔断降级。

比如一些很简单的本地方法一些纯计算的方法一些不会失败的方法。这些方法加了熔断反而增加了开销降低了性能。

所以我们梳理了一遍把不必要的熔断去掉了。

只在那些可能失败的可能慢的调用外部服务的方法上保留熔断降级。

这样又减少了很多开销性能又提升了。

九、优化效果

经过这一系列优化我们系统的性能提升了很多。

响应时间从平均500毫秒降到了100毫秒左右。吞吐量提升了3倍多。CPU使用率降低了40%左右。

而且系统的可用性也提升了。熔断降级能更及时地触发更合理地恢复故障不会蔓延不会雪崩。

从最开始的慢响应高延迟到后来的快响应低延迟中间踩了很多坑也积累了很多经验。

十、总结和建议

最后总结一下服务熔断降级性能优化的一些建议。

1. 不要盲目加熔断:熔断降级不是越多越好。只在需要的地方加不必要的地方不要加。

2. 合理配置线程池:不要每个命令都独立线程池。相关的命令可以合并线程池。根据实际情况权衡隔离性和性能。

3. 选择合适的隔离策略:需要强隔离的用线程池隔离。不需要的可以用信号量隔离性能更好。

4. 合理配置超时时间:根据每个命令的实际情况设置合适的超时时间不要都用默认值。

5. 优化降级逻辑:确保降级逻辑本身很快很可靠不要让降级逻辑成为新的瓶颈。

6. 合理配置熔断器参数:根据每个接口的实际情况调整熔断器参数让熔断器能更及时地触发更合理地恢复。

7. 监控和调优:熔断降级的配置不是一成不变的。要持续监控根据实际情况调优。

希望这些经验能帮大家更好地使用熔断降级提升系统的性能和可用性。

熔断降级是一把双刃剑用得好能提升系统的高可用。用得不好反而会降低性能。关键是根据实际情况合理配置持续优化。