乐趣区

SpringCloud-第四篇-Hystrix快速上手一

1:Hystrix 简介

1.1:分布式系统面临的问题

复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。
这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

1.2:Hystrix 是什么

Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix 能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

1.3:Hystrix 能干什么

Hystrix 能做很多事情,主要有:

  • 服务隔离、降级、熔断、限流、快速失败
  • 请求合并、请求缓存
  • 接近实时的监控

1.4:Hystrix 的设计原则

  • 防止任何单独的依赖使用所有的容器(如 Tomcat)用户线程
  • 切断负载并快速失败,而不是排队
  • 尽可能提供回退以保护用户免受故障
  • 使用隔离技术(例如舱壁,泳道和断路器模式)来限制任何一个依赖的影响
  • 通过接近实时的指标,监控和警报,优化发现时间
  • 通过配置更改的低延迟传播,优化恢复时间
  • 防止各种客户端执行失败,而不仅仅是网络通信

2: HelloWord

加入依赖

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

官方示例 https://github.com/Netflix/Hy…

3:基本使用

3.1:HystrixCommand 和 HystrixObservableCommand

两者主要区别是:

1:前者的命令逻辑写在 run();后者的命令逻辑写在 construct()
2:前者的 run()是由新创建的线程执行;后者的 construct()是由调用程序线程执行
3:前者一个实例只能向调用程序发送单条数据,比如上面例子中 run() 只能返回一个 String 结果;后者一个实例可以顺序发送多条数据,可以顺序调用多个 onNext(),便实现了向调用程序发送多条数据

3.2:命令执行方法

execute()、queue()、observe()、toObservable()这 4 个方法用来触发执行 run()/construct(),一个实例只能执行一次这 4 个方法,注意 HystrixObservableCommand 没有 execute()和 queue()。

  • 1:execute():以同步堵塞方式执行 run()。调用 execute()后,Hystrix 先创建一个新线程运行 run(),接着调用程序要在 execute()调用处一直堵塞着,直到 run()运行完成
  • 2:queue():以异步非堵塞方式执行 run()。一调用 queue()就直接返回一个 Future 对象,同时 hystrix 创建一个新线程运行 run(),调用程序通过 Future.get()拿到 run()的返回结果,而 Future.get()是堵塞执行的
  • 3:observe():事件注册前执行 run()/construct()。

第一步是事件注册前,先调用 observe()自动触发执行 run()/construct()(如果继承的是 HystrixCommand,hystrix 将创建新线程非堵塞执行 run();如果继承的是 HystrixObservableCommand,将以调用程序线程堵塞执行 construct())
第二步是从 observe()返回后调用程序调用 subscribe()完成事件注册,如果 run()/construct()执行成功则触发 onNext()和 onCompleted(),如果执行异常则触发 onError()

  • 4:toObservable():事件注册后执行 run()/construct()。

第一步是事件注册前,一调用 toObservable()方法就直接返回一个 Observable<String> 对象
第二步调用 subscribe()完成事件注册后自动触发执行 run()/construct()(如果继承的是 HystrixCommand,hystrix 将创建新线程非堵塞执行 run(),调用程序不必等待 run();如果继承的是 HystrixObservableCommand,将以调用程序线程堵塞执行 construct(),调用程序等待 construct()执行完才能继续往下走),如果 run()/construct()执行成功则触发 onNext()和 onCompleted(),如果执行异常则触发 onError()

3.3:命令名称

默认情况下,命令名是从类名派生的:getClass().getSimpleName();
要明确地定义名字,通过 HystrixCommand 或 HystrixObservableCommand 构造函数传入,例如:

super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloWorld1"))     
  .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")) 
  .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")));
  • 1:GroupKey 是 HystrixCommand 不可缺少的配置,其它配置均为可选,例如:

super(HystrixCommandGroupKey.Factory.asKey(“HelloWorld1”));
HystrixCommandGroupKey 的作用主要有两个:
一是起到分组监控、报警的作用;
二是在不配置 HystrixThreadPoolKey 的情况下,起到分组线程池的作用。即默认使用 HystrixCommandGroupKey 去命名线程池。使用同一个 HystrixCommandGroupKey 且没有自定义 HystrixThreadPoolKey 的 HystrixCommand 将使用同一个线程池。

  • 2:commandKey:命令的标识名称
  • 3:ThreadPoolKey:线程池的标识名称

