熔断机制

概念

  • 服务熔断

当下游的服务因为某种原因突然变得不可用响应过慢,上游服务为了保证自己整体服务的可用性,不再继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。

一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护。
适用场景:防止应用程序直接调用那些很可能会调用失败的远程服务或共享资源

  • 服务降级

当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。

应该这么理解:服务降级有很多种降级方式,如开关降级、限流降级、熔断降级。服务熔断属于降级方式的一种!

  • 雪崩效应

一种因 服务提供者 的不可用导致 服务调用者 的不可用,并将不可用 逐渐放大 的过程

分布式系统中经常会出现某个基础服务不可用造成整个系统不可用的情况, 这种现象被称为服务雪崩效应.。如:在分布式系统架构中多个系统之间通常是通过远程RPC调用进行通信,也就是 A 系统调用 B 系统服务,B 系统调用 C 系统的服务。当尾部应用 C 发生故障而系统 B 没有服务降级时候可能会导致 B,甚至系统 A 瘫痪。

为了应对服务雪崩, 一种常见的做法是手动服务降级. 而Hystrix的出现,给我们提供了另一种选择.

 

雪崩效应形成原因:

  • 服务提供者不可用 

a)硬件故障:硬件损坏造成的服务器主机宕机, 网络硬件故障造成的服务提供者的不可访问

b)程序Bug

c)缓存击穿:一般发生在缓存应用重启,所有缓存被清空时,以及短时间内大量缓存失效时,大量的缓存不命中,使请求直击后端,造成服务提供者超负荷运行,引起服务不可用

d)用户大量请求:在秒杀和大促开始前,如果准备不充分,用户发起大量请求也会造成服务提供者的不可用

  • 重试加大流量

a)用户重试:在服务提供者不可用后, 用户由于忍受不了界面上长时间的等待,而不断刷新页面甚至提交表单

b)代码逻辑重试: 服务调用端的会存在大量服务异常后的重试逻辑

  • 服务调用者不可用

a)同步等待造成的资源耗尽:当服务调用者使用 同步调用  时, 会产生大量的等待线程占用系统资源. 一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态, 于是服务雪崩效应产生了。

服务雪崩的应对策略:

  • 流量控制

a)网关限流:因为Nginx的高性能, 目前一线互联网公司大量采用Nginx+Lua的网关进行流量控制, 由此而来的OpenResty也越来越热门

b)用户交互限流:具体措施有: 1. 采用加载动画,提高用户的忍耐等待时间. 2. 提交按钮添加强制等待时间机制.

c)关闭重试

  • 改进缓存模式

a)缓存预加载

b)同步改为异步刷新

  • 服务自动扩容

a)AWS的auto scaling

  • 服务调用者降级服务

a)资源隔离:主要是对调用服务的线程池进行隔离

b)对依赖服务进行分类:根据具体业务,将依赖服务分为: 强依赖和若依赖。强依赖服务不可用会导致当前业务中止,而弱依赖服务不可用不会导致当前业务中止

c)不可用服务的调用快速失败:一般通过 超时机制熔断器 和熔断后的 降级方法 来实现

使用Hystrix预防服务雪崩

Netflix的 Hystrix 是一个帮助解决分布式系统交互时超时处理和容错的类库,它拥有保护系统的能力.

Hystrix的设计原则包括:

  • 资源隔离
  • 熔断器(Circuit Breaker)
  • 命令模式

资源隔离

  • 线程隔离

货船为了防止漏水和火灾的扩散,将货仓分隔为多个,这种资源隔离减少风险的方式被称为Bulkheads(舱壁隔离模式)。Hystrix将同样的模式运用到了服务调用者上。

在一个高度服务化的系统中,我们实现的一个业务逻辑通常会依赖多个服务,比如: 商品详情展示服务会依赖商品服务、 价格服务、商品评论服务。调用三个依赖服务会共享商品详情服务的线程池, 如果其中的商品评论服务不可用, 就会出现线程池里所有线程都因等待响应而被阻塞,从而造成服务雪崩。 如图所示:

 

Hystrix通过将每个依赖服务分配独立的线程池进行资源隔离,用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,则会进行降级处理,用户的请求不会被阻塞,至少可以看到一个执行结果(例如返回友好的提示信息),而不是无休止的等待或者看到系统崩溃。如下图所示, 当商品评论服务不可用时, 即使商品服务独立分配的20个线程全部处于同步等待状态,也不会影响其他依赖服务的调用.

  • 信号隔离

