Hystrix源码解析之——封装 HystrixCommand(一)

一、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;
}

上面这段代码执行的步骤答题如下:

  1. 通过MetaHolderFactory构建出被注解修饰方法中用于构建HystrixCommand必要的信息集合类MetaHolder。
  2. 根据MetaHolder来通过HystrixCommandFactory构建出合适的HystrixCommand。
  3. 委托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抽象类才是重点。这个将在下一篇文章中进行解析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值