|
| 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 会尝试执行请求,发现成功了,那么断路器关闭,之后的所有请求也都能正常调用了。 |
0 commit comments