Hystrix 使用与分析

一:为什么需要Hystrix?

在大中型分布式系统中,通常系统很多依赖(HTTP,hession,Netty,Dubbo等),如下图:

 

在高并发访问下,这些依赖的稳定性与否对系统的影响非常大,但是依赖有很多不可控问题:如网络连接缓慢,资源繁忙,暂时不可用,服务脱机等.

如下图:QPS为50的依赖 I 出现不可用,但是其他依赖仍然可用.

 

当依赖I 阻塞时,大多数服务器的线程池就出现阻塞(BLOCK),影响整个线上服务的稳定性.如下图:

 

在复杂的分布式架构的应用程序有很多的依赖,都会不可避免地在某些时候失败。高并发的依赖失败时如果没有隔离措施,当前应用服务就有被拖垮的风险。

 

Java代码  收藏代码

  1. 例如:一个依赖30个SOA服务的系统,每个服务99.99%可用。  
  2. 99.99%的30次方 ≈ 99.7%  
  3. 0.3% 意味着一亿次请求 会有 3,000,00次失败  
  4. 换算成时间大约每月有2个小时服务不稳定.  
  5. 随着服务依赖数量的变多,服务不稳定的概率会成指数性提高.  

 解决问题方案:对依赖做隔离,Hystrix就是处理依赖隔离的框架,同时也是可以帮我们做依赖服务的治理和监控.

 

Netflix 公司开发并成功使用Hystrix,使用规模如下:

 

Java代码  收藏代码

  1. The Netflix API processes 10+ billion HystrixCommand executions per day using thread isolation.   
  2. Each API instance has 40+ thread-pools with 5-20 threads in each (most are set to 10).  

二:Hystrix如何解决依赖隔离

1:Hystrix使用命令模式HystrixCommand(Command)包装依赖调用逻辑,每个命令在单独线程中/信号授权下执行。

2:可配置依赖调用超时时间,超时时间一般设为比99.5%平均时间略高即可.当调用超时时,直接返回或执行fallback逻辑。

3:为每个依赖提供一个小的线程池(或信号),如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间。

4:依赖调用结果分:成功,失败(抛出异常),超时,线程拒绝,短路。 请求失败(异常,拒绝,超时,短路)时执行fallback(降级)逻辑。

5:提供熔断器组件,可以自动运行或手动调用,停止当前依赖一段时间(10秒),熔断器默认错误率阈值为50%,超过将自动运行。

6:提供近实时依赖的统计和监控

Hystrix依赖的隔离架构,如下图:

三:如何使用Hystrix

1:使用maven引入Hystrix依赖

 

Html代码  收藏代码

  1.   
  2. <</span>hystrix.version>1.3.16</</span>hystrix.version>  
  3. <</span>hystrix-metrics-event-stream.version>1.1.2</</span>hystrix-metrics-event-stream.version>   
  4.    
  5. <</span>dependency>  
  6.      <</span>groupId>com.netflix.hystrix</</span>groupId>  
  7.      <</span>artifactId>hystrix-core</</span>artifactId>  
  8.      <</span>version>${hystrix.version}</</span>version>  
  9.  </</span>dependency>  
  10.      <</span>dependency>  
  11.      <</span>groupId>com.netflix.hystrix</</span>groupId>  
  12.      <</span>artifactId>hystrix-metrics-event-stream</</span>artifactId>  
  13.      <</span>version>${hystrix-metrics-event-stream.version}</</span>version>  
  14.  </</span>dependency>  
  15.   
  16. <</span>repository>  
  17.      <</span>id>nexus</</span>id>  
  18.      <</span>name>local private nexus</</span>name>  
  19.      <</span>url>http://maven.oschina.net/content/groups/public/</</span>url>  
  20.      <</span>releases>  
  21.           <</span>enabled>true</</span>enabled>  
  22.      </</span>releases>  
  23.      <</span>snapshots>  
  24.           <</span>enabled>false</</span>enabled>  
  25.      </</span>snapshots>  
  26. </</span>repository>  

2:使用命令模式封装依赖逻辑

 

