spring cloud hystrix详解

462 阅读9分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

hystrix是用来做熔断降级的,直白说就是你出了问题(超时、失败、宕机等)不能影 防止服务雪崩的解决方案,一般有降级、熔断、缓存、合并和隔离,这里主要介绍降级和熔断。熔断可以直接返回兜底数据、为服务资源分配固定的资源或者直接抛出一个异常(如系统繁忙等)。

熔断也是降级的一种方式。降级分主动和被动:

  • 主动降级:直接关闭非关键应用;
  • 被动降级:熔断降级、限流降级

用途

  • 对通过第三方客户端库访问的依赖项(通常通过网络)造成的延迟和故障提供保护和控制
  • 在复杂的分布式系统中停止级联故障
  • 快速失败,快速恢复
  • 在可能的情况下后退并优雅地降级
  • 启用近乎实时的监视、警报和操作控制

原则

  • 防止任何单一依赖项耗尽所有容器(如Tomcat)用户线程。
  • 快速卸载和故障,而不是排队。
  • 在可行的情况下提供后备方案以保护用户不发生故障。
  • 使用隔离技术(例如隔板、泳道和断路器模式)来限制任何一个依赖项的影响。
  • 通过低延迟传播配置更改和支持Hystrix大多数方面的动态属性更改来优化恢复时间,这允许您使用低延迟反馈循环进行实时操作修改。
  • 在整个依赖客户机执行中防止失败,而不仅仅是在网络通信中。

原理

  • 将所有对外部系统(或“依赖项”)的调用包装在HystrixCommand或HystrixObservableCommand对象中,该对象通常在单独的线程中执行(这是命令模式的一个示例)
  • 超时调用的时间超过您定义的阈值。有一个默认值,但对于大多数依赖项,您可以通过“属性”的方式自定义设置这些超时,以便它们略高于每个依赖项的99.5%的性能测量值
  • 为每个依赖项维护一个小的线程池(或信号量);如果它已满,发送到该依赖项的请求将立即被拒绝,而不是排队
  • 测量成功、失败(客户端抛出的异常)、超时和线程拒绝。
  • 触发断路器,在一段时间内停止对特定服务的所有请求,如果服务的错误百分比超过阈值,可以手动或自动
  • 在请求失败、被拒绝、超时或短路时执行回退逻辑
  • 近乎实时地监视度量和配置更改

image.png

当您使用Hystrix包装每个底层依赖项时,上面图表中所示的体系结构将更改为类似于下面的图表。

每个依赖都是相互隔离的,在延迟发生时它可以饱和的资源中受到限制,并在回退逻辑中覆盖,以决定在依赖中发生任何类型的故障时做出什么响应

image.png

向服务发出请求时,发生了什么?

image.png

详细过程:

  1. 构建 HystrixCommandHystrixObservableCommand,可以根据需要添加多个参数到构造函数:
HystrixCommand command = new HystrixCommand(arg1, arg2);
// or
HystrixObservableCommand command = new HystrixObservableCommand(arg1, arg2);
  1. 执行命令 有四种方式执行:
  • execute():阻塞,然后返回从依赖项接收到的单个响应(或在发生错误时抛出异常)
  • queue():返回一个Future,可以从依赖项中获取单个响应
  • observe():订阅来自依赖的响应的Observable,并返回一个复制源Observable的Observable
  • toObservable():返回一个Observable,当你订阅它时,它会执行Hystrix命令并发出它的响应
K             value   = command.execute();
Future<K>     fValue  = command.queue();
Observable<K> ohValue = command.observe();         //hot observable
Observable<K> ocValue = command.toObservable();    //cold observable
  1. 是否使用缓存 如果该命令启用了请求缓存,并且请求的响应在缓存中可用,那么这个缓存的响应将立即以Observable的形式返回。(请参阅下面的“请求缓存”。)
  2. 断路器是否打开 当你执行这个命令时,Hystrix会检查断路器,看电路是否打开。

如果电路是打开的(或“tripped”),那么Hystrix将不会执行命令,而是将流路由到(8)Get the Fallback。

如果电路被关闭,那么流继续到(5)检查是否有可用的容量来运行命令。

  1. 线程池/队列/信号量是否已满

如果与该命令相关的线程池和队列(或信号量,如果不是在线程中运行)已满,则Hystrix将不会执行该命令,而是立即将流路由到(8)Get the Fallback。

  1. hystrixobservableccommand .construct()HystrixCommand.run()
HystrixCommand.run() // returns a single response or throws an exception
HystrixObservableCommand.construct() // returns an Observable that emits the response(s) or sends an onError notification

如果run()或construct()方法超过了命令的超时值,线程将抛出TimeoutException(如果命令本身没有在自己的线程中运行,则另一个计时器线程将抛出TimeoutException)。在这种情况下,Hystrix将响应路由到8。获取Fallback,如果run()或construct()方法没有取消/中断,它将丢弃最终返回值。

请注意,没有办法强制潜在线程停止工作——Hystrix在JVM上做的最好的事情是抛出InterruptedException。 如果Hystrix包装的工作不处理InterruptedExceptions, Hystrix线程池中的线程将继续它的工作,尽管客户端已经收到了一个TimeoutException,这种行为会使Hystrix线程池饱和,尽管负载是“正确地释放”的。
大多数Java HTTP客户端库不解释InterruptedExceptions。因此,请确保在HTTP客户端上正确配置连接和读/写超时

如果该命令没有抛出任何异常并且返回响应,则Hystrix在执行一些日志记录和指标报告后返回此响应。

  1. 计算断路器状态

