当服务有较多外部依赖时,若是其中某个服务的不可用,致使整个集群会受到影响(好比超时,致使大量的请求被阻塞,从而致使外部请求没法进来),这种状况下采用hystrix就颇有用了java
出于这个目的,了解了下hystrix框架,下面记录下,框架尝新的历程git
经过官网和相关博文,能够简单的说一下这个工做机制,大体流程以下github
首先是请求过来 -> 判断熔断器是否开 -> 服务调用 -> 异常则走fallback,失败计数+1 -> 结束redis
下面是主流程图多线程
graph LR A(请求)-->B{熔断器是否已开} B --> | 熔断 | D[fallback逻辑] B --> | 未熔断 | E[线程池/Semphore] E --> F{线程池满/无可用信号量} F --> | yes | D F --> | no | G{建立线程执行/本线程运行} G --> | yes | I(结束) G --> | no | D D --> I(结束)
熔断机制主要提供了两种,一个是基于线程池的隔离方式来作;还有一个则是根据信号量的抢占来作hexo
线程池方式 : 支持异步,支持超时设置,支持限流app
信号量方式 : 本线程执行,无异步,无超时,支持限流,消耗更小框架
基本上有上面这个简单的概念以后,开始进入咱们的使用测试流程异步
<dependency> <groupId>com.netflix.hystrix</groupId> <artifactId>hystrix-core</artifactId> <version>1.5.12</version> </dependency>
从官方文档来看,支持两种Command方式,一个是基于观察者模式的ObserverCommand, 一个是基本的Command,先用简单的看如下ide
public class HystrixConfigTest extends HystrixCommand<String> { private final String name; public HystrixConfigTest(String name, boolean ans) { // 注意的是同一个任务, super(Setter.withGroupKey( // CommandGroup是每一个命令最少配置的必选参数,在不指定ThreadPoolKey的状况下,字面值用于对不一样依赖的线程池/信号区分 HystrixCommandGroupKey.Factory.asKey("CircuitBreakerTestGroup")) // 每一个CommandKey表明一个依赖抽象,相同的依赖要使用相同的CommandKey名称。依赖隔离的根本就是对相同CommandKey的依赖作隔离. .andCommandKey(HystrixCommandKey.Factory.asKey("CircuitBreakerTestKey_" + ans)) // 当对同一业务依赖作隔离时使用CommandGroup作区分,可是对同一依赖的不一样远程调用如(一个是redis 一个是http),可使用HystrixThreadPoolKey作隔离区分 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("CircuitBreakerTest_" + ans)) .andThreadPoolPropertiesDefaults( // 配置线程池 HystrixThreadPoolProperties.Setter() .withCoreSize(12) // 配置线程池里的线程数,设置足够多线程,以防未熔断却打满threadpool ) .andCommandPropertiesDefaults( // 配置熔断器 HystrixCommandProperties.Setter() .withCircuitBreakerEnabled(true) .withCircuitBreakerRequestVolumeThreshold(3) .withCircuitBreakerErrorThresholdPercentage(80) // .withCircuitBreakerForceOpen(true) // 置为true时,全部请求都将被拒绝,直接到fallback // .withCircuitBreakerForceClosed(true) // 置为true时,将忽略错误 // .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE) // 信号量隔离 .withExecutionIsolationSemaphoreMaxConcurrentRequests(20) .withExecutionTimeoutEnabled(true) .withExecutionTimeoutInMilliseconds(200) .withCircuitBreakerSleepWindowInMilliseconds(1000) //熔断器打开到关闭的时间窗长度 // .withExecutionTimeoutInMilliseconds(5000) ) ); this.name = name; } @Override protected String run() throws Exception { System.out.println("running run():" + name + " thread: " + Thread.currentThread().getName()); int num = Integer.valueOf(name); if (num % 2 == 0 && num < 10) { // 直接返回 return name; } else if (num < 40) { Thread.sleep(300); return "sleep+"+ name; } else { // 无限循环模拟超时 return name; } } // // @Override // protected String getFallback() { // Throwable t = this.getExecutionException(); // if(t instanceof HystrixRuntimeException) { // System.out.println(Thread.currentThread() + " --> " + ((HystrixRuntimeException) t).getFailureType()); // } else if (t instanceof HystrixTimeoutException) { // System.out.println(t.getCause()); // } else { // t.printStackTrace(); // } // System.out.println(Thread.currentThread() + " --> ----------over------------"); // return "CircuitBreaker fallback: " + name; // } public static class UnitTest { @Test public void testSynchronous() throws IOException, InterruptedException { for (int i = 0; i < 50; i++) { if (i == 41) { Thread.sleep(2000); } try { System.out.println("===========" + new HystrixConfigTest(String.valueOf(i), i % 2 == 0).execute()); } catch (HystrixRuntimeException e) { System.out.println(i + " : " + e.getFailureType() + " >>>> " + e.getCause() + " <<<<<"); } catch (Exception e) { System.out.println("run()抛出HystrixBadRequestException时,被捕获到这里" + e.getCause()); } } System.out.println("------开始打印现有线程---------"); Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces(); for (Thread thread : map.keySet()) { System.out.println("--->name-->" + thread.getName()); } System.out.println("thread num: " + map.size()); System.in.read(); } } }
使用起来仍是比较简单的,通常步骤以下:
HsytrixCommand
类写上面的代码比较简单,可是有几个地方不太好处理
根据上面那一段代码的删删改改,貌似理解了如下几个点,不知道对误
run方法是核心执行服务调用,若是须要某些服务不统计到熔断的失败率(好比由于调用姿式不对致使服务内部的异常抛上来了,可是服务自己是正常的),这个时候,就须要包装下调用逻辑,将不须要的异常包装到 HystrixBadRequestException
类里
如
@Override protected String run() { try { return func.apply(route, parameterDescs); } catch (Exception e) { if (exceptionExcept(e)) { // 若是是不关注的异常case, 不进入熔断逻辑 throw new HystrixBadRequestException("unexpected exception!", e); } else { throw e; } } }
当发生失败时,hystrix会把原生的异常包装到 HystrixRuntimeException
这个类里,因此咱们能够在调用的地方以下处理
try { System.out.println("===========" + new HystrixConfigTest(String.valueOf(i), i % 2 == 0).execute()); } catch (HystrixRuntimeException e) { System.out.println(i + " : " + e.getFailureType() + " >>>> " + e.getCause() + " <<<<<"); } catch (Exception e) { System.out.println("run()抛出HystrixBadRequestException时,被捕获到这里" + e.getCause()); }
当定义了fallback逻辑时,异常则不会抛到具体的调用方,因此在 fallback 方法内,则有必要获取对应的异常信息
// 获取异常信息 Throwable t = this.getExecutionException();
而后下一步就是须要获取对应的异常缘由了,经过FailureType来代表失败的根源
((HystrixRuntimeException) t).getFailureType()
hystrix本身提供了一套监控插件,基本上你们都会有本身的监控统计信息,所以须要对这个数据进行和自定义,目前还没想到能够如何优雅的处理这些统计信息
主要是看了下这个东西能够怎么玩,整个用下来的感受就是,设计的比较有意思,可是配置参数太多,不少都没有彻底摸透
其次就是一些特殊的case(如监控,报警,特殊状况过滤)须要处理时,用起来并非很顺手,主要问题仍是没有理解清楚这个框架的内部工做机制的问题
基于hexo + github pages搭建的我的博客,记录全部学习和工做中的博文,欢迎你们前去逛逛
尽信书则不如,以上内容,纯属一家之言,因本人能力通常,见识有限,如发现bug或者有更好的建议,随时欢迎批评指正