1 public class CommandHelloWorld extends HystrixCommand<String> {
2
3 private final String name;
4
5 public CommandHelloWorld(String name) {
6 super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) //必須
7 .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
8 .withExecutionTimeoutInMilliseconds(500)) //超時時間
9 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ExampleGroup-pool")) //可選,默認 使用 this.getClass().getSimpleName();
10 .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(4)));
11
12 this.name = name;
13 }
14
15 @Override
16 protected String run() throws InterruptedException {
17 System.out.println("running");
18 TimeUnit.MILLISECONDS.sleep(1000);
19 return "Hello " + name + "!";
20 }
21
22 @Override
23 protected String getFallback() {
24 return "Hello "+"Fallback";
25 }
26 }
27
28 @Test
29 public void fallbackTest(){
30 assertEquals("Hello Fallback",new CommandHelloWorld("World").execute());
31 }
Q2:什麼狀況下會觸發fallback?
簡單來講,就是run方法拋異常,超時,線程/信號量reject、短路
如下爲測試的主程序:
1 public class CommandHelloFailure extends HystrixCommand<String> {
2
3 private final String name;
4
5 public CommandHelloFailure(String name) {
6 super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) //必須
7 .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
8 .withExecutionTimeoutInMilliseconds(1000)) //超時時間
9 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ExampleGroup-pool"))
10 .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(3)));
11
12 this.name = name;
13 }
14
15 @Override
16 protected String run() throws InterruptedException {
17 String theadName = this.getThreadPoolKey().name();
18 String cmdKey=this.getThreadPoolKey().name();
19 System.out.println("running begin , threadPool="+theadName+" cmdKey="+cmdKey+" name="+name);
20
21 if("Exception".equals(name)) {
22 throw new RuntimeException("this command always fails");
23 }else if("Timeout".equals(name)){
24 TimeUnit.SECONDS.sleep(2);
25 }else if("Reject".equals(name)){
26 TimeUnit.MILLISECONDS.sleep(800);
27 }
28 System.out.println(" run end");
29
30 return "Hello " + name + "!";
31 }
32
33 @Override
34 protected String getFallback() {
35 StringBuilder sb = new StringBuilder("running fallback");
36 boolean isRejected = isResponseRejected();
37 boolean isException = isFailedExecution();
38 boolean isTimeout= isResponseTimedOut();
39 boolean isCircut = isCircuitBreakerOpen();
40
41 sb.append(", isRejected:").append(isRejected);
42 sb.append(", isException:"+isException);
43 if(isException){
44 sb.append(" msg=").append(getExecutionException().getMessage());
45 }
46 sb.append(", isTimeout: "+isTimeout);
47 sb.append(", isCircut:"+isCircut);
48
49 sb.append(", group:").append(this.getCommandGroup().name());
50 sb.append(", threadpool:").append(getThreadPoolKey().name());
51 System.out.println(sb.toString());
52
53 String msg="Hello Failure " + name + "!";
54 return msg;
55 }
56 }
FAILURE
測試由異常致使的fallback
1 @Test
2 public void expTest() {
3 assertEquals("Hello Failure Exception!", new CommandHelloFailure("Exception").execute());
4 }
5
//控制檯輸出
running begin , threadPool=ExampleGroup-pool cmdKey=ExampleGroup-pool name=Exception
running fallback, isRejected:false, isException:true msg=this command always fails, isTimeout: false, isCircut:false, group:ExampleGroup, threadpool:ExampleGroup-pool
TIMEOUT
測試有超時致使的fallback
@Test
public void timeOutTest() {
assertEquals("Hello Failure Timeout!", new CommandHelloFailure("Timeout").execute());
}
//控制檯輸出
running begin , threadPool=ExampleGroup-pool cmdKey=ExampleGroup-pool name=Timeout
running fallback, isRejected:false, isException:false, isTimeout: true, isCircut:false, group:ExampleGroup, threadpool:ExampleGroup-pool
THREAD_POOL_REJECTED
併發執行的任務數超過線程池和隊列之和會被reject,致使fallback
1 @Test
2 public void rejectTest() throws InterruptedException {
3 int count = 5;
4 while (count-- > 0){
5 new CommandHelloFailure("Reject").queue();
6 TimeUnit.MILLISECONDS.sleep(100);
7 }
8 }
//控制檯輸出
running begin , threadPool=ExampleGroup-pool cmdKey=ExampleGroup-pool name=Reject
running begin , threadPool=ExampleGroup-pool cmdKey=ExampleGroup-pool name=Reject
running begin , threadPool=ExampleGroup-pool cmdKey=ExampleGroup-pool name=Reject
running fallback, isRejected:true, isException:false, isTimeout: false, isCircut:false, group:ExampleGroup, threadpool:ExampleGroup-pool
running fallback, isRejected:true, isException:false, isTimeout: false, isCircut:false, group:ExampleGroup, threadpool:ExampleGroup-pool
SEMAPHORE_REJECTED 與 THREAD_POOL_REJECTED 相似,再也不演示
SHORT_CIRCUITED
在必定時間內,用戶請求超過必定的比例失敗時(timeout, failure, reject),斷路器就會打開;短路器打開後全部請求直接走fallback
參數設置
通常配置以下:
1 Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) //必須
2 .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
3 .withExecutionTimeoutInMilliseconds(50)//超時時間
4 .withCircuitBreakerRequestVolumeThreshold(5)
5 .withCircuitBreakerSleepWindowInMilliseconds(1000)
6 .withCircuitBreakerErrorThresholdPercentage(50))
7 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ExampleGroup-pool")) //可選,默認 使用 this.getClass().getSimpleName();
8 .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(4));
以上配置的含義是: 在10s內,若是請求在5個及以上,且有50%失敗的狀況下,開啓斷路器;斷路器開啓1000ms後嘗試關閉
短路器的工做機制,引用自官方文檔:
The precise way that the circuit opening and closing occurs is as follows:
Assuming the volume across a circuit meets a certain threshold (HystrixCommandProperties.circuitBreakerRequestVolumeThreshold())...
And assuming that the error percentage exceeds the threshold error percentage (HystrixCommandProperties.circuitBreakerErrorThresholdPercentage())...
Then the circuit-breaker transitions from CLOSED to OPEN.
While it is open, it short-circuits all requests made against that circuit-breaker.
After some amount of time (HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()), the next single request is let through (this is the HALF-OPEN state). If the request fails, the circuit-breaker returns to the OPEN state for the duration of the sleep window. If the request succeeds, the circuit-breaker transitions to CLOSED and the logic in 1. takes over again.
Q3:fallback時咱們應該怎麼辦?
通常有如下幾種策略:
一、不實現getFallback方法:依賴調用失敗時直接拋出異常
二、實現getFallback方法,返回默認值:這是一種常見的策略
三、實現getFallback方法,走降級方案
此外,生產環境中,fallback時,通常須要打點記錄
請求合併
簡單來講,就是將一段時間內的屢次請求合併爲一次請求,經常使用於網絡IO中,能減小IO次數,缺點是增長平均延遲