Hystrix向断路器报告成功、失败、拒绝和超时,断路器维护一组计算统计数据的滚动计数器。

它使用这些统计信息来确定电路何时应该“跳闸”,这时它将使任何后续请求短路,直到恢复期结束,在恢复期结束后,它将在首先检查某些健康检查后再次关闭电路。

  1. 获取托底返回(fallback)

当发生以下情况时,会执行托底返回:

  • construct()和run() 执行抛出异常
  • 断路器打开的情况
  • 当命令的线程池和队列或信号量达到容量时
  • 当命令已超过其超时长度时

降级策略

  1. 接口响应超时触发降级
  2. 服务熔断触发降级
  3. 资源隔离触发降级

响应超时降级处理

基于Openfeign的客户端方式

添加依赖

<!--openfeign中已经依赖了hystrix-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

配置

feign:
	hystrix:
		enabled: true

配置包扫描

在启动类上添加@EnableFeignClients启动Feign扫描 编写接口代理类:

@FeignClient(value = "demo-user",fallback = UserServiceApiFallback.class)
public interface UserServiceApiProxy {
    @GetMapping("user/info")
    String info(String uid);

    @GetMapping("user/list")
    List<String> list();
}

编写fallback类:

@Component
public class UserServiceApiFallback implements UserServiceApiProxy {
    @Override
    public String info(String uid) {
        return "超时了老弟";
    }

    @Override
    public List<String> list() {
        return Arrays.asList("超时了老兄!");
    }
}

基于Hystrix客户端方式

添加依赖

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

配置包扫描

在启动类上添加@EnableFeignClients启动Feign 在启动类上添加@EnableHystrix,启动Hystrix 编写接口代理类:

//这里不需要fallback属性,下面在调用该接口的地方采用HystrixCommand方式
@FeignClient(value = "demo-user")
public interface UserServiceApiProxy {
    @GetMapping("user/info")
    String info(String uid);

    @GetMapping("user/list")
    List<String> list();
}

编写业务逻辑类:


@RestController
@RequestMapping("order")
public class OrderServiceApi {

	//这是要通过openfeign调用的其他服务代理接口(就是上面那个接口)
    @Autowired
    UserServiceApiProxy userServiceApiProxy;

    @GetMapping("info")
    public String info(String oid){
        return  "order info by oid";
    }

    @GetMapping("list")
    @HystrixCommand(fallbackMethod = "err",commandProperties = {
              @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")  // 超时时间
            }) //当list中调用服务超时时,会立即执行err函数(代码中的下一个函数)
    public List list(){

        List list= this.userServiceApiProxy.list();
        return Arrays.asList("手机","电脑","书籍","汽车",list);
    }

	//当降级后被调用的err函数
    public List err(){
        return Arrays.asList("超时了大哥");
    }
}

上述两种写法其实效果一样,第二种把降级代码和业务代码混在一起是不好的实践,但是第二种可以更精细化配置,在HystrixCommand中可以使用HystrixProperties配置什么情况下允许执行降级。

服务熔断降级处理

 //这个服务熔断的意思是10s内请求10次失败率达到60%就熔断
 @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
            // 是否开启断路器
            @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
            // 请求次数
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
            // 时间窗口期,熔断后10s内,正常的调用也只会返回拖地数据“系统繁忙”,10s后才能正常访问
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), 
            // 失败率达到多少后跳闸,这是50%
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50")
    })
@GetMapping("list")     
public String list(@RequestParam("name") String name){return "bk";}

// 参数必须和上面list函数一致
public String paymentCircuitBreaker_fallback(String name){
	return "系统繁忙"; //返回兜底数据
}     

// 在启动类,添加 @EnableCircuitBreaker 启用

在同一个滑动窗口10秒内,如果有20次请求,且请求失败率在50%以上,会打开断路器,熔断,后续请求不再处理。
一段时间以后,保护器会尝试进入半熔断状态(Half-Open),允许少量请求进来尝试,如果调用仍然失败,则回到熔断状态,如果调用成功,则回到断路器闭合状态;

资源隔离降级处理

为了防止一个服务占用资源导致整个平台宕机,会采用隔离手段:

  • 平台隔离
  • 部署隔离
  • 业务隔离
  • 服务隔离
  • 资源隔离
feign:
  hystrix:
    enabled: true

hystrix:
  command:
    default : #全局配置 feignClient#method(params)
      execution:
        timeout:
          enable: true
        isolation: # execution.isolation.thread.timeoutInMilliseconds配置超时时间,设为3s
          thread:
            timeoutInMilliseconds: 3000
#   除了default默认设置,也可以为每个方法进行个性化设置
    OrderServiceFeignClient#getAllOrders(): #针对具体某个方法进行配置
      execution:
        isolation:
          strategy: SEMAPHORE   # execution.isolation.strategy 隔离策略 信号量; com.netflix.hystrix.HystrixCommandProperties.default_executionIsolationStrategy 枚举值
          semaphore:
            maxConcurrentRequests: 10 # execution.isolation.semaphore.maxConcurrentRequests  最大并发请求数

    OrderServiceFeignClient#insertOrder() :
      execution:
        isolation:
          strategy: THREAD   # execution.isolation.strategy 隔离策略 线程池
  threadpool:
    shen-order-service:
      coreSize : 2
      maxQueueSize : 1000 # 队列长度
      queueSizeRejectionThreshold : 800 # 超过800开始拒绝

ribbon: # 因为openFeign内部是使用ribbon进行负载均衡调用,需要设置ribbon的超时时间
  ReadTimeout: 8000
  ConnectTimeout : 8000