Java-Interview-Advanced/docs/high-availability/hystrix-circuit-breaker.md
2020-04-25 19:55:58 +08:00

181 lines
7.3 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 深入 Hystrix 断路器执行原理
### RequestVolumeThreshold
```java
HystrixCommandProperties.Setter()
.withCircuitBreakerRequestVolumeThreshold(int)
```
表示在滑动窗口中,至少有多少个请求,才可能触发断路。
Hystrix 经过断路器的流量超过了一定的阈值,才有可能触发断路。比如说,要求在 10s 内经过断路器的流量必须达到 20 个,而实际经过断路器的流量才 10 个,那么根本不会去判断要不要断路。
### ErrorThresholdPercentage
```java
HystrixCommandProperties.Setter()
.withCircuitBreakerErrorThresholdPercentage(int)
```
表示异常比例达到多少,才会触发断路,默认值是 50(%)。
如果断路器统计到的异常调用的占比超过了一定的阈值,比如说在 10s 内,经过断路器的流量达到了 30 个,同时其中异常访问的数量也达到了一定的比例,比如 60% 的请求都是异常(报错 / 超时 / reject就会开启断路。
### SleepWindowInMilliseconds
```java
HystrixCommandProperties.Setter()
.withCircuitBreakerSleepWindowInMilliseconds(int)
```
断路开启,也就是由 close 转换到 open 状态close -> open。那么之后在 `SleepWindowInMilliseconds` 时间内,所有经过该断路器的请求全部都会被断路,不调用后端服务,直接走 fallback 降级机制。
而在该参数时间过后,断路器会变为 `half-open` 半开闭状态,尝试让一条请求经过断路器,看能不能正常调用。如果调用成功了,那么就自动恢复,断路器转为 close 状态。
### Enabled
```java
HystrixCommandProperties.Setter()
.withCircuitBreakerEnabled(boolean)
```
控制是否允许断路器工作,包括跟踪依赖服务调用的健康状况,以及对异常情况过多时是否允许触发断路。默认值是 `true`
### ForceOpen
```java
HystrixCommandProperties.Setter()
.withCircuitBreakerForceOpen(boolean)
```
如果设置为 true 的话,直接强迫打开断路器,相当于是手动断路了,手动降级,默认值是 `false`
### ForceClosed
```java
HystrixCommandProperties.Setter()
.withCircuitBreakerForceClosed(boolean)
```
如果设置为 true直接强迫关闭断路器相当于手动停止断路了手动升级默认值是 `false`
## 实例 Demo
### HystrixCommand 配置参数
在 GetProductInfoCommand 中配置 Setter 断路器相关参数。
- 滑动窗口中,最少 20 个请求,才可能触发断路。
- 异常比例达到 40% 时,才触发断路。
- 断路后 3000ms 内,所有请求都被 reject直接走 fallback 降级,不会调用 run() 方法。3000ms 过后,变为 half-open 状态。
run() 方法中,我们判断一下 productId 是否为 -1是的话直接抛出异常。这么写我们之后测试的时候就可以传入 productId=-1**模拟服务执行异常**了。
在降级逻辑中,我们直接给它返回降级商品就好了。
```java
public class GetProductInfoCommand extends HystrixCommand<ProductInfo> {
private Long productId;
private static final HystrixCommandKey KEY = HystrixCommandKey.Factory.asKey("GetProductInfoCommand");
public GetProductInfoCommand(Long productId) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductInfoService"))
.andCommandKey(KEY)
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
// 是否允许断路器工作
.withCircuitBreakerEnabled(true)
// 滑动窗口中,最少有多少个请求,才可能触发断路
.withCircuitBreakerRequestVolumeThreshold(20)
// 异常比例达到多少才触发断路默认50%
.withCircuitBreakerErrorThresholdPercentage(40)
// 断路后多少时间内直接reject请求之后进入half-open状态默认5000ms
.withCircuitBreakerSleepWindowInMilliseconds(3000)));
this.productId = productId;
}
@Override
protected ProductInfo run() throws Exception {
System.out.println("调用接口查询商品数据productId=" + productId);
if (productId == -1L) {
throw new Exception();
}
String url = "http://localhost:8081/getProductInfo?productId=" + productId;
String response = HttpClientUtils.sendGetRequest(url);
return JSONObject.parseObject(response, ProductInfo.class);
}
@Override
protected ProductInfo getFallback() {
ProductInfo productInfo = new ProductInfo();
productInfo.setName("降级商品");
return productInfo;
}
}
```
### 断路测试类
我们在测试类中,前 30 次请求,传入 productId=-1然后休眠 3s之后 70 次请求,传入 productId=1。
```java
@SpringBootTest
@RunWith(SpringRunner.class)
public class CircuitBreakerTest {
@Test
public void testCircuitBreaker() {
String baseURL = "http://localhost:8080/getProductInfo?productId=";
for (int i = 0; i < 30; ++i) {
// 传入-1会抛出异常然后走降级逻辑
HttpClientUtils.sendGetRequest(baseURL + "-1");
}
TimeUtils.sleep(3);
System.out.println("After sleeping...");
for (int i = 31; i < 100; ++i) {
// 传入1走服务正常调用
HttpClientUtils.sendGetRequest(baseURL + "1");
}
}
}
```
### 测试结果
测试结果,我们可以明显看出系统断路与恢复的整个过程。
```c
调用接口查询商品数据,productId=-1
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)
// ...
// 这里重复打印了 20 次上面的结果
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)
// ...
// 这里重复打印了 8 次上面的结果
// 休眠 3s 后
调用接口查询商品数据,productId=1
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)
// ...
// 这里重复打印了 69 次上面的结果
```
前 30 次请求,我们传入的 productId 为 -1所以服务执行过程中会抛出异常。我们设置了最少 20 次请求通过断路器并且异常比例超出 40% 就触发断路。因此执行了 21 次接口调用每次都抛异常并且走降级21 次过后,断路器就被打开了。
之后的 9 次请求,都不会执行 run() 方法,也就不会打印以下信息。
```c
调用接口查询商品数据,productId=-1
```
而是直接走降级逻辑,调用 getFallback() 执行。
休眠了 3s 后,我们在之后的 70 次请求中,都传入 productId 为 1。由于我们前面设置了 3000ms 过后断路器变为 `half-open` 状态。因此 Hystrix 会尝试执行请求,发现成功了,那么断路器关闭,之后的所有请求也都能正常调用了。