如下是測試代碼主程序:
1 public class CommandCollapserGetValueForKey extends HystrixCollapser<List<String>, String, Integer> {
2
3 private final Integer key;
4
5 public CommandCollapserGetValueForKey(Integer key) {
6 super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("Collapser"))
7 .andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter()
8 .withMaxRequestsInBatch(3)
9 .withTimerDelayInMilliseconds(10)));
10 this.key = key;
11 }
12
13 @Override
14 public Integer getRequestArgument() {
15 return key;
16 }
17
18 @Override
19 protected HystrixCommand<List<String>> createCommand(final Collection<CollapsedRequest<String, Integer>> requests) {
20 return new BatchCommand(requests);
21 }
22
23 @Override
24 protected void mapResponseToRequests(List<String> batchResponse, Collection<CollapsedRequest<String, Integer>> requests) {
25 int count = 0;
26 for (CollapsedRequest<String, Integer> request : requests) {
27 request.setResponse(batchResponse.get(count++));
28 }
29 }
30
31 private static final class BatchCommand extends HystrixCommand<List<String>> {
32 private final Collection<CollapsedRequest<String, Integer>> requests;
33
34 private BatchCommand(Collection<CollapsedRequest<String, Integer>> requests) {
35 super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
36 .andCommandKey(HystrixCommandKey.Factory.asKey("GetValueForKey")));
37 this.requests = requests;
38 }
39
40 @Override
41 protected List<String> run() {
42 System.out.println("BatchCommand run "+requests.size());
43 ArrayList<String> response = new ArrayList<String>();
44 for (CollapsedRequest<String, Integer> request : requests) {
45 // artificial response for each argument received in the batch
46 response.add("ValueForKey: " + request.getArgument());
47 }
48 return response;
49 }
50 }
51 }
52
53
54 @Test
55 public void testCollapser() throws Exception {
56 HystrixRequestContext context = HystrixRequestContext.initializeContext();
57 try {
58 Future<String> f1 = new CommandCollapserGetValueForKey(1).queue();
59 Future<String> f2 = new CommandCollapserGetValueForKey(2).queue();
60 Future<String> f3 = new CommandCollapserGetValueForKey(3).queue();
61 Future<String> f4 = new CommandCollapserGetValueForKey(4).queue();
62
63
64 assertEquals("ValueForKey: 1", f1.get());
65 assertEquals("ValueForKey: 2", f2.get());
66 assertEquals("ValueForKey: 3", f3.get());
67 assertEquals("ValueForKey: 4", f4.get());
68
69 // assert that the batch command 'GetValueForKey' was in fact
70 // executed and that it executed only once
71 assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size());
72 HystrixCommand<?> command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().toArray(new HystrixCommand<?>[1])[0];
73 // assert the command is the one we're expecting
74 assertEquals("GetValueForKey", command.getCommandKey().name());
75 // confirm that it was a COLLAPSED command execution
76 assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED));
77 // and that it was successful
78 assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS));
79 } finally {
80 context.shutdown();
81 }
82 }
83
84 //控制輸出
85 BatchCommand run 3
86 BatchCommand run 1
執行流程:

