Skip to content

Commit c532639

Browse files
author
zhoushitong
committed
完善 Hystrix
1 parent eddf65c commit c532639

6 files changed

Lines changed: 67 additions & 47 deletions

File tree

docs/high-availability/README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
# 高可用架构
2-
- [Hystrix 介绍](/docs/high-availability/hystrix-introduction.md)
3-
- [电商网站详情页系统架构](/docs/high-availability/e-commerce-website-detail-page-architecture.md)
4-
- [Hystrix 线程池技术实现资源隔离](/docs/high-availability/hystrix-thread-pool-isolation.md)
5-
- [Hystrix 信号量机制实现资源隔离](/docs/high-availability/hystrix-semphore-isolation.md)
6-
- [Hystrix 隔离策略细粒度控制](/docs/high-availability/hystrix-execution-isolation.md)
7-
- [深入 Hystrix 执行时内部原理](/docs/high-availability/hystrix-process.md)
8-
- [基于 request cache 请求缓存技术优化批量商品数据查询接口](/docs/high-availability/hystrix-request-cache.md)
9-
- [基于本地缓存的 fallback 降级机制](/docs/high-availability/hystrix-fallback.md)
10-
- [深入 Hystrix 断路器执行原理](/docs/high-availability/hystrix-circuit-breaker.md)
11-
- [深入 Hystrix 线程池隔离与接口限流](/docs/high-availability/hystrix-thread-pool-current-limiting.md)
12-
- [基于 timeout 机制为服务接口调用超时提供安全保护](/docs/high-availability/hystrix-timeout.md)
2+
- [Hystrix 介绍](./hystrix-introduction.md)
3+
- [电商网站详情页系统架构](./e-commerce-website-detail-page-architecture.md)
4+
- [Hystrix 线程池技术实现资源隔离](./hystrix-thread-pool-isolation.md)
5+
- [Hystrix 信号量机制实现资源隔离](./hystrix-semphore-isolation.md)
6+
- [Hystrix 隔离策略细粒度控制](./hystrix-execution-isolation.md)
7+
- [深入 Hystrix 执行时内部原理](./hystrix-process.md)
8+
- [基于 request cache 请求缓存技术优化批量商品数据查询接口](./hystrix-request-cache.md)
9+
- [基于本地缓存的 fallback 降级机制](./hystrix-fallback.md)
10+
- [深入 Hystrix 断路器执行原理](./hystrix-circuit-breaker.md)
11+
- [深入 Hystrix 线程池隔离与接口限流](./hystrix-thread-pool-current-limiting.md)
12+
- [基于 timeout 机制为服务接口调用超时提供安全保护](./hystrix-timeout.md)
1313

1414
## 高可用系统
1515
- 如何设计一个高可用系统?

docs/high-availability/hystrix-circuit-breaker.md

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,54 @@
11
## 深入 Hystrix 断路器执行原理
22

