一、Hystrix简介
Hystrix是Nettflix中的一个开源项目,它能够在依赖服务失效的时候,可通过隔离系统依赖服务的方式,防止服务出现级联失败,通是Hystrix也提供了失败回滚机制,是系统能够更快的从异常中恢复。其实简单的一句话就是:hystrix为微服务间的调用提供强有力父容错机制,它主要的作用有:
- 在通过第三方客户访问依赖服务出现高延迟或者失败的时候,为系统提供保护和控制
- 在复杂的分布式系统中防止级联失败(服务雪崩效应)
- 快速失败(fail fast),同时能快速回复
- 提供失败回滚(Fallback)和优雅的服务降级机制
- 提供实时监控/报警和运维
服务雪崩
服务雪崩是一种因服务提供者不可用导致服务调用者不可用,并且将这种不可用的过程放大,如图所示:
在这里A服务作为B、C服务的提供者,D、E、F是B、C服务的调用者,当A服务不可用时,会导致B、C服务不可用,这时将会影响到D、E、F,从而导致整个系统不可用,因此雪崩将会导致服务调用者不可用。
服务雪崩产生一般有三个流程:首先服务提供者不可用,然后频繁的重试将导致网路流量增大,最后导致服务调用者不可用。
那么对于服务雪崩有什么解决方案呢,从可靠性、可用性的角度来看,能尽量防止系统的瘫痪。对于防止雪崩一般有三种方式,如:服务熔断、服务降级、服务雪崩。
服务熔断
服务熔断机制是对雪崩效用的一种微服务链路的保护机制。在分布式系统中,当服务提供者不可用时就很有可能发生雪崩效应,导致整合系统不可用,因此便使用这种熔断模式(断路器)来进行预防(电路中断路器,当电路中的电压过大时便自动断开)。
断路器是将远程方法调用包装到一个断路器对象中,用于监控方法调用过程的失败,一旦该方法调用发生的失败次数在一定时间内达到一定的阈值,那么这个短路其将会跳闸,在接下来的时间里再次调用该方法将会被断路器直接返回异常,而不是方法的真实调用,这样也就避免了服务调用者在服务提供者不可用时发送请求,从而减少了线程池中资源的消耗。
虽然断路器打开的时候保护了程序的无效调用,但是当服务提供者护肤正常的时候,需要无不干预来重置断路器,使得方法可以再次正常调用。因此合理的断路器应该具备一定开关转化逻辑,它需要一个机制来控制它的重新闭合。关于断路器的状态大概有如下三种:关闭状态、打开状态、半开状态。
- 关闭状态:断路器处于关闭状态,统计失败次数,在一段时间内达到一定的阈值后断路器打开。
- 打开状态:断路器出去打开状态,对方法调用直接返回失败错误,不发生真正的方法调用。设置一个重置时间,在重置时间结束后,断路器便设置成半开状态。
- 半开状态:断路器处于半开状态,此时允许方法调用,当调用成功后(或者成功达到一定的比例),便关闭断路器,否则认为服务没有恢复,重新打开断路器。
服务降级操作
断路器为隔断服务调用者和异常服务提供者防止服务雪崩现象,提供一种保护措施。而服务降级则是当系统整体资源不够用的时候,便选择适当的放弃部分服务,将主要的资源投放到核心服务中,待度过难关之后,再重启已关闭的服务,保证了系统核心服务的稳定。
在Hystrix中,当服务间调用发生问题时,它将采用备用的Fallback方法代替住方法执行返回结果,对失败服务进行服务降级。当调用服务失败次数在一段时间内超过了一定的阈值之后,断路器打开,不再执行真正的方法调用,而快速失败,直接执行Fallback逻辑,服务降级减少服务调用者的资源消耗,保护服务调用者中的线程资源。
资源隔离
在货船中,为了防止漏水和水灾的扩散,一般会将货仓进行分割,避免出事导致所有的货物都被影响。同样在Hystrix中也采用了舱壁模式,将系统中的服务提供者隔离起来,一个服务提供者延迟或者失败,而并不是整个系统的失败,同时也能控制这些服务的并发。
二、@HystrixCommand 注解
在基础应用中@HystrixCommand注解来包装需要保护的远程调用方法,这里先看下
@HystrixCommand 注解代码:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface HystrixCommand {
// 命令分组键用于报告、预警以及面板预示
String groupKey() default "";
// Hystrix 命令键,用于区分不同的注解方法, 默认为注解方法的名称
String commandKey() default "";
// 线程池键用来指定命令执行 HystrixThreadPool
String threadPoolKey() default "";
// 指定 Fallback 方法名称,Fallback 方法也可以被 HystrixCommand 注解
String fallbackMethod() default "";
// 自定义命令的相关配置
HystrixProperty[] commandProperties() default {};
// 自定义线程池的相关配置
HystrixProperty[] threadPoolProperties() default {};
// 定义忽略哪些异常
Class<? extends Throwable>[] ignoreExceptions() default {};
ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER;
HystrixException[] raiseHystrixExceptions() default {};
// 默认的 fallback
String defaultFallback() default "";
}
对于@HystrixCommand注解的配置,一般仅需要关注fallbackMethod方法,当然如果对命令和线程池有特定的需要,可以进行额外的配置。除了@HystrixCommand注解,还有一个@HystrixCollapser注解用于请求合并操作,但是需要和@HystrixCommand注解配合使用,批量操作的方法必须被@HystrixCommand注解。
三、HystrixCommandAspect 切面
被注解修饰的类会被HystrixCommand包装执行,在Hystrix中是通过Aspectj切面的方式来将被注解修饰大方法进行封装调用。代码如下:
@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 = metaHolderFactory.create(joinPoint);
HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder);
ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ?
metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType();
Object result;
try {
if (!metaHolder.isObservable()) {
result = CommandExecutor.execute(invokable, executionType, metaHolder);
} else {
result = executeObservable(invokable, executionType, metaHolder);
}
} catch (HystrixBadRequestException e) {
throw e.getCause();
} catch (HystrixRuntimeException e) {
throw hystrixRuntimeExceptionToThrowable(metaHolder, e);
}
return result;
}
上面这段代码执行的步骤答题如下:
- 通过MetaHolderFactory构建出被注解修饰方法中用于构建HystrixCommand必要的信息集合类MetaHolder。
- 根据MetaHolder来通过HystrixCommandFactory构建出合适的HystrixCommand。
- 委托CommandExecutor执行HystrixCommand,然后将得到的结果返回。
MetaHolder持有构建HystrixCommand和被包装方法相关的必要信息,如被注解的方法、失败回滚执行的方法和默认的命令键等属性,com.netflix.hystrix.contrib.javanica.command.MetaHolder类代码如下:
@Immutable
public final class MetaHolder {
private final HystrixCollapser hystrixCollapser;
private final HystrixCommand hystrixCommand;
private final DefaultProperties defaultProperties;
private final Method method; // 被注释的方法
private final Method cacheKeyMethod; //
private final Method ajcMethod;
private final Method fallbackMethod;
private final Object obj;
private final Object proxyObj;
private final Object[] args;
private final Closure closure;
private final String defaultGroupKey; // 默认的group键
private final String defaultCommandKey; // 默认的命令键
private final String defaultCollapserKey;//默认合并请求键
private final String defaultThreadPoolKey;//默认线程池键
private final ExecutionType executionType; 执行类型
private final boolean extendedFallback;
private final ExecutionType collapserExecutionType;
private final ExecutionType fallbackExecutionType;
private final boolean fallback;
private boolean extendedParentFallback;
private final boolean defaultFallback;
private final JoinPoint joinPoint;
private final boolean observable;
private final ObservableExecutionMode observableExecutionMode;
// 省略部分代码
}
在HystrixCommandFactory类中,用于创建 HystrixCommand 的方法如下所示:
public HystrixInvokable create(MetaHolder metaHolder) {
HystrixInvokable executable;
// 构建请求命令合并键
if (metaHolder.isCollapserAnnotationPresent()) {
executable = new CommandCollapser(metaHolder);
} else if (metaHolder.isObservable()) {
executable = new GenericObservableCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder));
} else {
executable = new GenericCommand(HystrixCommandBuilderFactory.getInstance().create(metaHolder));
}
return executable;
}
这里根据isObservable方法返回的属性不同,来构建不同的命令,比如HystrixCommand或者HystrixObservableCommand,前者是将处理同步或者异步执行命令,后者是处理异步回调执行命令。Hystrix根据被包装的方法返回值来决定命令的执行方式,代码如下:
com.netflix.hystrix.contrib.javanica.command.ExecutionType
public enum ExecutionType {
// 异步执行命令
ASYNCHRONOUS,
// 同步执行命令
SYNCHRONOUS,
// 响应式执行命令(异步回调)
OBSERVABLE;
// 根据方法返回类型来返回对应的ExecutionType
public static ExecutionType getExecutionType(Class<?> type) {
// Future 为异步执行
if (Future.class.isAssignableFrom(type)) {
return ExecutionType.ASYNCHRONOUS;
} else if (Observable.class.isAssignableFrom(type)) {
// 属于rxType,为异步回调执行
return ExecutionType.OBSERVABLE;
} else {
// 其他为同步执行
return ExecutionType.SYNCHRONOUS;
}
}
}
根据被包装的方法的返回值类型决定命令执行的ExecutionType,从而决定是构建HystrixCommand还是HystrixObservableCommand。其中 Future类型的返回值将会被异步执行,rx类型的返回值将会被异步回调执行,其他的都是同步执行。
CommandExecutor是根据MetaHolder中ExecutionType执行类型的不同,选择同步执行、异步执行还是异步回调执行,所返回的结果也不相同。同步执行直接返回结果对象;异步执行返回Future,里面封装了异步操作的结果;异步回调执行返回的是Observable,封装响应式执行的结果,可以通过它来对结果进行订阅,在执行结束后进行特定的操作。这里来看一下CommandExecutor#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);
}
}
这里再看下这些相关的类图结构,如下:
从上图中可以看出这里面使用了设计模式中的命令模式,图中的HystrixObservable是HystrixCommand的标记接口,继承了该类的接口,都可以被HystrixCommand执行,而提供具体执行方法的接口在HystrixExecutable类中,其中包含同步执行和异步执行的方法,HystrixObservable接口类则是用于异步回调命令的,它们对应命令模式中的Command和ConcreteCommand。CommandExecutor将会调用HystrixObservable执行命令,相当于命令模式中的Invoker。HystrixCommandFactory的作用则是用于生成命令,HystrixCommandAspect则相当于命令模式中的客户端情景类Client。图中的CommandAction类中持有Fallback方法或者被@HystrixCommand注解的远程调用方法,相当于命令模式中的Receiver。
四、HystrixCommand类结构
上图中看上去好像很复杂,但是主要的实现类其实只有三个,这三个分别是同步或异步执行命令的GenericCommand、请求合并执行命令的BatchHystrixCommand,以及异步回调执行命令的GenericObservableCommand,而以上三个类的关键实现都在AbstractCommand抽象类中,因此AbstractCommand抽象类才是重点。这个将在下一篇文章中进行解析。