信号隔离也可以用于限制并发访问,防止阻塞扩散,与线程隔离最大不同在于执行依赖代码的线程依然是请求线程(该线程需要通过信号申请,如果客户端是可信的且可以快速返回,可以使用信号隔离替换线程隔离,降低开销。信号量的大小可以动态调整,线程池大小不可以。

熔断器模式

熔断器是位于线程池之前的组件。用户请求某一服务之后,Hystrix会先经过熔断器,此时如果熔断器的状态是打开,这时将直接进行降级处理,不会继续将请求发到线程池。熔断器相当于在线程池之前的一层屏障。每个熔断器默认维护10个bucket ,每秒创建一个bucket ,每个blucket记录成功,失败,超时,拒绝的次数。当有新的bucket被创建时,最旧的bucket会被抛弃。

熔断器模式定义了熔断器开关相互转换的逻辑:

三种状态

  • open:打开熔断,也就是服务调用方执行本地降级策略,不进行远程调用。
  • closed:关闭了熔断,这时候服务调用方直接发起远程调用。
  • half-open:一个中间状态,允许定量的服务请求直接发起远程调用。

 

服务的健康状况 = 请求失败数 / 请求总数. 
熔断器开关由关闭到打开的状态转换是通过当前服务健康状况和设定阈值比较决定的。

熔断器的开关能保证服务调用者在调用异常服务时,快速返回结果,避免大量的同步等待,并且熔断器能在一段时间后继续侦测请求执行结果,提供恢复服务调用的可能。

Hystrix提供的熔断器具有自我反馈,自我恢复的功能,Hystrix会根据调用接口的情况,让熔断器在closed、open、half-open三种状态之间自动切换:

  • closed->open:正常情况下熔断器为closed状态,如果当前健康状况高于设定阈值,保持closed。如果当前健康状况低于设定阈值,则切换为open。
  • open->half-open:当服务接口对应的熔断器为open状态,所有服务调用方调用该服务方法时全部执行本地降级方法。Hystrix提供了一种测试策略,即设置了一个时间窗口(一般设置成平均故障处理时间,也就是MTTR),从熔断器变为open状态开始的一个时间窗口内,调用该服务接口时都委托服务降级方法进行执行。如果时间超过了时间窗口,则把熔断状态从open->half-open
  • half-open->closed:当熔断器为half-open状态,允许定量的服务请求。 若全部(或一定比例)的请求调用成功, 熔断器恢复到closed,否则,熔断器状态为open, 接下来的请求被禁止通过,重新记录时间窗口开始时间。

命令模式

Hystrix使用命令模式(继承HystrixCommand类)来包裹具体的服务调用逻辑(run方法),并在命令模式中添加了服务调用失败后的降级逻辑(getFallback).
同时我们在Command的构造方法中可以定义当前服务线程池和熔断器的相关参数, 如下代码所示:

public class Service1HystrixCommand extends HystrixCommand<Response> {
  private Service1 service;
  private Request request;

  public Service1HystrixCommand(Service1 service, Request request){
    supper(
      Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ServiceGroup"))
            .andCommandKey(HystrixCommandKey.Factory.asKey("servcie1query"))
            .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("service1ThreadPool"))
            .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
            .withCoreSize(20))//服务线程池数量
            .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
            .withCircuitBreakerErrorThresholdPercentage(60)//熔断器关闭到打开阈值
            .withCircuitBreakerSleepWindowInMilliseconds(3000)//熔断器打开到关闭的时间窗长度
      ))
      this.service = service;
      this.request = request;
    );
  }

  @Override
  protected Response run(){
    return service1.call(request);
  }

  @Override
  protected Response getFallback(){
    return Response.dummy();
  }
}

 在使用了Command模式构建了服务对象之后, 服务便拥有了熔断器和线程池的功能

 

Hystrix的内部处理逻辑

  1. 构建Hystrix的Command对象,调用执行方法.

  2. Hystrix检查当前服务的熔断器开关是否开启,若开启,则执行降级服务getFallback方法.

  3. 若熔断器开关关闭,则Hystrix检查当前服务的线程池是否能接收新的请求,若超过线程池已满,则执行降级服务getFallback方法.

  4. 若线程池接受请求,则Hystrix开始执行服务调用具体逻辑run方法.

  5. 若服务执行失败,则执行降级服务getFallback方法,并将执行结果上报Metrics更新服务健康状况.(run()方法抛出非HystrixBadRequestException异常)

  6. 若服务执行超时,则执行降级服务getFallback方法,并将执行结果上报Metrics更新服务健康状况.

  7. 若服务执行成功,返回正常结果.

  8. 若服务降级方法getFallback执行成功,则返回降级结果.

  9. 若服务降级方法getFallback执行失败,则抛出异常.

执行方式

  • 同步执行:即一旦开始执行该命令,当前线程就得阻塞着直到该命令返回结果,然后才能继续执行下面的逻辑
  • 异步执行:命令开始执行会返回一个Future<T>的对象,不阻塞后面的逻辑,开发者自己根据需要去获取结果。
  • 响应式执行:命令开始执行会返回一个Observable<T> 对象,开发者可以给给Obeservable对象注册上Observer或者Action1对象,响应式地处理命令执行过程中的不同阶段。当调用HystrixCommand的observe()方法,或使用Observable的工厂方法(just(),from())即为响应式执行,这个功能的实现是基于Netflix的另一个开源项目RxJava(https://github.com/Netflix/RxJava)来的,更细节的用法可以参考:https://github.com/Netflix/Hystrix/wiki/How-To-Use#wiki-Reactive-Execution

 

 整理自:https://segmentfault.com/a/1190000005988895、https://blog.csdn.net/smillest/article/details/80660565

posted @ 2019-06-10 18:31  WhatAreWords  阅读(3229)  评论(0编辑  收藏  举报