第7章 熔断、限流、降级

7.1 基础概念

2020年3月,受疫情影响,美股迎来了4次熔断,为啥要熔断?因为如果不熔断美国股市这个大系统会面临系 统崩溃的风险。

以前家里的保险丝有时候会被烧断,现在改成了跳闸,这些都是家庭用电时候触发了家庭电力系统的熔断机制引起的。如果没有这个机制, 随着家里电力负荷的增大,很有可能烧坏电路,引发短路甚至火灾。

熔断,是分布式系统中非常重要的一个概念。一个典型的场景就是在多次发现某个接口或者某处逻辑频繁出现超时或者其他异常情况时,消费者调用提供者的接口耗时过长,超过了某一个既定阈值,就阻断这个接口或者服务的调用。以此来避免“雪崩”出现。

微服务系统中离不开熔断机制,如果离开了熔断机制,就很容易出现如图7.1的情况。当服务A调用服务B,服务B调用服务C这种依赖调用存在时,服务C出现异常,会逐渐阻塞服务B,进而阻塞服务A,导致最后的系统崩溃,这种因为某一个服务引起的整个调用链崩溃,我们称之为雪崩。

760e4a7f90d2489365baa92ff28b22cd.png

图7.1 雪崩图示

为了避免雪崩情况出现,在微服务系统中,我们壮士断腕,舍小保大,不让一个小的错误引起整个系统的崩溃。

且这种情况频繁出现,一般会要求系统短路掉这个调用,不进行实际调用,直接返回一个假设的mock值。

这样,在微服务系统中的一次涉及到若干个服务的调用就不会因为其中的这一点问题导致请求阻塞,系统就不会因为某一个局部的调用出现问题而导致系统整体性能变慢甚至崩溃。


有些时候, 接口或者服务的熔断是因为并发量太大引起的, 在系统运行过程中, 我们并不希望发生熔断, 就像我们不希望家里忽然跳闸一样。为了不至于频繁跳闸,我们可以采取限制大功率电器同时开启,在系统中, 我们也可以减少访问流量来降低熔断发生的概率。 在业务允许的范围内, 设置一个可以接受的阈值, 让系统的运行可以稳定地持续运行。这种情况就是限流,做一个全局拦截器,对每一次请求进行拦截,存储接口以及对应的单位访问次数,就可以做到最基本的限流目的。

限流存在一个比较难以把握的平衡点,对某一个接口到底将流量限制为多少合适呢?这个没有通用的公式,流量大了系统可能崩溃,流量小了又会造成资源的浪费。这种情况下,就可以引入降级策略了。当流量过大时, 不直接限制接口的访问,降级到另外一个接口或者服务。这样不至于让系统直接崩掉,服务也是正常返回的。拿到这个降级返回的调用发起方,也可以根据获取的值调整下一步调用策略。

7.2 微服务系统中的熔断机制

Spring Cloud Hystrix基于Netflix的开源框架Hystrix实现,提供了服务熔断、线程隔离等一系列的服务保护功能。

针对熔断机制,Hystrix设计了三种状态:

  • 关闭状态(Closed):服务正常工作,熔断器不对调用方做任何限制。
  • 开启状态(Open):固定时间内接口出错达到既定阈值,熔断开启,后续对该服务的调用会直接执行本地fallback方法。
  • 半熔断状态(Half-Open):熔断开启状态后,经过一个固定的时间,熔断器进入半熔断状态。半熔断状态时熔断器允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预定条件,则熔断器关闭,否则重新进入开启状态。

7.3 Spring Cloud Netflix Hystrix

Spring Cloud框架中对Netflix开源的Hystrix进行了封装,

我们为order服务的/users/{userId}接口添加Spring Cloud Netflix
Hystrix支持,代码如下。

在pom.xml中添加:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

修改接口代码:

@HystrixCommand(fallbackMethod = "getUserInfoFailure")
@GetMapping("/users/{userId}")
public String getUserInfo(@PathVariable Long userId){
    System.out.println("这里要使用Ribbon来远程调用customer服务获取用户信息");
    String userInfoStr = restTemplate.getForObject("http://customer/users/" +
    userId, String.class);
    return userInfoStr;
}

/**
* @param userId
* @return
*/
public String getUserInfoFailure(Long userId){
    System.out.println("熔断咯");
    return "我是用来充数的";
}

上面代码中,@HystrixCommand(fallbackMethod = “getUserInfoFailure”)中的getUserInfoFailure是熔断使要执行的方法,这个方法的入参和返回都需要和使用@HystrixCommand注解标记的接口一致。fallbackMethod就是服务降级后执行的方法。

同时,在启动类添加@EnableCircuitBreaker开启Hystrix支持。

上面的代码为Ribbon调用添加了熔断支持,我们可以通诺修改customer的端口号,启动两个customer实例,然后停掉其中一个实例,模拟服务中断的情况来观察Hystrix的工作情况。

正常情况下,通过网关调用order服务的接口,返回是图7.2所示的情况,当我们停掉其中的一个customer时,出现了图7.3的情况。

b39dc55cf8b52ed7ffa6a99b23f496d0.png

图7.2 正常访问

989dbbf774c2acdc04abd93b1bf09e58.png

图7.3 Hystrix fallback起作用

可以看到当前是交替出现图7.2和图7.3的情况,快速刷新页面,就会发现接口会一直返回图7.3的信息,这时候是因为熔断器打开了,Hystrix默认5s内失败20次即会打开熔断器。

对Feign的Hystrix支持,其本质和Ribbon的支持类似,本书不进行讲解,有兴趣可以自行查看官方文档。

7.4 小结

熔断器是微服务系统中不可或缺的一个组件,离开熔断器的系统都有可能出现“雪崩”事件。本文我们介绍了基本的熔断概念,并且使用Spring Cloud Netflix Hystrix为前面的Ribbon请求示例做了熔断支持。

发表评论

电子邮件地址不会被公开。 必填项已用*标注