Java代码  收藏代码

  1. public class HelloWorldCommand extends HystrixCommand {  
  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 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();

3:注册异步事件回调执行

 

Java代码  收藏代码

  1. //注册观察者事件拦截  
  2. Observable fs = new HelloWorldCommand("World").observe();  
  3. //注册结果回调事件  
  4. fs.subscribe(new Action1() {  
  5.     @Override  
  6.     public void call(String result) {  
  7.          //执行结果处理,result 为HelloWorldCommand返回的结果  
  8.         //用户对结果做二次处理.  
  9.     }  
  10. });  
  11. //注册完整执行生命周期事件  
  12. fs.subscribe(new Observer() {  
  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.   

4:使用Fallback() 提供降级策略

 

Java代码  收藏代码

  1. //重载HystrixCommand 的getFallback方法实现逻辑  
  2. public class HelloWorldCommand extends HystrixCommand {  
  3.     private final String name;  
  4.     public HelloWorldCommand(String name) {  
  5.         super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloWorldGroup"))  
  6.                   
  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.   

 

NOTE: 除了HystrixBadRequestException异常之外,所有从run()方法抛出的异常都算作失败,并触发降级getFallback()和断路器逻辑。

          HystrixBadRequestException用在非法参数或非系统故障异常等不应触发回退逻辑的场景。

5:依赖命名:CommandKey

 

Java代码  收藏代码

  1. public HelloWorldCommand(String name) {  
  2.         super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))  
  3.                   
  4.                 .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")));  
  5.         this.name = name;  
  6.     }  

 NOTE: 每个CommandKey代表一个依赖抽象,相同的依赖要使用相同的CommandKey名称。依赖隔离的根本就是对相同CommandKey的依赖做隔离.

6:依赖分组:CommandGroup

命令分组用于对依赖操作分组,便于统计,汇总等.

Java代码  收藏代码

  1. //使用HystrixCommandGroupKey工厂定义  
  2. public HelloWorldCommand(String name) {  
  3.     Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloWorldGroup"))  
  4. }  

 NOTE: CommandGroup是每个命令最少配置的必选参数,在不指定ThreadPoolKey的情况下,字面值用于对不同依赖的线程池/信号区分.

7:线程池/信号:ThreadPoolKey

Java代码  收藏代码

  1. public HelloWorldCommand(String name) {  
  2.         super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))  
  3.                 .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"))  
  4.                   
  5.                 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")));  
  6.         this.name = name;  
  7.     }  

 NOTE: 

当对同一业务依赖做隔离时使用CommandGroup做区分,但是对同一依赖的不同远程调用如(一个是redis 一个是http),可以使用HystrixThreadPoolKey做隔离区分.

           最然在业务上都是相同的组,但是需要在资源上做隔离时,可以使用HystrixThreadPoolKey区分.

8:请求缓存 Request-Cache

Java代码  收藏代码

  1. public class RequestCacheCommand extends HystrixCommand {  
  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请求上下文

Java代码  收藏代码

  1. public 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.   
  13.       HystrixRequestContextServletFilter  
  14.       HystrixRequestContextServletFilter  
  15.       class>com.netflix.hystrix.contrib.requestservlet.HystrixRequestContextServletFilterclass>  
  16.       
  17.       
  18.       HystrixRequestContextServletFilter  
  19.         
  20.                 .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)));  
  21.         this.name = name;  
  22.     }  
  23.     @Override  
  24.     protected String run() throws Exception {  
  25.         return "HystrixThread:" + Thread.currentThread().getName();  
  26.     }  
  27.     public static void main(String[] args) throws Exception{  
  28.         HelloWorldCommand command = new HelloWorldCommand("semaphore");  
  29.         String result = command.execute();  
  30.         System.out.println(result);  
  31.         System.out.println("MainThread:" + Thread.currentThread().getName());  
  32.     }  
  33. }  
  34.   

10:fallback降级逻辑命令嵌套

 

  适用场景:用于fallback逻辑涉及网络访问的情况,如缓存访问。

 

Java代码  收藏代码

  1. public class CommandWithFallbackViaNetwork extends HystrixCommand {  
  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 {  
  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:依赖调用和降级调用使用不同的线程池做隔离,防止上层线程池跑满,影响二级降级逻辑调用.

 11:显示调用fallback逻辑,用于特殊业务处理

 

Java代码  收藏代码

  1. public class CommandFacadeWithPrimarySecondary extends HystrixCommand {  
  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 {  
  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 {  
  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不再承担降级职责,建议慎重使用,会造成监控统计换乱等问题.

12:命令调用合并:HystrixCollapser

命令调用合并允许多个请求合并到一个线程/信号下批量执行。

执行流程图如下:

Java代码  收藏代码

  1. public class CommandCollapserGetValueForKey extends HystrixCollapser, 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> createCommand(final Collection> requests) {  
  12.         //创建返回command对象  
  13.         return new BatchCommand(requests);  
  14.     }  
  15.     @Override  
  16.     protected void mapResponseToRequests(List batchResponse, Collection> requests) {  
  17.         int count = 0;  
  18.         for (CollapsedRequest request : requests) {  
  19.             //手动匹配请求和响应  
  20.             request.setResponse(batchResponse.get(count++));  
  21.         }  
  22.     }  
  23.     private static final class BatchCommand extends HystrixCommand> {  
  24.         private final Collection> requests;  
  25.         private BatchCommand(Collection> 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 run() {  
  32.             ArrayList response = new ArrayList();  
  33.             for (CollapsedRequest 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 f1 = new CommandCollapserGetValueForKey(1).queue();  
  43.             Future f2 = new CommandCollapserGetValueForKey(2).queue();  
  44.             Future f3 = new CommandCollapserGetValueForKey(3).queue();  
  45.             Future 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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值