3.4:Command Thread-Pool 设置

虽然 HystrixCommandGroupKey 可以起到隔离线程池的作用,但是无法起到对线程池进行精细配置的作用。所以这里就需要线程池进行配置,例如:

super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloWorld1")) 
        .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("MyThreadPool"))
        .andThreadPoolPropertiesDefaults(.HystrixThreadPoolProperties.Setter()
        .withCoreSize(20)
        .withKeepAliveTimeMinutes(1)
        .withMaxQueueSize(-1)
     )
     .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(100)
)
  • 1:andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(“MyThreadPool”)) 这是配置 ThreadPoolKey。如果需要在同一个 GroupKey 下面配置不同的 ThreadPool 就需要这个配置。
  • 2:andThreadPoolPropertiesDefaults 表示设置线程池默认的属性值,包括:

(1)withCoreSize(20)

 用来配置线程池大小,不配置的话使用的默认值是 10。

(2)withKeepAliveTimeMinutes(1)

用来配置核心线程数空闲时 keep alive 的时长,默认为 1 mins,一般不需要修改。

(3)withMaxQueueSize(-1)
配置线程池任务队列的大小,默认值为 -1。当使用 -1 时,SynchronousQueue 将被使用,即意味着其实这个队列只是一个交换器,任务将被直接交给工作线程处理。如果工作线程不足,那任务将被拒绝;如果使用任何正整数,LinkedBlockingQueue 将被使用

3.5:使用

编写完 自己的 Command 之后,使用的时候每次都需要 new 一个新对象,再调用 execute() 方法。注意,不要调用 run() 方法,否则熔断、隔离等功能是不生效的

3.6:错误传播

run()里面抛出的 HystrixBadRequestException 只用做计数,方法抛出的所有其它异常都作为失败,触发 getFallback()和断路器逻辑。
你可以包装你想要抛出的异常,HystrixBadRequestException 适用的情况,如举报非法参数或非系统故障,不会计入失败的指标,不会触发回退逻辑。

3.7:快速失败

快速失败就是指没有重写 getFallback,遇到异常后直接抛出,程序停止运行

3.8:回退/降级

所谓降级,就是指在在 Hystrix 执行非核心链路功能失败的情况下,我们如何处理,比如我们返回默认值等。触发时会调用 fallback 设置的降级方法,在降级方法中,可以设置默认的降级返回数据。
使用 fallback 机制很简单,继承 HystrixCommand 只需重写 getFallback(),继承 HystrixObservableCommand 只需重写 resumeWithFallback()。
如下情况将会启动回退

  • 非 HystrixBadRequestException 异常
  • run()/construct()运行超时
  • 熔断器启动
  • 线程池 / 信号量已满

3.9:请求缓存

Hystrix 支持将一个请求结果缓存起来,下一个具有相同 key 的请求将直接从缓存中取出结果,减少请求开销。使用方式如下:
1:通过 getCacheKey()在一个 HystrixCommand 或一个 HystrixObservableCommand 对象上实现该方法来启用请求缓存
2:另外要求这多个请求必须在同一个上下文。通过 HystrixRequestContext.initializeContext() 和 context.shutdown()可以构建一个 context,这两条语句间的所有请求都处于同一个 context
3:通过 isResponseFromCache()可检查返回结果是否来自缓存

3.10:合并请求

Hystrix 支持 N 个请求自动合并为一个请求,这将使多次网络交互变成一次,极大节省开销。注意一点,两个请求能自动合并的前提是两者足够“近”,即两者启动执行的间隔时长要足够小,默认为 10ms,即超过 10ms 将不自动合并。
Hystrix 支持 2 种请求合并方式:请求范围和全局范围。这是在 collapser 构造中配置的,默认为 request-scoped。请求范围是指在一个 HystrixRequestContexts 上下文中的请求;全局范围指垮 HystrixRequestContexts 的请求。

退出移动版