使用該特性
一、必須繼承HystrixCollapser類,
二、實現如下方法:
getRequestArgument: 返回請求參數對象
createCommand : 返回BatchCommand
mapResponseToRequests:實現Response和Request的映射
三、建立對應的BatchCommand類:批量請求的具體實現
參數配置:
通常配置以下
Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("Collapser"))
.andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter()
.withMaxRequestsInBatch(3)
.withTimerDelayInMilliseconds(5));
請求cache
1 public class CommandUsingRequestCache extends HystrixCommand<Boolean> {
2 private final int value;
3
4 public CommandUsingRequestCache(int value) {
5 super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
6 this.value = value;
7 }
8
9 @Override
10 public Boolean run() {
11 return value == 0 || value % 2 == 0;
12 }
13
14 //使用cache功能,必須實現該方法
15 @Override
16 public String getCacheKey() {
17 return String.valueOf(value);
18 }
19 }
20
21 @Test
22 public void testWithCacheHits() {
23 HystrixRequestContext context = HystrixRequestContext.initializeContext();
24 try {
25 CommandUsingRequestCache command2a = new CommandUsingRequestCache(2);
26 CommandUsingRequestCache command2b = new CommandUsingRequestCache(2);
27
28 assertTrue(command2a.execute());
29 //第一次請求,沒有cache
30 assertFalse(command2a.isResponseFromCache());
31
32 assertTrue(command2b.execute());
33 // 第二次請求,從cache中拿的結果
34 assertTrue(command2b.isResponseFromCache());
35 } finally {
36 context.shutdown();
37 }
38
39 context = HystrixRequestContext.initializeContext();
40 try {
41 CommandUsingRequestCache command3b = new CommandUsingRequestCache(2);
42 assertTrue(command3b.execute());
43 // this is a new request context so this
44 //new了新的 request context後,以前的cache失效
45 assertFalse(command3b.isResponseFromCache());
46 } finally {
47 context.shutdown();
48 }
49 }
Hystrix Context
Global Context
UserRequest Context
使用與監控
一、工程中使用
使用Hystrix很簡單,只須要添加相應依賴便可,以Maven爲例:
1 <!-- hystrix 依賴 -->
2 <dependency>
3 <groupId>com.netflix.hystrix</groupId>
4 <artifactId>hystrix-core</artifactId>
5 <version>1.5.9</version>
6 </dependency>
7 <dependency>
8 <groupId>com.netflix.hystrix</groupId>
9 <artifactId>hystrix-metrics-event-stream</artifactId>
10 <version>1.5.9</version>
11 </dependency>
二、DashBoard使用
web.xml中配置相應的Servlet
1 <servlet>
2 <display-name>HystrixMetricsStreamServlet</display-name>
3 <servlet-name>HystrixMetricsStreamServlet</servlet-name>
4 <servlet-class>com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet</servlet-class>
5 </servlet>
6 <servlet-mapping>
7 <servlet-name>HystrixMetricsStreamServlet</servlet-name>
8 <url-pattern>/hystrix.stream</url-pattern>
9 </servlet-mapping>
下載附件中的war文件和jar文件到任意目錄,執行
java -jar jetty-runner-9.2.10.v20150310.jar --port 8410 hystrix-dashboard-1.5.1.war
而後在瀏覽器中打開:http://localhost:8410/ ,在輸入框中填寫 http://hostname:port/application/hystrix.stream, 點擊 Add Stream ,而後在點擊Monitor Stream, 看到以下圖:

每一個指標對應的含義:

通常來講: Thread-pool Rejections 和Failuress/Exception應該是0,Thread timeouts是個很小的值。
代碼結構
附件
一、啓動腳本 start.sh
二、hystrix-dashboard-1.5.1.war
三、jetty-runner-9.2.10.v20150310.jar