什么是Hystrix
Hystrix是Spring Cloud提供的一种带有熔断机制的框架,由于在微服务系统中同一个操作会由多个不同的微服务来共同完成,所以微服务与微服务之间会由很多相互的调用,由于在分布式环境中经常会出现某个微服务节点故障的情况,所以会由调用失败发生,而熔断器的作用就是当出现远程调用失败的时候提供一种机制来保证程序的正常运行而不会卡死在某一次调用,类似Java程序中的try-catch结构,而只有当异常发生的时候才会进入catch的代码块。
Hystrix源码解析
Hystrix在底层使用了Spring提供的切面技术。
在HystrixCommandAspect.java文件中可以看到定义的切面:
@Aspect
public class HystrixCommandAspect {
private static final Map META_HOLDER_FACTORY_MAP;
static {
META_HOLDER_FACTORY_MAP = ImmutableMap.builder()
.put(HystrixPointcutType.COMMAND, new CommandMetaHolderFactory())
.put(HystrixPointcutType.COLLAPSER, new CollapserMetaHolderFactory())
.build();
}
@Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)")
public void hystrixCommandAnnotationPointcut() {
}
@Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser)")
public void hystrixCollapserAnnotationPointcut() {
}
@Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()")
public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable {
//通过切点获取被拦截的方法
Method method = getMethodFromTarget(joinPoint);
Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint);
if (method.isAnnotationPresent(HystrixCommand.class) && method.isAnnotationPresent(HystrixCollapser.class)) {
throw new IllegalStateException("method cannot be annotated with HystrixCommand and HystrixCollapser " +
"annotations at the same time");
}
MetaHolderFactory metaHolderFactory = META_HOLDER_FACTORY_MAP.get(HystrixPointcutType.of(method));
//metaholder中保存了很多和切点相关的信息,详见后文的贴图
MetaHolder metaHolder = metaHolderFactory.create(joinPoint);
HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder);
ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ?
metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType();
//result中保留操作的结果,可能是成功操作的返回值也可能是fallback方法的返回值
Object result;
try {
//如果返回的结果使用了observable模式则执行以下代码
if (!metaHolder.isObservable()) {
result = CommandExecutor.execute(invokable, executionType, metaHolder);
} else {
//否则执行else
result = executeObservable(invokable, executionType, metaHolder);
}
} catch (HystrixBadRequestException e) {
throw e.getCause() != null ? e.getCause() : e;
} catch (HystrixRuntimeException e) {
throw hystrixRuntimeExceptionToThrowable(metaHolder, e);
}
return result;
}
..........
}
可以看到在这个类中首先是定义了两个切面,由于我们这里讨论的是HystrixCommand,所以只关注第一个切面,在这个切面里可以看到切点是被HystrixCommand注解的方法,然后对方法的具体增强是在接下来的一个名为methodsAnnotatedWithHystrixCommand()的方法里,具体的流程用注释写在了代码中。
metaHolder的内容:
可以看到在以上代码中,最重要的便是获取到result具体结果的那一步,由于我们的例子不是使用,所以重点放在
result = CommandExecutor.execute(invokable, executionType, metaHolder);
接下来跳转到CommandExecutor.java中的execute()方法
public static Object execute(HystrixInvokable invokable, ExecutionType executionType, MetaHolder metaHolder) throws RuntimeException {
Validate.notNull(invokable);
Validate.notNull(metaHolder);
switch (executionType) {
case SYNCHRONOUS: {
return castToExecutable(invokable, executionType).execute();
}
case ASYNCHRONOUS: {
HystrixExecutable executable = castToExecutable(invokable, executionType);
if (metaHolder.hasFallbackMethodCommand()
&& ExecutionType.ASYNCHRONOUS == metaHolder.getFallbackExecutionType()) {
return new FutureDecorator(executable.queue());
}
return executable.queue();
}
case OBSERVABLE: {
HystrixObservable observable = castToObservable(invokable);
return ObservableExecutionMode.EAGER == metaHolder.getObservableExecutionMode() ? observable.observe() : observable.toObservable();
}
default:
throw new RuntimeException("unsupported execution type: " + executionType);
}
}
在execute()方法中使用了rxjava的观察者模式并最终在这里计算出结果返回。当出现失败是,会调用GenericCommand.java中的getFallBack()方法,代码如下:
@Override
protected Object getFallback() {
final CommandAction commandAction = getFallbackAction();
if (commandAction != null) {
try {
return process(new Action() {
@Override
Object execute() {
MetaHolder metaHolder = commandAction.getMetaHolder();
Object[] args = createArgsForFallback(metaHolder, getExecutionException());
return commandAction.executeWithArgs(metaHolder.getFallbackExecutionType(), args);
}
});
} catch (Throwable e) {
LOGGER.error(FallbackErrorMessageBuilder.create()
.append(commandAction, e).build());
throw new FallbackInvocationException(unwrapCause(e));
}
} else {
return super.getFallback();
}
}
在这个方法中首先会对是否设置了commandAction进行判断,commandAction就是我们之前所设置的fallback字段指向的方法,如果发现有降级方法,则调用降级方法获取结果,进入process()方法,代码如下:
Object process(Action action) throws Exception {
Object result;
try {
result = action.execute();
flushCache();
} catch (CommandActionExecutionException throwable) {
Throwable cause = throwable.getCause();
if (isIgnorable(cause)) {
throw new HystrixBadRequestException(cause.getMessage(), cause);
}
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Exception) {
throw (Exception) cause;
} else {
// instance of Throwable
throw new CommandActionExecutionException(cause);
}
}
return result;
}
最终在process()方法中获取降级方法执行的结果,而如果没有设置降级方法则调用父类的getFallBack()方法,父类的getFallBack方法会抛出一个找不到降级方法的异常,代码如下:
protected T getFallback() {
throw new RuntimeException("No fallback available.", getExecutionException());
}
至于何时会跳转到降级方法,则是在AbstractCommand.java中,在这里定义了很多种执行失败的情况,通过rxjava框架的观察者模式对错误进行监听,根据不同的情况会进入不同的处理方法,最终这些处理方法都会调用HystrixCommand.java中的getFallbackObservable()方法,并最终进入上文所述的真正执行fallback方法的代码。
以上就是对Hystrix降级机制的说明,想要深入理解Hystrix,必须要对rxjava有比较深刻的认识,在Hystrix中大量运用了rxjava。
本文将通过若干demo,加入对官网How-it-Works的理解和翻译,力求更清晰解释hystrix的基础功能。所用demo均对官网How-To-Use进行了二次修改,见https://github.com/star2478/java-hystrix
Hystrix是Netflix开源的一款容错系统,能帮助使用者码出具备强大的容错能力和鲁棒性的程序。如果某程序或class要使用Hystrix,只需简单继承HystrixCommand/HystrixObservableCommand
并重写run()/construct()
,然后调用程序实例化此class并执行execute()/queue()/observe()/toObservable()
。
// HelloWorldHystrixCommand要使用Hystrix功能
public class HelloWorldHystrixCommand extends HystrixCommand {
private final String name;
public HelloWorldHystrixCommand(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
// 如果继承的是HystrixObservableCommand,要重写Observable construct()
@Override
protected String run() {
return "Hello " + name;
}
}
/* 调用程序对HelloWorldHystrixCommand实例化,执行execute()即触发HelloWorldHystrixCommand.run()的执行 */
String result = new HelloWorldHystrixCommand("HLX").execute();
System.out.println(result); // 打印出Hello HLX
pom.xml加上以下依赖。spring cloud也集成了hystrix,不过本文只介绍原生hystrix。
com.netflix.hystrix
hystrix-core
1.5.8
本文重点介绍的是Hystrix各项基础能力的用法及其效果,不从零介绍hystrix,要了解基础知识推荐官网wiki或民间blog
1、HystrixCommand vs HystrixObservableCommand
要想使用hystrix,只需要继承HystrixCommand
或HystrixObservableCommand
,简单用法见上面例子。两者主要区别是:
前者的命令逻辑写在
run()
;后者的命令逻辑写在construct()
前者的
run()
是由新创建的线程执行;后者的construct()
是由调用程序线程执行前者一个实例只能向调用程序发送(emit)单条数据,比如上面例子中
run()
只能返回一个String结果;后者一个实例可以顺序发送多条数据,比如demo中顺序调用多个onNext()
,便实现了向调用程序发送多条数据,甚至还能发送一个范围的数据集
2、4个命令执行方法
execute()
、queue()
、observe()
、toObservable()
这4个方法用来触发执行run()/construct()
,一个实例只能执行一次这4个方法,特别说明的是HystrixObservableCommand
没有execute()
和queue()
。
4个方法的主要区别是:
execute()
:以同步堵塞方式执行run()
。以demo为例,调用execute()
后,hystrix先创建一个新线程运行run()
,接着调用程序要在execute()
调用处一直堵塞着,直到run()
运行完成queue()
:以异步非堵塞方式执行run()
。以demo为例,一调用queue()
就直接返回一个Future对象,同时hystrix创建一个新线程运行run()
,调用程序通过Future.get()
拿到run()
的返回结果,而Future.get()
是堵塞执行的observe()
:事件注册前执行run()/construct()
。以demo为例,第一步是事件注册前,先调用observe()
自动触发执行run()/construct()
(如果继承的是HystrixCommand
,hystrix将创建新线程非堵塞执行run()
;如果继承的是HystrixObservableCommand
,将以调用程序线程堵塞执行construct()
),第二步是从observe()
返回后调用程序调用subscribe()
完成事件注册,如果run()/construct()
执行成功则触发onNext()
和onCompleted()
,如果执行异常则触发onError()
toObservable()
:事件注册后执行run()/construct()
。以demo为例,第一步是事件注册前,一调用toObservable()
就直接返回一个Observable
对象,第二步调用subscribe()
完成事件注册后自动触发执行run()/construct()
(如果继承的是HystrixCommand
,hystrix将创建新线程非堵塞执行run()
,调用程序不必等待run()
;如果继承的是HystrixObservableCommand
,将以调用程序线程堵塞执行construct()
,调用程序等待construct()
执行完才能继续往下走),如果run()/construct()
执行成功则触发onNext()
和onCompleted()
,如果执行异常则触发onError()
3、fallback(降级)
使用fallback机制很简单,继承HystrixCommand
只需重写getFallback()
,继承HystrixObservableCommand
只需重写resumeWithFallback()
,比如HelloWorldHystrixCommand
加上下面代码片段:
@Override
protected String getFallback() {
return "fallback: " + name;
}
fallback实际流程是当run()/construct()
被触发执行时或执行中发生错误时,将转向执行getFallback()/resumeWithFallback()
。结合下图,4种错误情况将触发fallback:
非HystrixBadRequestException异常:以demo为例,当抛出HystrixBadRequestException时,调用程序可以捕获异常,没有触发
getFallback()
,而其他异常则会触发getFallback()
,调用程序将获得getFallback()
的返回run()/construct()
运行超时:以demo为例,使用无限while循环或sleep模拟超时,触发了getFallback()
熔断器启动:以demo为例,我们配置10s内请求数大于3个时就启动熔断器,请求错误率大于80%时就熔断,然后for循环发起请求,当请求符合熔断条件时将触发
getFallback()
。更多熔断策略见下文线程池/信号量已满:以demo为例,我们配置线程池数目为3,然后先用一个for循环执行
queue()
,触发的run()
sleep 2s,然后再用第2个for循环执行execute()
,发现所有execute()
都触发了fallback,这是因为第1个for的线程还在sleep,占用着线程池所有线程,导致第2个for的所有命令都无法获取到线程
调用程序可以通过
isResponseFromFallback()
查询结果是由run()/construct()
还是getFallback()/resumeWithFallback()
返回的
4、隔离策略
hystrix提供了两种隔离策略:线程池隔离和信号量隔离。hystrix默认采用线程池隔离。
线程池隔离:不同服务通过使用不同线程池,彼此间将不受影响,达到隔离效果。以demo为例,我们通过andThreadPoolKey配置使用命名为
ThreadPoolTest
的线程池,实现与其他命名的线程池天然隔离,如果不配置andThreadPoolKey则使用withGroupKey配置来命名线程池信号量隔离:线程隔离会带来线程开销,有些场景(比如无网络请求场景)可能会因为用开销换隔离得不偿失,为此hystrix提供了信号量隔离,当服务的并发数大于信号量阈值时将进入fallback。以demo为例,通过
withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)
配置为信号量隔离,通过withExecutionIsolationSemaphoreMaxConcurrentRequests
配置执行并发数不能大于3,由于信号量隔离下无论调用哪种命令执行方法,hystrix都不会创建新线程执行run()/construct()
,所以调用程序需要自己创建多个线程来模拟并发调用execute()
,最后看到一旦并发线程>3,后续请求都进入fallback
5、熔断机制
熔断机制相当于电路的跳闸功能,举个栗子,我们可以配置熔断策略为当请求错误比例在10s内>50%时,该服务将进入熔断状态,后续请求都会进入fallback。
以demo为例,我们通过withCircuitBreakerRequestVolumeThreshold
配置10s内请求数超过3个时熔断器开始生效,通过withCircuitBreakerErrorThresholdPercentage
配置错误比例>80%时开始熔断,然后for循环执行execute()
触发run()
,在run()
里,如果name
是小于10的偶数则正常返回,否则超时,通过多次循环后,超时请求占所有请求的比例将大于80%,就会看到后续请求都不进入run()
而是进入getFallback()
,因为不再打印"running run():" + name
了。
除此之外,hystrix还支持多长时间从熔断状态自动恢复等功能,见下文附录。
6、结果cache
hystrix支持将一个请求结果缓存起来,下一个具有相同key的请求将直接从缓存中取出结果,减少请求开销。要使用hystrix cache功能,第一个要求是重写getCacheKey()
,用来构造cache key;第二个要求是构建context,如果请求B要用到请求A的结果缓存,A和B必须同处一个context。通过HystrixRequestContext.initializeContext()
和context.shutdown()
可以构建一个context,这两条语句间的所有请求都处于同一个context。
以demo的testWithCacheHits()
为例,command2a、command2b、command2c同处一个context,前两者的cache key都是2HLX
(见getCacheKey()
),所以command2a执行完后把结果缓存,command2b执行时就不走run()
而是直接从缓存中取结果了,而command2c的cache key是2HLX1
,无法从缓存中取结果。此外,通过isResponseFromCache()
可检查返回结果是否来自缓存。
7、合并请求collapsing
hystrix支持N个请求自动合并为一个请求,这个功能在有网络交互的场景下尤其有用,比如每个请求都要网络访问远程资源,如果把请求合并为一个,将使多次网络交互变成一次,极大节省开销。重要一点,两个请求能自动合并的前提是两者足够“近”,即两者启动执行的间隔时长要足够小,默认为10ms,即超过10ms将不自动合并。
以demo为例,我们连续发起多个queue请求,依次返回f1~f6共6个Future对象,根据打印结果可知f1~f5同处一个线程,说明这5个请求被合并了,而f6由另一个线程执行,这是因为f5和f6中间隔了一个sleep,超过了合并要求的最大间隔时长。
附录:各种策略配置
根据http://hot66hot.iteye.com/blog/2155036 整理而得。
- HystrixCommandProperties
/* --------------统计相关------------------*/
// 统计滚动的时间窗口,默认:5000毫秒(取自circuitBreakerSleepWindowInMilliseconds)
private final HystrixProperty metricsRollingStatisticalWindowInMilliseconds;
// 统计窗口的Buckets的数量,默认:10个,每秒一个Buckets统计
private final HystrixProperty metricsRollingStatisticalWindowBuckets; // number of buckets in the statisticalWindow
// 是否开启监控统计功能,默认:true
private final HystrixProperty metricsRollingPercentileEnabled;
/* --------------熔断器相关------------------*/
// 熔断器在整个统计时间内是否开启的阀值,默认20。也就是在metricsRollingStatisticalWindowInMilliseconds(默认10s)内至少请求20次,熔断器才发挥起作用
private final HystrixProperty circuitBreakerRequestVolumeThreshold;
// 熔断时间窗口,默认:5秒.熔断器中断请求5秒后会进入半打开状态,放下一个请求进来重试,如果该请求成功就关闭熔断器,否则继续等待一个熔断时间窗口
private final HystrixProperty circuitBreakerSleepWindowInMilliseconds;
//是否启用熔断器,默认true. 启动
private final HystrixProperty circuitBreakerEnabled;
//默认:50%。当出错率超过50%后熔断器启动
private final HystrixProperty circuitBreakerErrorThresholdPercentage;
//是否强制开启熔断器阻断所有请求,默认:false,不开启。置为true时,所有请求都将被拒绝,直接到fallback
private final HystrixProperty circuitBreakerForceOpen;
//是否允许熔断器忽略错误,默认false, 不开启
private final HystrixProperty circuitBreakerForceClosed;
/* --------------信号量相关------------------*/
//使用信号量隔离时,命令调用最大的并发数,默认:10
private final HystrixProperty executionIsolationSemaphoreMaxConcurrentRequests;
//使用信号量隔离时,命令fallback(降级)调用最大的并发数,默认:10
private final HystrixProperty fallbackIsolationSemaphoreMaxConcurrentRequests;
/* --------------其他------------------*/
//使用命令调用隔离方式,默认:采用线程隔离,ExecutionIsolationStrategy.THREAD
private final HystrixProperty executionIsolationStrategy;
//使用线程隔离时,调用超时时间,默认:1秒
private final HystrixProperty executionIsolationThreadTimeoutInMilliseconds;
//线程池的key,用于决定命令在哪个线程池执行
private final HystrixProperty executionIsolationThreadPoolKeyOverride;
//是否开启fallback降级策略 默认:true
private final HystrixProperty fallbackEnabled;
// 使用线程隔离时,是否对命令执行超时的线程调用中断(Thread.interrupt())操作.默认:true
private final HystrixProperty executionIsolationThreadInterruptOnTimeout;
// 是否开启请求日志,默认:true
private final HystrixProperty requestLogEnabled;
//是否开启请求缓存,默认:true
private final HystrixProperty requestCacheEnabled; // Whether request caching is enabled.
- HystrixCollapserProperties
//请求合并是允许的最大请求数,默认: Integer.MAX_VALUE
private final HystrixProperty maxRequestsInBatch;
//批处理过程中每个命令延迟的时间,默认:10毫秒
private final HystrixProperty timerDelayInMilliseconds;
//批处理过程中是否开启请求缓存,默认:开启
private final HystrixProperty requestCacheEnabled;
- HystrixThreadPoolProperties
/* 配置线程池大小,默认值10个 */
private final HystrixProperty corePoolSize;
/* 配置线程值等待队列长度,默认值:-1 建议值:-1表示不等待直接拒绝,测试表明线程池使用直接决绝策略+ 合适大小的非回缩线程池效率最高.所以不建议修改此值。 当使用非回缩线程池时,queueSizeRejectionThreshold,keepAliveTimeMinutes 参数无效 */
private final HystrixProperty maxQueueSize;
参考文献
https://github.com/Netflix/Hystrix
https://github.com/Netflix/Hystrix/wiki/How-To-Use
http://hot66hot.iteye.com/blog/2155036