3-
### RequestVolumeThreshold
3+
### 状态机
4+
5+
Hystrix 断路器有三种状态,分别是关闭(Closed)、打开(Open)与半开(Half-Open),三种状态转化关系如下:
6+
7+
![image-20191104211642271](./images/Hystrix断路器状态机.png)
8+
9+
1. `Closed` 断路器关闭:调用下游的请求正常通过
10+
2. `Open` 断路器打开:阻断对下游服务的调用,直接走 Fallback 逻辑
11+
3. `Half-Open` 断路器处于半开状态:[SleepWindowInMilliseconds](#circuitBreaker.sleepWindowInMilliseconds)
12+
13+
### [Enabled](https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakerenabled)
414

515
```java
616
HystrixCommandProperties.Setter()
7-
.withCircuitBreakerRequestVolumeThreshold(int)
17+
.withCircuitBreakerEnabled(boolean)
818
```
919

10-
表示在滑动窗口中,至少有多少个请求,才可能触发断路
20+
控制断路器是否工作,包括跟踪依赖服务调用的健康状况,以及对异常情况过多时是否允许触发断路。默认值 `true`
1121

12-
Hystrix 经过断路器的流量超过了一定的阈值,才有可能触发断路。比如说,要求在 10s 内经过断路器的流量必须达到 20 个,而实际经过断路器的流量才 10 个,那么根本不会去判断要不要断路。
13-
14-
### ErrorThresholdPercentage
22+
### [circuitBreaker.requestVolumeThreshold](https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakerrequestvolumethreshold)
1523

1624
```java
1725
HystrixCommandProperties.Setter()
18-
.withCircuitBreakerErrorThresholdPercentage(int)
26+
.withCircuitBreakerRequestVolumeThreshold(int)
1927
```
2028

21-
表示异常比例达到多少,才会触发断路,默认值是 50(%)。
22-
23-
如果断路器统计到的异常调用的占比超过了一定的阈值,比如说在 10s 内,经过断路器的流量达到了 30 个,同时其中异常访问的数量也达到了一定的比例,比如 60% 的请求都是异常(报错 / 超时 / reject),就会开启断路。
29+
表示在一次统计的**时间滑动窗口中(这个参数也很重要,下面有说到)**,至少经过多少个请求,才可能触发断路,默认值 20。**经过 Hystrix 断路器的流量只有在超过了一定阈值后,才有可能触发断路。**比如说,要求在 10s 内经过断路器的流量必须达到 20 个,而实际经过断路器的请求有 19 个,即使这 19 个请求全都失败,也不会去判断要不要断路。
2430

25-
### SleepWindowInMilliseconds
31+
### [circuitBreaker.errorThresholdPercentage](https://github.com/Netflix/Hystrix/wiki/Configuration#circuitBreaker.errorThresholdPercentage)
2632

2733
```java
2834
HystrixCommandProperties.Setter()
29-
.withCircuitBreakerSleepWindowInMilliseconds(int)
35+
.withCircuitBreakerErrorThresholdPercentage(int)
3036
```
3137

32-
断路开启,也就是由 close 转换到 open 状态(close -> open)。那么之后在 `SleepWindowInMilliseconds` 时间内,所有经过该断路器的请求全部都会被断路,不调用后端服务,直接走 fallback 降级机制。
33-
34-
而在该参数时间过后,断路器会变为 `half-open` 半开闭状态,尝试让一条请求经过断路器,看能不能正常调用。如果调用成功了,那么就自动恢复,断路器转为 close 状态。
38+
表示异常比例达到多少,才会触发断路,默认值是 50(%)。
3539

36-
### Enabled
40+
#### [circuitBreaker.sleepWindowInMilliseconds](https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakersleepwindowinmilliseconds)
3741

3842
```java
3943
HystrixCommandProperties.Setter()
40-
.withCircuitBreakerEnabled(boolean)
44+
.withCircuitBreakerSleepWindowInMilliseconds(int)
4145
```
4246

43-
控制是否允许断路器工作,包括跟踪依赖服务调用的健康状况,以及对异常情况过多时是否允许触发断路。默认值是 `true`
47+
断路器状态由 Close 转换到 Open,在之后 `SleepWindowInMilliseconds` 时间内,所有经过该断路器的请求会被断路,不调用后端服务,直接走 Fallback 降级机制,默认值 5000(ms)。
48+
49+
而在该参数时间过后,断路器会变为 `Half-Open` 半开闭状态,尝试让一条请求经过断路器,看能不能正常调用。如果调用成功了,那么就自动恢复,断路器转为 Close 状态。
4450

45-
### ForceOpen
51+
### [ForceOpen](https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakerforceopen)
4652

4753
```java
4854
HystrixCommandProperties.Setter()
@@ -51,7 +57,7 @@ HystrixCommandProperties.Setter()
5157

5258
如果设置为 true 的话,直接强迫打开断路器,相当于是手动断路了,手动降级,默认值是 `false`
5359

54-
### ForceClosed
60+
### [ForceClosed](https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakerforceclosed)
5561

5662
```java
5763
HystrixCommandProperties.Setter()
@@ -60,6 +66,14 @@ HystrixCommandProperties.Setter()
6066

6167
如果设置为 true,直接强迫关闭断路器,相当于手动停止断路了,手动升级,默认值是 `false`
6268

69+
### Metrics 统计器
70+
71+
与 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):
72+
73+
> 一位乘客坐在正在行驶的列车的靠窗座位上,列车行驶的公路两侧种着一排挺拔的白杨树,随着列车的前进,路边的白杨树迅速从窗口滑过。我们用每棵树来代表一个请求,用列车的行驶代表时间的流逝,那么,列车上的这个窗口就是一个典型的滑动窗口,这个乘客能通过窗口看到的白杨树就是 Hystrix 要统计的数据。
74+
75+
Hystrix 并不是只要有一条请求经过就去统计,而是将整个滑动窗口均分为 numBuckets 份,时间每经过一份就去统计一次。**在经过一个时间窗口后,才会判断断路器状态要不要开启,请看下面的例子。**
76+
6377
## 实例 Demo
6478

