Hystrix 服务资源隔离

在一个服务化的系统中,有时候我们会在同一个应用中对外提供多个服务,例如,我们的商品详情应用既提供价格查询也提供库存查询功能,如下

下面程序模拟用户不停的访问两个服务,而库存服务有 50% 的几率会超时(程序中用 sleep 1小时模拟)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;

public class GoodsDetailService {
private ExecutorService threadPool = Executors.newFixedThreadPool(10);
private AtomicInteger badCount = new AtomicInteger();

public int getStockCount(String skuId) {

Future<Integer> future = threadPool.submit(() -> {
System.out.println("开始查询 " + skuId + " 的库存信息");
long waitMills = 20;
if (Math.random() > 0.5) {
//50%的请求耗时较长
System.out.println("---->要坏了,这是第 " + badCount.incrementAndGet() + " 个了");
waitMills = 600000;
}
try {
Thread.sleep(waitMills);
} catch (InterruptedException e) {
}
System.out.println("结束查询 " + skuId + " 的库存信息");
return 100;
});

try {
return future.get();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}

public Double getPrice(String skuId) {
Future<Double> future = threadPool.submit(() -> {
System.out.println("开始查询 " + skuId + " 的价格信息");
try {
Thread.sleep(30);
} catch (InterruptedException e) {
}
System.out.println("结束查询 " + skuId + " 的价格信息");
return 99.99d;
});
try {
return future.get();
} catch (Exception e) {
e.printStackTrace();
}
return 0d;
}

public static void main(String[] args) {
GoodsDetailService goodsDetailService = new GoodsDetailService();
ExecutorService executorService = Executors.newFixedThreadPool(30);
int i = 0;
while (true) {
String skuId = String.format("sku-%03d", ++i);
System.out.println("第 " + i + " 个用户来查询 " + skuId + " 的信息了");
executorService.execute(() -> goodsDetailService.getStockCount(skuId));
executorService.execute(() -> goodsDetailService.getPrice(skuId));
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
}
```

程序运行结果

```console
...
开始查询 sku-023 的价格信息
结束查询 sku-023 的价格信息
24 个用户来查询 sku-024 的信息了
开始查询 sku-024 的库存信息
结束查询 sku-024 的库存信息
开始查询 sku-024 的价格信息
结束查询 sku-024 的价格信息
25 个用户来查询 sku-025 的信息了
开始查询 sku-025 的库存信息
---->要坏了,这是第 10 个了
26 个用户来查询 sku-026 的信息了
27 个用户来查询 sku-027 的信息了
28 个用户来查询 sku-028 的信息了
```

从结果可以看出,在第 25 个用户访问时,库存服务已有 10 次 超时,占完了所有线程,价格服务一些正常,却因没有线程可用导致不可访问,下面我们对程序进行优化,将两个服务的线程池隔离开

![](https://s.niuhp.com/blog/hystrix/res-iso-2.png)

主要对原代码做如下修改:

1. 原线程池 `private ExecutorService threadPool = Executors.newFixedThreadPool(10);` 改成两个 `private ExecutorService stockPool = Executors.newFixedThreadPool(5);` 和 `private ExecutorService pricePool = Executors.newFixedThreadPool(5);`
2. 两个服务分别使用自己的线程池

```java
public int getStockCount(String skuId) {
Future<Integer> future = stockPool.submit(() -> {
...
return 0;
}
```

```java
public Double getPrice(String skuId) {
Future<Double> future = pricePool.submit(() -> {
...
return 0d;
}
```

优化完再次运行程序,通过程序结果可以看出,在第 14 个用户访问时,库存服务已有 5 次 超时,其线程已消耗殆尽,此时用户已经不能正常访问库存服务了,令人欣喜的是,得益于我们已经对服务资源做了隔离,价格服务并没有受到影响,仍然可以正常提供服务。

```console
...
14 个用户来查询 sku-014 的信息了
开始查询 sku-014 的库存信息
---->要坏了,这是第 5 个了
开始查询 sku-014 的价格信息
结束查询 sku-014 的价格信息
15 个用户来查询 sku-015 的信息了
开始查询 sku-015 的价格信息
结束查询 sku-015 的价格信息
16 个用户来查询 sku-016 的信息了
开始查询 sku-016 的价格信息
结束查询 sku-016 的价格信息
...