Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fce3c1f

Browse files
committed
feat: add hystrix-circuit-breaker
深入 Hystrix 断路器执行原理
1 parent 82173fe commit fce3c1f

File tree

4 files changed

+186
-5
lines changed

4 files changed

+186
-5
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
- [深入 Hystrix 执行时内部原理](/docs/high-availability/hystrix-process.md)
8989
- [基于 request cache 请求缓存技术优化批量商品数据查询接口](/docs/high-availability/hystrix-request-cache.md)
9090
- [基于本地缓存的 fallback 降级机制](/docs/high-availability/hystrix-fallback.md)
91+
- [深入 Hystrix 断路器执行原理](/docs/high-availability/hystrix-circuit-breaker.md)
9192

9293
### 高可用系统
9394
- 如何设计一个高可用系统?
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
## 深入 Hystrix 断路器执行原理
2+
3+
### RequestVolumeThreshold
4+
5+
```java
6+
HystrixCommandProperties.Setter()
7+
.withCircuitBreakerRequestVolumeThreshold(int)
8+
```
9+
10+
表示在滑动窗口中,至少有多少个请求,才可能触发断路。
11+
12+
Hystrix 经过断路器的流量超过了一定的阈值,才有可能触发断路。比如说,要求在 10s 内经过断路器的流量必须达到 20 个,而实际经过断路器的流量才 10 个,那么根本不会去判断要不要断路。
13+
14+
### ErrorThresholdPercentage
15+
16+
```java
17+
HystrixCommandProperties.Setter()
18+
.withCircuitBreakerErrorThresholdPercentage(int)
19+
```
20+
21+
表示异常比例达到多少,才会触发断路,默认值是 50(%)。
22+
23+
如果断路器统计到的异常调用的占比超过了一定的阈值,比如说在 10s 内,经过断路器的流量达到了 30 个,同时其中异常访问的数量也达到了一定的比例,比如 60% 的请求都是异常(报错 / 超时 / reject),就会开启断路。
24+
25+
### SleepWindowInMilliseconds
26+
27+
```java
28+
HystrixCommandProperties.Setter()
29+
.withCircuitBreakerSleepWindowInMilliseconds(int)
30+
```
31+
32+
断路开启,也就是由 close 转换到 open 状态(close -> open)。那么之后在 `SleepWindowInMilliseconds` 时间内,所有经过该断路器的请求全部都会被断路,不调用后端服务,直接走 fallback 降级机制。
33+
34+
而在该参数时间过后,断路器会变为 `half-open` 半开闭状态,尝试让一条请求经过断路器,看能不能正常调用。如果调用成功了,那么就自动恢复,断路器转为 close 状态。
35+
36+
### Enabled
37+
38+
```java
39+
HystrixCommandProperties.Setter()
40+
.withCircuitBreakerEnabled(boolean)
41+
```
42+
43+
控制是否允许断路器工作,包括跟踪依赖服务调用的健康状况,以及对异常情况过多时是否允许触发断路。默认值是 `true`
44+
45+
### ForceOpen
46+
47+
```java
48+
HystrixCommandProperties.Setter()
49+
.withCircuitBreakerForceOpen(boolean)
50+
```
51+
52+
如果设置为 true 的话,直接强迫打开断路器,相当于是手动断路了,手动降级,默认值是 `false`
53+
54+
### ForceClosed
55+
56+
```java
57+
HystrixCommandProperties.Setter()
58+
.withCircuitBreakerForceClosed(boolean)
59+
```
60+
61+
如果设置为 true,直接强迫关闭断路器,相当于手动停止断路了,手动升级,默认值是 `false`
62+
63+
## 实例 Demo
64+
65+
### HystrixCommand 配置参数
66+
在 GetProductInfoCommand 中配置 Setter 断路器相关参数。
67+
68+
- 滑动窗口中,最少 20 个请求,才可能触发断路。
69+
- 异常比例达到 40% 时,才触发断路。
70+
- 断路后 3000ms 内,所有请求都被 reject,直接走 fallback 降级,不会调用 run() 方法。3000ms 过后,变为 half-open 状态。
71+
72+
run() 方法中,我们判断一下 productId 是否为 -1,是的话,直接抛出异常。这么写,我们之后测试的时候就可以传入 productId=-1,**模拟服务执行异常**了。
73+
74+
在降级逻辑中,我们直接给它返回降级商品就好了。
75+
76+
```java
77+
public class GetProductInfoCommand extends HystrixCommand<ProductInfo> {
78+
79+
private Long productId;
80+
81+
private static final HystrixCommandKey KEY = HystrixCommandKey.Factory.asKey("GetProductInfoCommand");
82+
83+
public GetProductInfoCommand(Long productId) {
84+
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductInfoService"))
85+
.andCommandKey(KEY)
86+
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
87+
// 是否允许断路器工作
88+
.withCircuitBreakerEnabled(true)
89+
// 滑动窗口中,最少有多少个请求,才可能触发断路
90+
.withCircuitBreakerRequestVolumeThreshold(20)
91+
// 异常比例达到多少,才触发断路,默认50%
92+
.withCircuitBreakerErrorThresholdPercentage(40)
93+
// 断路后多少时间内直接reject请求,之后进入half-open状态,默认5000ms
94+
.withCircuitBreakerSleepWindowInMilliseconds(3000)));
95+
this.productId = productId;
96+
}
97+
98+
@Override
99+
protected ProductInfo run() throws Exception {
100+
System.out.println("调用接口查询商品数据,productId=" + productId);
101+
102+
if (productId == -1L) {
103+
throw new Exception();
104+
}
105+
106+
String url = "http://localhost:8081/getProductInfo?productId=" + productId;
107+
String response = HttpClientUtils.sendGetRequest(url);
108+
return JSONObject.parseObject(response, ProductInfo.class);
109+
}
110+
111+
@Override
112+
protected ProductInfo getFallback() {
113+
ProductInfo productInfo = new ProductInfo();
114+
productInfo.setName("降级商品");
115+
return productInfo;
116+
}
117+
}
118+
```
119+
120+
### 断路测试类
121+
我们在测试类中,前 30 次请求,传入 productId=-1,然后休眠 3s,之后 70 次请求,传入 productId=1。
122+
123+
```java
124+
@SpringBootTest
125+
@RunWith(SpringRunner.class)
126+
public class CircuitBreakerTest {
127+
128+
@Test
129+
public void testCircuitBreaker() {
130+
String baseURL = "http://localhost:8080/getProductInfo?productId=";
131+
132+
for (int i = 0; i < 30; ++i) {
133+
// 传入-1,会抛出异常,然后走降级逻辑
134+
HttpClientUtils.sendGetRequest(baseURL + "-1");
135+
}
136+
137+
TimeUtils.sleep(3);
138+
System.out.println("After sleeping...");
139+
140+
for (int i = 31; i < 100; ++i) {
141+
// 传入1,走服务正常调用
142+
HttpClientUtils.sendGetRequest(baseURL + "1");
143+
}
144+
}
145+
}
146+
```
147+
148+
### 测试结果
149+
150+
测试结果,我们可以明显看出系统断路与恢复的整个过程。
151+
152+
```c
153+
调用接口查询商品数据,productId=-1
154+
ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
155+
// ...
156+
// 这里重复打印了 20 次上面的结果
157+
158+
159+
ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null)
160+
// ...
161+
// 这里重复打印了 8 次上面的结果
162+
163+
164+
// 休眠 3s 后
165+
调用接口查询商品数据,productId=1
166+
ProductInfo(id=1, name=iphone7手机, price=5599.0, pictureList=a.jpg,b.jpg, specification=iphone7的规格, service=iphone7的售后服务, color=红色,白色,黑色, size=5.5, shopId=1, modifiedTime=2017-01-01 12:00:00, cityId=1, cityName=null, brandId=1, brandName=null)
167+
// ...
168+
// 这里重复打印了 69 次上面的结果
169+
```
170+
171+
前 30 次请求,我们传入的 productId 为 -1,所以服务执行过程中会抛出异常。我们设置了最少 20 次请求通过断路器并且异常比例超出 40% 就触发断路。因此执行了 21 次接口调用,每次都抛异常并且走降级,21 次过后,断路器就被打开了。
172+
173+
之后的 9 次请求,都不会执行 run() 方法,也就不会打印以下信息。
174+
175+
```
176+
调用接口查询商品数据,productId=-1
177+
```
178+
179+
而是直接走降级逻辑,调用 getFallback() 执行。
180+
181+
休眠了 3s 后,我们在之后的 70 次请求中,都传入 productId 为 1。由于我们前面设置了 3000ms 过后断路器变为 `half-open` 状态。因此 Hystrix 会尝试执行请求,发现成功了,那么断路器关闭,之后的所有请求也都能正常调用了。

