最近小主看到不少公衆號都在發佈Hystrix停更的文章,spring cloud體系的使用者和擁護者一片哀嚎,實際上,spring做爲Java最大的家族,根本不須要擔憂其中一兩個零件的廢棄,Hystrix的停更,只會催生更多或者更好的零件來替代它,所以,咱們須要作的是:**知道Hystrix是幹嗎,怎麼用的,這樣要找替代者就易於反掌了。java
文章提綱:redis
- 爲何須要Hystrix?
- Hystrix如何解決依賴隔離
- 如何使用Hystrix
在大中型分佈式系統中,一般系統不少依賴(HTTP,hession,Netty,Dubbo等),以下圖:
spring
以下圖:QPS爲50的依賴 I 出現不可用,可是其餘依賴仍然可用.
編程
1例如:一個依賴30個SOA服務的系統,每一個服務99.99%可用。
299.99%的30次方 ≈ 99.7%
30.3% 意味着一億次請求 會有 3,000,00次失敗
4換算成時間大約每個月有2個小時服務不穩定.
5隨着服務依賴數量的變多,服務不穩定的機率會成指數性提升.
複製代碼
解決問題方案:對依賴作隔離,Hystrix就是處理依賴隔離的框架,同時也是能夠幫咱們作依賴服務的治理和監控.
Netflix 公司開發併成功使用Hystrix,使用規模以下:緩存
1he Netflix API processes 10+ billion HystrixCommand executions per day using thread isolation.
2Each API instance has 40+ thread-pools with 5-20 threads in each (most are set to 10).
3
複製代碼
Hystrix使用命令模式HystrixCommand(Command)包裝依賴調用邏輯,每一個命令在單獨線程中/信號受權下執行。服務器
可配置依賴調用超時時間,超時時間通常設爲比99.5%平均時間略高便可.當調用超時時,直接返回或執行fallback邏輯。網絡
爲每一個依賴提供一個小的線程池(或信號),若是線程池已滿調用將被當即拒絕,默認不採用排隊.加速失敗斷定時間。架構
依賴調用結果分:成功,失敗(拋出異常),超時,線程拒絕,短路。 請求失敗(異常,拒絕,超時,短路)時執行fallback(降級)邏輯。併發
提供熔斷器組件,能夠自動運行或手動調用,中止當前依賴一段時間(10秒),熔斷器默認錯誤率閾值爲50%,超過將自動運行。app
提供近實時依賴的統計和監控
Hystrix依賴的隔離架構,以下圖:
- 使用maven引入Hystrix依賴
1<!-- 依賴版本 -->
2<hystrix.version>1.3.16</hystrix.version>
3<hystrix-metrics-event-stream.version>1.1.2</hystrix-metrics-event-stream.version>
4
5<dependency>
6 <groupId>com.netflix.hystrix</groupId>
7 <artifactId>hystrix-core</artifactId>
8 <version>${hystrix.version}</version>
9 </dependency>
10 <dependency>
11 <groupId>com.netflix.hystrix</groupId>
12 <artifactId>hystrix-metrics-event-stream</artifactId>
13 <version>${hystrix-metrics-event-stream.version}</version>
14 </dependency>
15<!-- 倉庫地址 -->
16<repository>
17 <id>nexus</id>
18 <name>local private nexus</name>
19 <url>http://maven.oschina.net/content/groups/public/</url>
20 <releases>
21 <enabled>true</enabled>
22 </releases>
23 <snapshots>
24 <enabled>false</enabled>
25 </snapshots>
26</repository>
複製代碼
- 使用命令模式封裝依賴邏輯
1public class HelloWorldCommand extends HystrixCommand<String> {
2 private final String name;
3 public HelloWorldCommand(String name) {
4 //最少配置:指定命令組名(CommandGroup)
5 super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
6 this.name = name;
7 }
8 @Override
9 protected String run() {
10 // 依賴邏輯封裝在run()方法中
11 return "Hello " + name +" thread:" + Thread.currentThread().getName();
12 }
13 //調用實例
14 public static void main(String[] args) throws Exception{
15 //每一個Command對象只能調用一次,不能夠重複調用,
16 //重複調用對應異常信息:This instance can only be executed once. Please instantiate a new instance.
17 HelloWorldCommand helloWorldCommand = new HelloWorldCommand("Synchronous-hystrix");
18 //使用execute()同步調用代碼,效果等同於:helloWorldCommand.queue().get();
19 String result = helloWorldCommand.execute();
20 System.out.println("result=" + result);
21
22 helloWorldCommand = new HelloWorldCommand("Asynchronous-hystrix");
23 //異步調用,可自由控制獲取結果時機,
24 Future<String> future = helloWorldCommand.queue();
25 //get操做不能超過command定義的超時時間,默認:1秒
26 result = future.get(100, TimeUnit.MILLISECONDS);
27 System.out.println("result=" + result);
28 System.out.println("mainThread=" + Thread.currentThread().getName());
29 }
30
31}
32 //運行結果: run()方法在不一樣的線程下執行
33 // result=Hello Synchronous-hystrix thread:hystrix-HelloWorldGroup-1
34 // result=Hello Asynchronous-hystrix thread:hystrix-HelloWorldGroup-2
35 // mainThread=main
複製代碼
note:異步調用使用 command.queue()get(timeout, TimeUnit.MILLISECONDS);同步調用使用command.execute() 等同於 command.queue().get();
- 註冊異步事件回調執行
1//註冊觀察者事件攔截
2Observable<String> fs = new HelloWorldCommand("World").observe();
3//註冊結果回調事件
4fs.subscribe(new Action1<String>() {
5 @Override
6 public void call(String result) {
7 //執行結果處理,result 爲HelloWorldCommand返回的結果
8 //用戶對結果作二次處理.
9 }
10});
11//註冊完整執行生命週期事件
12fs.subscribe(new Observer<String>() {
13 @Override
14 public void onCompleted() {
15 // onNext/onError完成以後最後回調
16 System.out.println("execute onCompleted");
17 }
18 @Override
19 public void onError(Throwable e) {
20 // 當產生異常時回調
21 System.out.println("onError " + e.getMessage());
22 e.printStackTrace();
23 }
24 @Override
25 public void onNext(String v) {
26 // 獲取結果後回調
27 System.out.println("onNext: " + v);
28 }
29 });
30/* 運行結果
31call execute result=Hello observe-hystrix thread:hystrix-HelloWorldGroup-3
32onNext: Hello observe-hystrix thread:hystrix-HelloWorldGroup-3
33execute onCompleted
34*/
複製代碼
- 使用Fallback() 提供降級策略
1//重載HystrixCommand 的getFallback方法實現邏輯
2public class HelloWorldCommand extends HystrixCommand<String> {
3 private final String name;
4 public HelloWorldCommand(String name) {
5 super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloWorldGroup"))
6 /* 配置依賴超時時間,500毫秒*/
7 .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionIsolationThreadTimeoutInMilliseconds(500)));
8 this.name = name;
9 }
10 @Override
11 protected String getFallback() {
12 return "exeucute Falled";
13 }
14 @Override
15 protected String run() throws Exception {
16 //sleep 1 秒,調用會超時
17 TimeUnit.MILLISECONDS.sleep(1000);
18 return "Hello " + name +" thread:" + Thread.currentThread().getName();
19 }
20 public static void main(String[] args) throws Exception{
21 HelloWorldCommand command = new HelloWorldCommand("test-Fallback");
22 String result = command.execute();
23 }
24}
25/* 運行結果:getFallback() 調用運行
26getFallback executed
27*/
複製代碼
NOTE: 除了HystrixBadRequestException異常以外,全部從run()方法拋出的異常都算做失敗,並觸發降級getFallback()和斷路器邏輯。
1 HystrixBadRequestException用在非法參數或非系統故障異常等不該觸發回退邏輯的場景。
複製代碼
- 依賴命名:CommandKey
1public HelloWorldCommand(String name) {
2 super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
3 /* HystrixCommandKey工廠定義依賴名稱 */
4 .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")));
5 this.name = name;
6 }
複製代碼
NOTE: 每一個CommandKey表明一個依賴抽象,相同的依賴要使用相同的CommandKey名稱。依賴隔離的根本就是對相同CommandKey的依賴作隔離.
- 依賴分組:CommandGroup
命令分組用於對依賴操做分組,便於統計,彙總等.
1//使用HystrixCommandGroupKey工廠定義
2public HelloWorldCommand(String name) {
3 Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloWorldGroup"))
4}
複製代碼
NOTE: CommandGroup是每一個命令最少配置的必選參數,在不指定ThreadPoolKey的狀況下,字面值用於對不一樣依賴的線程池/信號區分.
- 線程池/信號:ThreadPoolKey
1public HelloWorldCommand(String name) {
2 super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
3 .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"))
4 /* 使用HystrixThreadPoolKey工廠定義線程池名稱*/
5 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")));
6 this.name = name;
7 }
複製代碼
NOTE: 當對同一業務依賴作隔離時使用CommandGroup作區分,可是對同一依賴的不一樣遠程調用如(一個是redis 一個是http),可使用HystrixThreadPoolKey作隔離區分.
1 最然在業務上都是相同的組,可是須要在資源上作隔離時,可使用HystrixThreadPoolKey區分.
複製代碼
- 請求緩存 Request-Cache
1public class RequestCacheCommand extends HystrixCommand<String> {
2 private final int id;
3 public RequestCacheCommand( int id) {
4 super(HystrixCommandGroupKey.Factory.asKey("RequestCacheCommand"));
5 this.id = id;
6 }
7 @Override
8 protected String run() throws Exception {
9 System.out.println(Thread.currentThread().getName() + " execute id=" + id);
10 return "executed=" + id;
11 }
12 //重寫getCacheKey方法,實現區分不一樣請求的邏輯
13 @Override
14 protected String getCacheKey() {
15 return String.valueOf(id);
16 }
17
18 public static void main(String[] args){
19 HystrixRequestContext context = HystrixRequestContext.initializeContext();
20 try {
21 RequestCacheCommand command2a = new RequestCacheCommand(2);
22 RequestCacheCommand command2b = new RequestCacheCommand(2);
23 Assert.assertTrue(command2a.execute());
24 //isResponseFromCache斷定是不是在緩存中獲取結果
25 Assert.assertFalse(command2a.isResponseFromCache());
26 Assert.assertTrue(command2b.execute());
27 Assert.assertTrue(command2b.isResponseFromCache());
28 } finally {
29 context.shutdown();
30 }
31 context = HystrixRequestContext.initializeContext();
32 try {
33 RequestCacheCommand command3b = new RequestCacheCommand(2);
34 Assert.assertTrue(command3b.execute());
35 Assert.assertFalse(command3b.isResponseFromCache());
36 } finally {
37 context.shutdown();
38 }
39 }
40}
複製代碼
NOTE:請求緩存可讓(CommandKey/CommandGroup)相同的狀況下,直接共享結果,下降依賴調用次數,在高併發和CacheKey碰撞率高場景下能夠提高性能.
Servlet容器中,能夠直接實用Filter機制Hystrix請求上下文
1public class HystrixRequestContextServletFilter implements Filter {
2 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
3 throws IOException, ServletException {
4 HystrixRequestContext context = HystrixRequestContext.initializeContext();
5 try {
6 chain.doFilter(request, response);
7 } finally {
8 context.shutdown();
9 }
10 }
11}
12<filter>
13 <display-name>HystrixRequestContextServletFilter</display-name>
14 <filter-name>HystrixRequestContextServletFilter</filter-name>
15 <filter-class>com.netflix.hystrix.contrib.requestservlet.HystrixRequestContextServletFilter</filter-class>
16 </filter>
17 <filter-mapping>
18 <filter-name>HystrixRequestContextServletFilter</filter-name>
19 <url-pattern>/*</url-pattern>
20 </filter-mapping>
21
複製代碼
- 信號量隔離:SEMAPHORE
隔離本地代碼或可快速返回遠程調用(如memcached,redis)能夠直接使用信號量隔離,下降線程隔離開銷.
1public class HelloWorldCommand extends HystrixCommand<String> {
2 private final String name;
3 public HelloWorldCommand(String name) {
4 super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloWorldGroup"))
5 /* 配置信號量隔離方式,默認採用線程池隔離 */
6 .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)));
7 this.name = name;
8 }
9 @Override
10 protected String run() throws Exception {
11 return "HystrixThread:" + Thread.currentThread().getName();
12 }
13 public static void main(String[] args) throws Exception{
14 HelloWorldCommand command = new HelloWorldCommand("semaphore");
15 String result = command.execute();
16 System.out.println(result);
17 System.out.println("MainThread:" + Thread.currentThread().getName());
18 }
19}
20/** 運行結果
21 HystrixThread:main
22 MainThread:main
23*/
複製代碼
- fallback降級邏輯命令嵌套
1public class CommandWithFallbackViaNetwork extends HystrixCommand<String> {
2 private final int id;
3
4 protected CommandWithFallbackViaNetwork(int id) {
5 super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceX"))
6 .andCommandKey(HystrixCommandKey.Factory.asKey("GetValueCommand")));
7 this.id = id;
8 }
9
10 @Override
11 protected String run() {
12 // RemoteService.getValue(id);
13 throw new RuntimeException("force failure for example");
14 }
15
16 @Override
17 protected String getFallback() {
18 return new FallbackViaNetwork(id).execute();
19 }
20
21 private static class FallbackViaNetwork extends HystrixCommand<String> {
22 private final int id;
23 public FallbackViaNetwork(int id) {
24 super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceX"))
25 .andCommandKey(HystrixCommandKey.Factory.asKey("GetValueFallbackCommand"))
26 // 使用不一樣的線程池作隔離,防止上層線程池跑滿,影響降級邏輯.
27 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("RemoteServiceXFallback")));
28 this.id = id;
29 }
30 @Override
31 protected String run() {
32 MemCacheClient.getValue(id);
33 }
34
35 @Override
36 protected String getFallback() {
37 return null;
38 }
39 }
40}
複製代碼
NOTE:依賴調用和降級調用使用不一樣的線程池作隔離,防止上層線程池跑滿,影響二級降級邏輯調用.
- 顯示調用fallback邏輯,用於特殊業務處理
1public class CommandFacadeWithPrimarySecondary extends HystrixCommand<String> {
2 private final static DynamicBooleanProperty usePrimary = DynamicPropertyFactory.getInstance().getBooleanProperty("primarySecondary.usePrimary", true);
3 private final int id;
4 public CommandFacadeWithPrimarySecondary(int id) {
5 super(Setter
6 .withGroupKey(HystrixCommandGroupKey.Factory.asKey("SystemX"))
7 .andCommandKey(HystrixCommandKey.Factory.asKey("PrimarySecondaryCommand"))
8 .andCommandPropertiesDefaults(
9 HystrixCommandProperties.Setter()
10 .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)));
11 this.id = id;
12 }
13 @Override
14 protected String run() {
15 if (usePrimary.get()) {
16 return new PrimaryCommand(id).execute();
17 } else {
18 return new SecondaryCommand(id).execute();
19 }
20 }
21 @Override
22 protected String getFallback() {
23 return "static-fallback-" + id;
24 }
25 @Override
26 protected String getCacheKey() {
27 return String.valueOf(id);
28 }
29 private static class PrimaryCommand extends HystrixCommand<String> {
30 private final int id;
31 private PrimaryCommand(int id) {
32 super(Setter
33 .withGroupKey(HystrixCommandGroupKey.Factory.asKey("SystemX"))
34 .andCommandKey(HystrixCommandKey.Factory.asKey("PrimaryCommand"))
35 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("PrimaryCommand"))
36 .andCommandPropertiesDefaults(
37 // we default to a 600ms timeout for primary
38 HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(600)));
39 this.id = id;
40 }
41 @Override
42 protected String run() {
43 // perform expensive 'primary' service call
44 return "responseFromPrimary-" + id;
45 }
46 }
47 private static class SecondaryCommand extends HystrixCommand<String> {
48 private final int id;
49 private SecondaryCommand(int id) {
50 super(Setter
51 .withGroupKey(HystrixCommandGroupKey.Factory.asKey("SystemX"))
52 .andCommandKey(HystrixCommandKey.Factory.asKey("SecondaryCommand"))
53 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("SecondaryCommand"))
54 .andCommandPropertiesDefaults(
55 // we default to a 100ms timeout for secondary
56 HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(100)));
57 this.id = id;
58 }
59 @Override
60 protected String run() {
61 // perform fast 'secondary' service call
62 return "responseFromSecondary-" + id;
63 }
64 }
65 public static class UnitTest {
66 @Test
67 public void testPrimary() {
68 HystrixRequestContext context = HystrixRequestContext.initializeContext();
69 try {
70 ConfigurationManager.getConfigInstance().setProperty("primarySecondary.usePrimary", true);
71 assertEquals("responseFromPrimary-20", new CommandFacadeWithPrimarySecondary(20).execute());
72 } finally {
73 context.shutdown();
74 ConfigurationManager.getConfigInstance().clear();
75 }
76 }
77 @Test
78 public void testSecondary() {
79 HystrixRequestContext context = HystrixRequestContext.initializeContext();
80 try {
81 ConfigurationManager.getConfigInstance().setProperty("primarySecondary.usePrimary", false);
82 assertEquals("responseFromSecondary-20", new CommandFacadeWithPrimarySecondary(20).execute());
83 } finally {
84 context.shutdown();
85 ConfigurationManager.getConfigInstance().clear();
86 }
87 }
88 }
89}
複製代碼
NOTE:顯示調用降級適用於特殊需求的場景,fallback用於業務處理,fallback再也不承擔降級職責,建議慎重使用,會形成監控統計換亂等問題.
- 命令調用合併:HystrixCollapser
命令調用合併容許多個請求合併到一個線程/信號下批量執行。
執行流程圖以下:
1public class CommandCollapserGetValueForKey extends HystrixCollapser<List<String>, String, Integer> {
2 private final Integer key;
3 public CommandCollapserGetValueForKey(Integer key) {
4 this.key = key;
5 }
6 @Override
7 public Integer getRequestArgument() {
8 return key;
9 }
10 @Override
11 protected HystrixCommand<List<String>> createCommand(final Collection<CollapsedRequest<String, Integer>> requests) {
12 //建立返回command對象
13 return new BatchCommand(requests);
14 }
15 @Override
16 protected void mapResponseToRequests(List<String> batchResponse, Collection<CollapsedRequest<String, Integer>> requests) {
17 int count = 0;
18 for (CollapsedRequest<String, Integer> request : requests) {
19 //手動匹配請求和響應
20 request.setResponse(batchResponse.get(count++));
21 }
22 }
23 private static final class BatchCommand extends HystrixCommand<List<String>> {
24 private final Collection<CollapsedRequest<String, Integer>> requests;
25 private BatchCommand(Collection<CollapsedRequest<String, Integer>> requests) {
26 super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
27 .andCommandKey(HystrixCommandKey.Factory.asKey("GetValueForKey")));
28 this.requests = requests;
29 }
30 @Override
31 protected List<String> run() {
32 ArrayList<String> response = new ArrayList<String>();
33 for (CollapsedRequest<String, Integer> request : requests) {
34 response.add("ValueForKey: " + request.getArgument());
35 }
36 return response;
37 }
38 }
39 public static class UnitTest {
40 HystrixRequestContext context = HystrixRequestContext.initializeContext();
41 try {
42 Future<String> f1 = new CommandCollapserGetValueForKey(1).queue();
43 Future<String> f2 = new CommandCollapserGetValueForKey(2).queue();
44 Future<String> f3 = new CommandCollapserGetValueForKey(3).queue();
45 Future<String> f4 = new CommandCollapserGetValueForKey(4).queue();
46 assertEquals("ValueForKey: 1", f1.get());
47 assertEquals("ValueForKey: 2", f2.get());
48 assertEquals("ValueForKey: 3", f3.get());
49 assertEquals("ValueForKey: 4", f4.get());
50 assertEquals(1, HystrixRequestLog.getCurrentRequest().getExecutedCommands().size());
51 HystrixCommand<?> command = HystrixRequestLog.getCurrentRequest().getExecutedCommands().toArray(new HystrixCommand<?>[1])[0];
52 assertEquals("GetValueForKey", command.getCommandKey().name());
53 assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED));
54 assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS));
55 } finally {
56 context.shutdown();
57 }
58 }
59}
複製代碼
NOTE:使用場景:HystrixCollapser用於對多個相同業務的請求合併到一個線程甚至能夠合併到一個鏈接中執行,下降線程交互次和IO數,但必須保證他們屬於同一依賴.
原文出自:
http://hot66hot.iteye.com/blog/2155036
以爲本文對你有幫助?請分享給更多人 關注「編程無界」,提高裝逼技能