提交 c5326390 编写于 作者: Z zhoushitong 提交者: Pliza

完善 Hystrix

上级 eddf65c7
# 高可用架构
- [Hystrix 介绍](/docs/high-availability/hystrix-introduction.md)
- [电商网站详情页系统架构](/docs/high-availability/e-commerce-website-detail-page-architecture.md)
- [Hystrix 线程池技术实现资源隔离](/docs/high-availability/hystrix-thread-pool-isolation.md)
- [Hystrix 信号量机制实现资源隔离](/docs/high-availability/hystrix-semphore-isolation.md)
- [Hystrix 隔离策略细粒度控制](/docs/high-availability/hystrix-execution-isolation.md)
- [深入 Hystrix 执行时内部原理](/docs/high-availability/hystrix-process.md)
- [基于 request cache 请求缓存技术优化批量商品数据查询接口](/docs/high-availability/hystrix-request-cache.md)
- [基于本地缓存的 fallback 降级机制](/docs/high-availability/hystrix-fallback.md)
- [深入 Hystrix 断路器执行原理](/docs/high-availability/hystrix-circuit-breaker.md)
- [深入 Hystrix 线程池隔离与接口限流](/docs/high-availability/hystrix-thread-pool-current-limiting.md)
- [基于 timeout 机制为服务接口调用超时提供安全保护](/docs/high-availability/hystrix-timeout.md)
- [Hystrix 介绍](./hystrix-introduction.md)
- [电商网站详情页系统架构](./e-commerce-website-detail-page-architecture.md)
- [Hystrix 线程池技术实现资源隔离](./hystrix-thread-pool-isolation.md)
- [Hystrix 信号量机制实现资源隔离](./hystrix-semphore-isolation.md)
- [Hystrix 隔离策略细粒度控制](./hystrix-execution-isolation.md)
- [深入 Hystrix 执行时内部原理](./hystrix-process.md)
- [基于 request cache 请求缓存技术优化批量商品数据查询接口](./hystrix-request-cache.md)
- [基于本地缓存的 fallback 降级机制](./hystrix-fallback.md)
- [深入 Hystrix 断路器执行原理](./hystrix-circuit-breaker.md)
- [深入 Hystrix 线程池隔离与接口限流](./hystrix-thread-pool-current-limiting.md)
- [基于 timeout 机制为服务接口调用超时提供安全保护](./hystrix-timeout.md)
## 高可用系统
- 如何设计一个高可用系统?
......
## 深入 Hystrix 断路器执行原理
### RequestVolumeThreshold
### 状态机
Hystrix 断路器有三种状态,分别是关闭(Closed)、打开(Open)与半开(Half-Open),三种状态转化关系如下:
![image-20191104211642271](./images/Hystrix断路器状态机.png)
1. `Closed` 断路器关闭:调用下游的请求正常通过
2. `Open` 断路器打开:阻断对下游服务的调用,直接走 Fallback 逻辑
3. `Half-Open` 断路器处于半开状态:[SleepWindowInMilliseconds](#circuitBreaker.sleepWindowInMilliseconds)
### [Enabled](https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakerenabled)
```java
HystrixCommandProperties.Setter()
.withCircuitBreakerRequestVolumeThreshold(int)
.withCircuitBreakerEnabled(boolean)
```
表示在滑动窗口中,至少有多少个请求,才可能触发断路
控制断路器是否工作,包括跟踪依赖服务调用的健康状况,以及对异常情况过多时是否允许触发断路。默认值 `true`
Hystrix 经过断路器的流量超过了一定的阈值,才有可能触发断路。比如说,要求在 10s 内经过断路器的流量必须达到 20 个,而实际经过断路器的流量才 10 个,那么根本不会去判断要不要断路。
### ErrorThresholdPercentage
### [circuitBreaker.requestVolumeThreshold](https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakerrequestvolumethreshold)
```java
HystrixCommandProperties.Setter()
.withCircuitBreakerErrorThresholdPercentage(int)
.withCircuitBreakerRequestVolumeThreshold(int)
```
表示异常比例达到多少,才会触发断路,默认值是 50(%)。
如果断路器统计到的异常调用的占比超过了一定的阈值,比如说在 10s 内,经过断路器的流量达到了 30 个,同时其中异常访问的数量也达到了一定的比例,比如 60% 的请求都是异常(报错 / 超时 / reject),就会开启断路。
表示在一次统计的**时间滑动窗口中(这个参数也很重要,下面有说到)**,至少经过多少个请求,才可能触发断路,默认值 20。**经过 Hystrix 断路器的流量只有在超过了一定阈值后,才有可能触发断路。**比如说,要求在 10s 内经过断路器的流量必须达到 20 个,而实际经过断路器的请求有 19 个,即使这 19 个请求全都失败,也不会去判断要不要断路。
### SleepWindowInMilliseconds
### [circuitBreaker.errorThresholdPercentage](https://github.com/Netflix/Hystrix/wiki/Configuration#circuitBreaker.errorThresholdPercentage)
```java
HystrixCommandProperties.Setter()
.withCircuitBreakerSleepWindowInMilliseconds(int)
.withCircuitBreakerErrorThresholdPercentage(int)
```
断路开启,也就是由 close 转换到 open 状态(close -> open)。那么之后在 `SleepWindowInMilliseconds` 时间内,所有经过该断路器的请求全部都会被断路,不调用后端服务,直接走 fallback 降级机制。
而在该参数时间过后,断路器会变为 `half-open` 半开闭状态,尝试让一条请求经过断路器,看能不能正常调用。如果调用成功了,那么就自动恢复,断路器转为 close 状态。
表示异常比例达到多少,才会触发断路,默认值是 50(%)。
### Enabled
#### [circuitBreaker.sleepWindowInMilliseconds](https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakersleepwindowinmilliseconds)
```java
HystrixCommandProperties.Setter()
.withCircuitBreakerEnabled(boolean)
.withCircuitBreakerSleepWindowInMilliseconds(int)
```
控制是否允许断路器工作,包括跟踪依赖服务调用的健康状况,以及对异常情况过多时是否允许触发断路。默认值是 `true`
断路器状态由 Close 转换到 Open,在之后 `SleepWindowInMilliseconds` 时间内,所有经过该断路器的请求会被断路,不调用后端服务,直接走 Fallback 降级机制,默认值 5000(ms)。
而在该参数时间过后,断路器会变为 `Half-Open` 半开闭状态,尝试让一条请求经过断路器,看能不能正常调用。如果调用成功了,那么就自动恢复,断路器转为 Close 状态。
### ForceOpen
### [ForceOpen](https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakerforceopen)
```java
HystrixCommandProperties.Setter()
......@@ -51,7 +57,7 @@ HystrixCommandProperties.Setter()
如果设置为 true 的话,直接强迫打开断路器,相当于是手动断路了,手动降级,默认值是 `false`
### ForceClosed
### [ForceClosed](https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakerforceclosed)
```java
HystrixCommandProperties.Setter()
......@@ -60,6 +66,14 @@ HystrixCommandProperties.Setter()
如果设置为 true,直接强迫关闭断路器,相当于手动停止断路了,手动升级,默认值是 `false`
### Metrics 统计器
与 Hystrix 断路器紧密协作的,还有另一个重要组件 —— **统计器(Metrics)**。统计器中最重要的参数要数滑动窗口([metrics.rollingStats.timeInMilliseconds](https://github.com/Netflix/Hystrix/wiki/Configuration#metricsrollingstatstimeinmilliseconds))以及桶([metrics.rollingStats.numBuckets](https://github.com/Netflix/Hystrix/wiki/Configuration#metricsrollingstatsnumbuckets))了,这里引用[一段博文](https://zhenbianshu.github.io/2018/09/hystrix_configuration_analysis.html)来解释滑动窗口(默认值是 10000 ms):
> 一位乘客坐在正在行驶的列车的靠窗座位上,列车行驶的公路两侧种着一排挺拔的白杨树,随着列车的前进,路边的白杨树迅速从窗口滑过。我们用每棵树来代表一个请求,用列车的行驶代表时间的流逝,那么,列车上的这个窗口就是一个典型的滑动窗口,这个乘客能通过窗口看到的白杨树就是 Hystrix 要统计的数据。
Hystrix 并不是只要有一条请求经过就去统计,而是将整个滑动窗口均分为 numBuckets 份,时间每经过一份就去统计一次。**在经过一个时间窗口后,才会判断断路器状态要不要开启,请看下面的例子。**
## 实例 Demo
### HystrixCommand 配置参数
......@@ -178,4 +192,10 @@ ProductInfo(id=1, name=iphone7手机, price=5599.0, pictureList=a.jpg,b.jpg, spe
而是直接走降级逻辑,调用 getFallback() 执行。
休眠了 3s 后,我们在之后的 70 次请求中,都传入 productId 为 1。由于我们前面设置了 3000ms 过后断路器变为 `half-open` 状态。因此 Hystrix 会尝试执行请求,发现成功了,那么断路器关闭,之后的所有请求也都能正常调用了。
\ No newline at end of file
休眠了 3s 后,我们在之后的 70 次请求中,都传入 productId 为 1。由于我们前面设置了 3000ms 过后断路器变为 `half-open` 状态。因此 Hystrix 会尝试执行请求,发现成功了,那么断路器关闭,之后的所有请求也都能正常调用了。
### 参考内容
1. [Hystrix issue 1459](https://github.com/Netflix/Hystrix/issues/1459)
2. [Hystrix Metrics](https://github.com/Netflix/Hystrix/wiki/Configuration#metrics)
3.
\ No newline at end of file
......@@ -17,7 +17,7 @@ HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolat
HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)
```
线程池机制,每个 command 运行在一个线程中,限流是通过线程池的大小来控制的;信号量机制,command 是运行在调用线程中,通过信号量的容量来进行限流。
线程池机制,每个 command 运行在一个线程中,限流是通过线程池的大小来控制的;信号量机制,command 是运行在调用线程中(也就是 Tomcat 的线程池),通过信号量的容量来进行限流。
如何在线程池和信号量之间做选择?
......@@ -33,7 +33,7 @@ HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolat
每一个 command,都可以设置一个自己的名称 command key,同时可以设置一个自己的组 command group。
```java
private static final Setter cachedSetter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"));
.andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"));
public CommandHelloWorld(String name) {
super(cachedSetter);
......@@ -49,8 +49,8 @@ ThreadPoolKey 代表了一个 HystrixThreadPool,用来进行统一监控、统
如果不想直接用 command group,也可以手动设置 ThreadPool 的名称。
```java
private static final Setter cachedSetter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool"));
.andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool"));
public CommandHelloWorld(String name) {
super(cachedSetter);
......@@ -59,13 +59,13 @@ public CommandHelloWorld(String name) {
```
### command key & command group & command thread pool
**command key** ,代表了一类 command,一般来说,代表了底层的依赖服务的一个接口。
**command key** ,代表了一类 command,一般来说,代表了下游依赖服务的某个接口。
**command group** ,代表了某一个底层的依赖服务,这是很合理的,一个依赖服务可能会暴露出来多个接口,每个接口就是一个 command key。command group 在逻辑上去组织起来一堆 command key 的调用、统计信息、成功次数、timeout 超时次数、失败次数等,可以看到某一个服务整体的一些访问情况。一般来说,**推荐**根据一个服务区划分出一个线程池,command key 默认都是属于同一个线程池的。
**command group** ,代表了某一个下游依赖服务,这是很合理的,一个依赖服务可能会暴露出来多个接口,每个接口就是一个 command key。command group 在逻辑上对一堆 command key 的调用次数、成功次数、timeout 次数、失败次数等进行统计,可以看到某一个服务整体的一些访问情况。**一般来说,推荐根据一个服务区划分出一个线程池,command key 默认都是属于同一个线程池的。**
比如说你以一个服务为粒度,估算出来这个服务每秒的所有接口加起来的整体 `QPS` 在 100 左右,你调用这个服务,当前这个服务部署了 10 个服务实例,每个服务实例上,其实用这个 command group 对应这个服务,给一个线程池,量大概在 10 个左右就可以了,你对整个服务的整体的访问 QPS 就大概在每秒 100 左右
比如说有一个服务 A,你估算出来服务 A 每秒所有接口加起来的整体 `QPS` 在 100 左右,你有一个服务 B 去调用服务 A。你的服务 B 部署了 10 个实例,每个实例上,用 command group 去对应下游服务 A。给一个线程池,量大概是 10 就可以了,这样服务 B 对服务 A 整体的访问 QPS 就大概是每秒 100 了
但是,如果说 command group 对应了一个服务,而这个服务暴露出来的几个接口,访问量很不一样,差异非常之大。你可能就希望在这个服务 command group 内部,包含的对应多个接口的 command key,做一些细粒度的资源隔离。就是说,对同一个服务的不同接口,使用不同的线程池。
但是,如果说 command group 对应了一个服务,而这个服务暴露出来的几个接口,访问量很不一样,差异非常之大。你可能就希望在这个服务对应 command group 的内部,包含对应多个接口的 command key,做一些细粒度的资源隔离。**就是说,希望对同一个服务的不同接口,使用不同的线程池。**
```
command key -> command group
......@@ -86,7 +86,7 @@ HystrixThreadPoolProperties.Setter().withCoreSize(int value);
### queueSizeRejectionThreshold
如果说线程池中的 10 个线程都在工作中,没有空闲的线程来做其它的事情,此时再有请求过来,会先进入队列积压。如果说队列积压满了,再有请求过来,就直接 reject,拒绝请求,执行 fallback 降级的逻辑,快速返回。
![hystrix-thread-pool-queue](/images/hystrix-thread-pool-queue.png)
![hystrix-thread-pool-queue](./images/hystrix-thread-pool-queue.png)
控制 queue 满了之后 reject 的 threshold,因为 maxQueueSize 不允许热修改,因此提供这个参数可以热修改,控制队列的最大大小。
......
......@@ -6,9 +6,9 @@
Hystrix 可以让我们在分布式系统中对服务间的调用进行控制,加入一些**调用延迟**或者**依赖故障****容错机制**
Hystrix 通过将依赖服务进行**资源隔离**,进而阻止某个依赖服务出现故障时在整个系统所有的依赖服务调用中进行蔓延;同时Hystrix 还提供故障时的 fallback 降级机制。
Hystrix 通过将依赖服务进行**资源隔离**,进而阻止某个依赖服务出现故障时在整个系统所有的依赖服务调用中进行蔓延;同时 Hystrix 还提供故障时的 fallback 降级机制。
总而言之,Hystrix 通过这些方法帮助我们提升分布式系统的可用性和稳定性。
**总而言之,Hystrix 通过这些方法帮助我们提升分布式系统的可用性和稳定性。**
### Hystrix 的历史
Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外的优酷或者爱奇艺之类的视频网站)的 API 团队从 2011 年开始做一些提升系统可用性和稳定性的工作,Hystrix 就是从那时候开始发展出来的。
......@@ -31,7 +31,7 @@ Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外
有这样一个分布式系统,服务 A 依赖于服务 B,服务 B 依赖于服务 C/D/E。在这样一个成熟的系统内,比如说最多可能只有 100 个线程资源。正常情况下,40 个线程并发调用服务 C,各 30 个线程并发调用 D/E。
调用服务 C,只需要 20ms,现在因为服务 C 故障了,比如延迟,或者挂了,此时线程会 hang 住 2s 左右。40 个线程全部被卡住,由于请求不断涌入,其它的线程也用来调用服务 C,同样也会被卡住。这样导致服务 B 的线程资源被耗尽,无法接收新的请求,甚至可能因为大量线程不断的运转,导致自己宕机。服务 A 也挂
调用服务 C,只需要 20ms,现在因为服务 C 故障了,比如延迟,或者挂了,此时线程会 hang 住 2s 左右。40 个线程全部被卡住,由于请求不断涌入,其它的线程也用来调用服务 C,同样也会被卡住。这样导致服务 B 的线程资源被耗尽,无法接收新的请求,甚至可能因为大量线程不断的运转,导致自己宕机。这种影响势必会蔓延至服务 A,导致服务 A 也跟着挂掉
![service-invoke-road](./images/service-invoke-road.png)
......
## 基于 Hystrix 线程池技术实现资源隔离
[上一讲](./e-commerce-website-detail-page-architecture.md)提到,如果从 Nginx 开始,缓存都失效了,Nginx 会直接通过缓存服务调用商品服务获取最新商品数据(我们基于电商项目做个讨论),有可能出现调用延时而把缓存服务资源耗尽的情况。这里,我们就来说说,怎么通过 Hystrix 线程池技术实现资源隔离。
资源隔离,就是说,你如果要把对某一个依赖服务的所有调用请求,全部隔离在同一份资源池内,不会去用其它资源了,这就叫资源隔离。哪怕对这个依赖服务,比如说商品服务,现在同时发起的调用量已经到了 1000,但是线程池内就 10 个线程,最多就只会用这 10 个线程去执行,不会说,对商品服务的请求,因为接口调用延时,将 Tomcat 内部所有的线程资源全部耗尽。
资源隔离,就是说,你如果要把对某一个依赖服务的所有调用请求,全部隔离在同一份资源池内,不会去用其它资源了,这就叫资源隔离。哪怕对这个依赖服务,比如说商品服务,现在同时发起的调用量已经到了 1000,但是分配给商品服务线程池内就 10 个线程,最多就只会用这 10 个线程去执行。不会因为对商品服务调用的延迟,将 Tomcat 内部所有的线程资源全部耗尽。
Hystrix 进行资源隔离,其实是提供了一个抽象,叫做 command。这也是 Hystrix 最最基本的资源隔离技术。
Hystrix 进行资源隔离,其实是提供了一个抽象,叫做 Command。这也是 Hystrix 最最基本的资源隔离技术。
### 利用 HystrixCommand 获取单条数据
我们通过将调用商品服务的操作封装在 HystrixCommand 中,限定一个 key,比如下面的 `GetProductInfoCommandGroup`,在这里我们可以简单认为这是一个线程池,每次调用商品服务,就只会用该线程池中的资源,不会再去用其它线程资源了。
......@@ -28,7 +28,7 @@ public class GetProductInfoCommand extends HystrixCommand<ProductInfo> {
}
```
我们在缓存服务接口中,根据 productId 创建 command 并执行,获取到商品数据。
我们在缓存服务接口中,根据 productId 创建 Command 并执行,获取到商品数据。
```java
@RequestMapping("/getProductInfo")
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册