docs/high-availability/hystrix-fallback.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fallback 降级逻辑中,也可以直接返回一个默认值。
2323

2424
假如说,品牌服务接口挂掉了,那么我们可以尝试从本地内存中,获取一份稍过期的数据,先凑合着用。
2525

26-
### 本地缓存获取数据
26+
### 步骤一:本地缓存获取数据
2727
本地获取品牌名称的代码大致如下。
2828

2929
```java
@@ -50,7 +50,7 @@ public class BrandCache {
5050
}
5151
```
5252

53-
### 实现 GetBrandNameCommand
53+
### 步骤二:实现 GetBrandNameCommand
5454
在 GetBrandNameCommand 中,run() 方法的正常逻辑是去调用品牌服务的接口获取到品牌名称,如果调用失败,报错了,那么就会去调用 fallback 降级机制。
5555

5656
这里,我们直接**模拟接口调用报错**,给它抛出个异常。
@@ -94,7 +94,7 @@ public class GetBrandNameCommand extends HystrixCommand<String> {
9494

9595
`FallbackIsolationSemaphoreMaxConcurrentRequests` 用于设置 fallback 最大允许的并发请求量,默认值是 10,是通过 semaphore 信号量的机制去限流的。如果超出了这个最大值,那么直接 reject。
9696

97-
### CacheController 调用接口
97+
### 步骤三:CacheController 调用接口
9898
CacheController 中,我们通过 productInfo 获取 brandId,然后创建 GetBrandNameCommand 并执行,去尝试获取 brandName。这里执行会报错,因为我们在 run() 方法中直接抛出异常,Hystrix 就会去调用 getFallback() 方法走降级逻辑。
9999

100100
```java
@@ -118,7 +118,6 @@ public class CacheController {
118118
System.out.println(productInfo);
119119
return "success";
120120
}
121-
122121
}
123122
```
124123

docs/high-availability/hystrix-introduction.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Hystrix 可以对其进行资源隔离,比如限制服务 B 只有 40 个线
4141
- 阻止任何一个依赖服务耗尽所有的资源,比如 tomcat 中的所有线程资源。
4242
- 避免请求排队和积压,采用限流和 `fail fast` 来控制故障。
4343
- 提供 fallback 降级机制来应对故障。
44-
- 使用资源隔离技术,比如 `bulkhead`(舱壁隔离技术)、`swimlane`(泳道技术)、`circuit breaker`短路技术)来限制任何一个依赖服务的故障的影响。
44+
- 使用资源隔离技术,比如 `bulkhead`(舱壁隔离技术)、`swimlane`(泳道技术)、`circuit breaker`断路技术)来限制任何一个依赖服务的故障的影响。
4545
- 通过近实时的统计/监控/报警功能,来提高故障发现的速度。
4646
- 通过近实时的属性和配置**热修改**功能,来提高故障处理和恢复的速度。
4747
- 保护依赖服务调用的所有故障情况,而不仅仅只是网络故障情况。

0 commit comments

Comments
 (0)