6579
### HystrixCommand 配置参数
@@ -178,4 +192,10 @@ ProductInfo(id=1, name=iphone7手机, price=5599.0, pictureList=a.jpg,b.jpg, spe
178192

179193
而是直接走降级逻辑,调用 getFallback() 执行。
180194

181-
休眠了 3s 后,我们在之后的 70 次请求中,都传入 productId 为 1。由于我们前面设置了 3000ms 过后断路器变为 `half-open` 状态。因此 Hystrix 会尝试执行请求,发现成功了,那么断路器关闭,之后的所有请求也都能正常调用了。
195+
休眠了 3s 后,我们在之后的 70 次请求中,都传入 productId 为 1。由于我们前面设置了 3000ms 过后断路器变为 `half-open` 状态。因此 Hystrix 会尝试执行请求,发现成功了,那么断路器关闭,之后的所有请求也都能正常调用了。
196+
197+
### 参考内容
198+
199+
1. [Hystrix issue 1459](https://github.com/Netflix/Hystrix/issues/1459)
200+
2. [Hystrix Metrics](https://github.com/Netflix/Hystrix/wiki/Configuration#metrics)
201+
3.

docs/high-availability/hystrix-execution-isolation.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolat
1717
HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)
1818
```
1919

20-
线程池机制,每个 command 运行在一个线程中,限流是通过线程池的大小来控制的;信号量机制,command 是运行在调用线程中,通过信号量的容量来进行限流。
20+
线程池机制,每个 command 运行在一个线程中,限流是通过线程池的大小来控制的;信号量机制,command 是运行在调用线程中(也就是 Tomcat 的线程池),通过信号量的容量来进行限流。
2121

2222
如何在线程池和信号量之间做选择?
2323

@@ -33,7 +33,7 @@ HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolat
3333
每一个 command,都可以设置一个自己的名称 command key,同时可以设置一个自己的组 command group。
3434
```java
3535
private static final Setter cachedSetter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
36-
.andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"));
36+
.andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"));
3737

3838
public CommandHelloWorld(String name) {
3939
super(cachedSetter);
@@ -49,8 +49,8 @@ ThreadPoolKey 代表了一个 HystrixThreadPool,用来进行统一监控、统
4949
如果不想直接用 command group,也可以手动设置 ThreadPool 的名称。
5050
```java
5151
private static final Setter cachedSetter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
52-
.andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"))
53-
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool"));
52+
.andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"))
53+
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool"));
5454

5555
public CommandHelloWorld(String name) {
5656
super(cachedSetter);
@@ -59,13 +59,13 @@ public CommandHelloWorld(String name) {
5959
```
6060

6161
### command key & command group & command thread pool
62-
**command key** ,代表了一类 command,一般来说,代表了底层的依赖服务的一个接口
62+
**command key** ,代表了一类 command,一般来说,代表了下游依赖服务的某个接口
6363

64-
**command group**代表了某一个底层的依赖服务,这是很合理的,一个依赖服务可能会暴露出来多个接口,每个接口就是一个 command key。command group 在逻辑上去组织起来一堆 command key 的调用、统计信息、成功次数、timeout 超时次数、失败次数等,可以看到某一个服务整体的一些访问情况。一般来说,**推荐**根据一个服务区划分出一个线程池,command key 默认都是属于同一个线程池的。
64+
**command group**代表了某一个下游依赖服务,这是很合理的,一个依赖服务可能会暴露出来多个接口,每个接口就是一个 command key。command group 在逻辑上对一堆 command key 的调用次数、成功次数、timeout 次数、失败次数等进行统计,可以看到某一个服务整体的一些访问情况。**一般来说,推荐根据一个服务区划分出一个线程池,command key 默认都是属于同一个线程池的。**
6565

66-
比如说你以一个服务为粒度,估算出来这个服务每秒的所有接口加起来的整体 `QPS` 在 100 左右,你调用这个服务,当前这个服务部署了 10 个服务实例,每个服务实例上,其实用这个 command group 对应这个服务,给一个线程池,量大概在 10 个左右就可以了,你对整个服务的整体的访问 QPS 就大概在每秒 100 左右
66+
比如说有一个服务 A,你估算出来服务 A 每秒所有接口加起来的整体 `QPS` 在 100 左右,你有一个服务 B 去调用服务 A。你的服务 B 部署了 10 个实例,每个实例上,用 command group 去对应下游服务 A。给一个线程池,量大概是 10 就可以了,这样服务 B 对服务 A 整体的访问 QPS 就大概是每秒 100
6767

68-
但是,如果说 command group 对应了一个服务,而这个服务暴露出来的几个接口,访问量很不一样,差异非常之大。你可能就希望在这个服务 command group 内部,包含的对应多个接口的 command key,做一些细粒度的资源隔离。就是说,对同一个服务的不同接口,使用不同的线程池。
68+
但是,如果说 command group 对应了一个服务,而这个服务暴露出来的几个接口,访问量很不一样,差异非常之大。你可能就希望在这个服务对应 command group 的内部,包含对应多个接口的 command key,做一些细粒度的资源隔离。**就是说,希望对同一个服务的不同接口,使用不同的线程池。**
6969

7070
```
7171
command key -> command group
@@ -86,7 +86,7 @@ HystrixThreadPoolProperties.Setter().withCoreSize(int value);
8686
### queueSizeRejectionThreshold
8787
如果说线程池中的 10 个线程都在工作中,没有空闲的线程来做其它的事情,此时再有请求过来,会先进入队列积压。如果说队列积压满了,再有请求过来,就直接 reject,拒绝请求,执行 fallback 降级的逻辑,快速返回。
8888

89-
![hystrix-thread-pool-queue](/images/hystrix-thread-pool-queue.png)
89+
![hystrix-thread-pool-queue](./images/hystrix-thread-pool-queue.png)
9090

9191
控制 queue 满了之后 reject 的 threshold,因为 maxQueueSize 不允许热修改,因此提供这个参数可以热修改,控制队列的最大大小。
9292

docs/high-availability/hystrix-introduction.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
Hystrix 可以让我们在分布式系统中对服务间的调用进行控制,加入一些**调用延迟**或者**依赖故障****容错机制**
88

9-
Hystrix 通过将依赖服务进行**资源隔离**,进而阻止某个依赖服务出现故障时在整个系统所有的依赖服务调用中进行蔓延;同时Hystrix 还提供故障时的 fallback 降级机制。
9+
Hystrix 通过将依赖服务进行**资源隔离**,进而阻止某个依赖服务出现故障时在整个系统所有的依赖服务调用中进行蔓延;同时 Hystrix 还提供故障时的 fallback 降级机制。
1010

11-
总而言之,Hystrix 通过这些方法帮助我们提升分布式系统的可用性和稳定性。
11+
**总而言之,Hystrix 通过这些方法帮助我们提升分布式系统的可用性和稳定性。**
1212

1313
### Hystrix 的历史
1414
Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外的优酷或者爱奇艺之类的视频网站)的 API 团队从 2011 年开始做一些提升系统可用性和稳定性的工作,Hystrix 就是从那时候开始发展出来的。
@@ -31,7 +31,7 @@ Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外
3131

3232
有这样一个分布式系统,服务 A 依赖于服务 B,服务 B 依赖于服务 C/D/E。在这样一个成熟的系统内,比如说最多可能只有 100 个线程资源。正常情况下,40 个线程并发调用服务 C,各 30 个线程并发调用 D/E。
3333

34-
调用服务 C,只需要 20ms,现在因为服务 C 故障了,比如延迟,或者挂了,此时线程会 hang 住 2s 左右。40 个线程全部被卡住,由于请求不断涌入,其它的线程也用来调用服务 C,同样也会被卡住。这样导致服务 B 的线程资源被耗尽,无法接收新的请求,甚至可能因为大量线程不断的运转,导致自己宕机。服务 A 也挂
34+
调用服务 C,只需要 20ms,现在因为服务 C 故障了,比如延迟,或者挂了,此时线程会 hang 住 2s 左右。40 个线程全部被卡住,由于请求不断涌入,其它的线程也用来调用服务 C,同样也会被卡住。这样导致服务 B 的线程资源被耗尽,无法接收新的请求,甚至可能因为大量线程不断的运转,导致自己宕机。这种影响势必会蔓延至服务 A,导致服务 A 也跟着挂掉
3535

3636
![service-invoke-road](./images/service-invoke-road.png)
3737

docs/high-availability/hystrix-thread-pool-isolation.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
## 基于 Hystrix 线程池技术实现资源隔离
22
[上一讲](./e-commerce-website-detail-page-architecture.md)提到,如果从 Nginx 开始,缓存都失效了,Nginx 会直接通过缓存服务调用商品服务获取最新商品数据(我们基于电商项目做个讨论),有可能出现调用延时而把缓存服务资源耗尽的情况。这里,我们就来说说,怎么通过 Hystrix 线程池技术实现资源隔离。
33

4-
资源隔离,就是说,你如果要把对某一个依赖服务的所有调用请求,全部隔离在同一份资源池内,不会去用其它资源了,这就叫资源隔离。哪怕对这个依赖服务,比如说商品服务,现在同时发起的调用量已经到了 1000,但是线程池内就 10 个线程,最多就只会用这 10 个线程去执行,不会说,对商品服务的请求,因为接口调用延时,将 Tomcat 内部所有的线程资源全部耗尽。
4+
资源隔离,就是说,你如果要把对某一个依赖服务的所有调用请求,全部隔离在同一份资源池内,不会去用其它资源了,这就叫资源隔离。哪怕对这个依赖服务,比如说商品服务,现在同时发起的调用量已经到了 1000,但是分配给商品服务线程池内就 10 个线程,最多就只会用这 10 个线程去执行。不会因为对商品服务调用的延迟,将 Tomcat 内部所有的线程资源全部耗尽。
55

6-
Hystrix 进行资源隔离,其实是提供了一个抽象,叫做 command。这也是 Hystrix 最最基本的资源隔离技术。
6+
Hystrix 进行资源隔离,其实是提供了一个抽象,叫做 Command。这也是 Hystrix 最最基本的资源隔离技术。
77

88
### 利用 HystrixCommand 获取单条数据
99
我们通过将调用商品服务的操作封装在 HystrixCommand 中,限定一个 key,比如下面的 `GetProductInfoCommandGroup`,在这里我们可以简单认为这是一个线程池,每次调用商品服务,就只会用该线程池中的资源,不会再去用其它线程资源了。
@@ -28,7 +28,7 @@ public class GetProductInfoCommand extends HystrixCommand<ProductInfo> {
2828
}
2929
```
3030

31-
我们在缓存服务接口中,根据 productId 创建 command 并执行,获取到商品数据。
31+
我们在缓存服务接口中,根据 productId 创建 Command 并执行,获取到商品数据。
3232

3333
```java
3434
@RequestMapping("/getProductInfo")
176 KB
Loading

0 commit comments

Comments
 (0)