Spring Cloud学习——服务容错保护:Hystrix

本文介绍了Hystrix在分布式系统中的作用,如防止因依赖服务故障导致的系统崩溃。详细讲解了Hystrix的工作原理,包括降级、熔断、命令模式以及其在Spring Cloud中的使用。通过Hystrix,可以实现服务的隔离,提高系统的稳定性和可用性。文章还探讨了Hystrix的熔断器机制,以及其配置选项和状态变化。最后,对比了线程池和信号量两种隔离策略的应用场景和优缺点。
摘要由CSDN通过智能技术生成

在分布式系统中,难免有对外部接口的依赖,而外部接口有可能出现响应缓慢,大量请求超时,大量访问出现异常等情况。出现上面所说的情况有可能是由很多原因导制的,可能是网络抖动,外部系统有没有测出的bug,系统遭遇黑客攻击等。因为一个接口的异常,有可能导制线程阻塞,影响到其它接口的服务,甚至整个系统的服务给拖跨,对外部系统依赖的模块越多,出现的风险也就会越高,Hystrix正是用于解决这样的问题。Hystrix同样是Netflix公司开源的用于解决分布式问题而开源的框架。

降级和熔断

熔断:类似于保险丝,为了防止整个系统故障,暂时停止对下游服务的调用。
降级:抛弃一些非核心的接口和数据。

相同点:

  1. 从可用性和可靠性出发,为了防止系统崩溃。
  2. 最终让用户体验到到是某些功能暂时不能用。

不同点:

  1. 服务熔断一般是下游服务故障导致的,而服务降级一般是从整体系统负荷考虑,由调用方控制。
命令模式

Hystrix使用了命令模式,所以我们来先看一下命令模式。

命令模式是为了解决命令的请求者和命令的实现者之间的耦合关系。经典的命令模式包括4个角色:

Command: 定义命令的统一接口。
ConcreteCommand: Command接口的实现者,用来执行具体的命令,某些情况下可以直接用来充当Receiver。
Receiver: 命令的实际执行者。
Invoker: 命令的请求者,是命令模式中最重要的角色。这个角色用来对各个命令进行控制。

下面对上面四个角色的经典实现用代码来进行说明,这也是大部分文章对命令模式的运用方式。

//通用Receiver类
public abstract class Receiver {
    public abstract void doSomething();
}

//具体Receiver类
public class ConcreteReciver1 extends Receiver{ 
    //每个接收者都必须处理一定的业务逻辑 
    public void doSomething(){ } 
} 
public class ConcreteReciver2 extends Receiver{ 
    //每个接收者都必须处理一定的业务逻辑 
    public void doSomething(){ } 
}

//抽象Command类
public abstract class Command {
    public abstract void execute();
}

//具体的Command类
public class ConcreteCommand1 extends Command { 
    //对哪个Receiver类进行命令处理 
    private Receiver receiver; 
    //构造函数传递接收者 
    public ConcreteCommand1(Receiver _receiver){
        this.receiver = _receiver; 
    } 

    //必须实现一个命令 
    public void execute() { 
    //业务处理 
        this.receiver.doSomething(); 
    } 
} 

public class ConcreteCommand2 extends Command { 
    //哪个Receiver类进行命令处理 
    private Receiver receiver; 
    //构造函数传递接收者 
    public ConcreteCommand2(Receiver _receiver){
        this.receiver = _receiver; 
    } 
    //必须实现一个命令 
    public void execute() { 
        //业务处理 
        this.receiver.doSomething();
    } 
}

//调用者Invoker类
public class Invoker {
    private Command command;
    
    public void setCommand(Command _command){
        this.command = _command;
    }
    
    public void action() {
        this.command.execute();
    }
}

//场景类
public class Client {
    public static void main(String[] args){
        Invoker invoker = new Invoker();
        Receiver receiver = new ConcreteReceiver1();
        
        Command command = new ConcreteCommand1(receiver);
        invoker.setCommand(command);
        invoker.action();
    }
}

适用场景:

  1. 命令的发送者和命令执行者有不同的生命周期。命令发送了并不是立即执行。

  2. 命令需要进行各种管理逻辑。

  3. 需要支持撤消\重做操作(这种状况的代码大家可以上网搜索下,有很多,这里不进行详细解读)。

结论:

  1. 命令模式是通过命令发送者和命令执行者的解耦来完成对命令的具体控制的。

  2. 命令模式是对功能方法的抽象,并不是对对象的抽象。

  3. 命令模式是将功能提升到对象来操作,以便对多个功能进行一系列的处理以及封装。

Spring Cloud 中使用Hystrix

使用Hystrix需要两步:

  1. 启动类添加@EnableHystrix注解。
  2. 方法上添加@HystrixCommand注解,并指定fallback的方法。

既然加了@EnableHystrix注解,那还是先进入这个注解看吧:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableCircuitBreaker
public @interface EnableHystrix {
}

这个注解的功能就是开启Hystrix。这个注解还引入了@EnableCircuitBreaker注解。

在代码同一级目录下,还可以看到两个配置类:HystrixAutoConfiguration和HystrixCircuitBreakerConfiguration。 下面是HystrixAutoConfiguration配置类的配置:

@Configuration
@ConditionalOnClass({ Hystrix.class, HealthIndicator.class })
@AutoConfigureAfter({ HealthIndicatorAutoConfiguration.class })
public class HystrixAutoConfiguration {

    @Bean
    @ConditionalOnEnabledHealthIndicator("hystrix")
    public HystrixHealthIndicator hystrixHealthIndicator() {
        return new HystrixHealthIndicator();
    }

}

从代码中可以看到,HystrixAutoConfiguration这个配置类主要是hystrix的健康检查的配置。再看下HystrixCircuitBreakerConfiguration这个类,这个类里面就配置了很多内容。

@Bean
public HystrixCommandAspect hystrixCommandAspect() {
    return new HystrixCommandAspect();
}

这里返回了HystrixCommandAspect的bean,这个切面中定义了Pointcut:

@Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)")
public void hystrixCommandAnnotationPointcut() {
}

@Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser)")
public void hystrixCollapserAnnotationPointcut() {
}

所以,这个Aspect就是利用AOP切面对 HystrixCommand 、 HystrixCollapser 两种注解的方法进行扩展处理。 我们在方法上添加@HystrixCommand注解,就会经过这个切面,这个切面中定义了@Around(…)拦截所有请求。

下面看下这个方法:

@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 {
        result = CommandExecutor.execute(invokable, executionType, metaHolder);
    } catch (HystrixBadRequestException e) {
        throw e.getCause();
    }
    return result;
}

这个方法中,一开始先获取拦截的Method,然后判断,如果方法上同时加了@HystrixCommand和@HystrixCollapser两个注解的话,就抛异常。

在创建MetaHolder的时候,调用了MetaHolderFactory的create方法,MetaHolderFactory有两个子类,CollapserMetaHolderFactory和CommandMetaHolderFactory,最终执行的是子类的create方法,下面是CommandMetaHolderFactory中的create方法:

private static class CommandMetaHolderFactory extends MetaHolderFactory {
    @Override
    public MetaHolder create(Object proxy, Method method, Object obj, Object[] args, final ProceedingJoinPoint joinPoint) {
        HystrixCommand hystrixCommand = method.getAnnotation(HystrixCommand.class);
        ExecutionType executionType = ExecutionType.getExecutionType(method.getReturnType());
        MetaHolder.Builder builder = metaHolderBuilder(proxy, method, obj, args, joinPoint);
        return builder.defaultCommandKey(method.getName())
                        .hystrixCommand(hystrixCommand)
                        .observableExecutionMode(hystrixCommand.observableExecutionMode())
                        .executionType(executionType)
                        .observable(ExecutionType.OBSERVABLE == executionType)
                        .build();
    }
}
MetaHolder.Builder metaHolderBuilder(Object proxy, Method method, Object obj, Object[] args, final ProceedingJoinPoint joinPoint) {
    MetaHolder.Builder builder = MetaHolder.builder()
            .args(args).method(method).obj(obj).proxyObj(proxy)
            .defaultGroupKey(obj.getClass().getSimpleName())
            .joinPoint(joinPoint);
    if (isCompileWeaving()) {
        builder.ajcMethod(getAjcMethodFromTarget(joinPoint));
    }

    FallbackMethod fallbackMethod = MethodProvider.getInstance().getFallbackMethod(obj.getClass(), method);
    if (fallbackMethod.isPresent()) {
        fallbackMethod.validateReturnType(method);
        builder
                .fallbackMethod(fallbackMethod.getMethod())
                .fallbackExecutionType(ExecutionType.getExecutionType(fallbackMethod.getMethod().getReturnType()));
    }
    return builder;
}

在创建MetaHolder的过程中,就会指定fallback方法。
创建完MetaHolder之后,就会根据MetaHolder创建HystrixInvokable。

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

这段代码里定义了后续真正执行HystrixCommand的GenericCommand实例方法最终会去执行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);
    }
}

这里会分成同步和异步的场景,进入execute方法看下:

public R execute() {
    try {
        return queue().get();
    } catch (Exception e) {
        throw decomposeException(e);
    }
}

这个方法的注释中说明了返回值,可以返回请求的结果,当失败的时候,则会通过getFallback()方法来执行一个回退操作,由于是GenericCommand实例,那就看下这个实例中的getFallback()方法:

@Override
protected Object getFallback() {
    if (getFallbackAction() != null) {
        final CommandAction commandAction = getFallbackAction();
        try {
            return process(new Action() {
                @Override
                Object execute() {
                    MetaHolder metaHolder = commandAction.getMetaHolder();
                    Object[] args = createArgsForFallback(metaHolder, getExecutionException());
                    return commandAction.executeWithArgs(commandAction.getMetaHolder().getFallbackExecutionType(), args);
                }
            });
        } catch (Throwable e) {
            LOGGER.error(FallbackErrorMessageBuilder.create()
                    .append(commandAction, e).build());
            throw new FallbackInvocationException(e.getCause());
        }
    } else {
        return super.getFallback();
    }
}

总结

大体的一个流程也就知道了,就是通过HystrixCommandAspect根据注解拦截请求,请求成功返回接口的结果,请求失败执行fallback的逻辑。

Hystrix 工作原理

当需要完成某项任务时,通过 Hystrix 将任务包裹起来,交由 Hystrix 来完成任务,从而享受 Hystrix 带来保护。这和古代镖局生意有点类似,将任务委托给镖局,以期安全完成任务。Hystrix完成任务的处理流程分成9步:

1、构建命令

Hystrix 提供了两个Command, HystrixCommand 和 HystrixObservableCommand,可以使用这两个对象来包裹待执行的任务。

例如使用 @HystrixCommand 注解标记方法,Hystrix 将利用AOP自动将目标方法包装成HystrixCommand来执行。

@HystrixCommand
public String hello() {
    ...
}

也可以继承HystrixCommand或HystrixObservableCommand来创建Command,例如:

public class MyCommand extends HystrixCommand {
    
    public MyCommand(HystrixCommandGroupKey group) {
        super(group);
    }

    @Override
    protected Object run() throws Exception {
        // 需要做的事情及需要返回的结果
        return null;
    }
}

任务委托给 Hystrix 后,Hystrix 可以应用自己的一系列保护机制,在执行用户任务的各节点(执行前、执行后、异常、超时等)做一系列的事情。

2、执行命令

有四种方式执行command。

  • R execute():同步执行,从依赖服务得到单一结果对象。
  • Future queue():异步执行,返回一个 Future 以便获取执行结果,也是单一结果对象。
  • Observable observe():hot observable,创建Observable后会订阅Observable,可以返回多个结果。
  • Observable toObservable():cold observable,返回一个Observable,只有订阅时才会执行,可以返回多个结果。

execute() 的实现为 queue().get(); queue() 的实现为 toObservable().toBlocking().toFuture()。

最后Obserable都由toObservable()来创建,本文的主要内容就是toObservable()。

// 利用queue()拿到Future, 执行 get()同步等待拿到执行结果
public R execute() {
    ...
    return queue().get();
}

// 利用toObservable()得到Observable最后转成Future
public Future<R> queue() {
    final Future<R> delegate = toObservable().toBlocking().toFuture();
    ...
} 

// 利用toObservable()得到Observable并直接订阅它,立即执行命令
public Observable<R> observe() {
    ReplaySubject<R> subject = ReplaySubject.create();
    final Subscription sourceSubscription = toObservable().subscribe(subject);
    ...
}

3、检查缓存

第3到9步骤构成了 Hystrix 的保护能力,通过这一些列步骤来执行任务,从而起到保护作用。

如果启用了 Hystrix Cache,任务执行前将先判断是否有相同命令执行的缓存。如果有则直接返回缓存的结果;如果没有缓存的结果,但启动了缓存,将缓存本次执行结果以供后续使用。

4.检查断路器是否打开

断路器(circuit-breaker)和保险丝类似,保险丝在发生危险时将会烧断以保护电路,而断路器可以在达到我们设定的阀值时触发短路(比如请求失败率达到50%),拒绝执行任何请求。

如果断路器被打开,Hystrix 将不会执行命令,直接进入Fallback处理逻辑。

5.检查线程池/信号量情况

Hystrix 隔离方式有线程池隔离和信号量隔离。当使用Hystrix线程池时,Hystrix 默认为每个依赖服务分配10个线程,当10个线程都繁忙时,将拒绝执行命令。信号量同理。

6.执行具体的任务

通过HystrixObservableCommand.construct() 或者 HystrixCommand.run() 来运行用户真正的任务。

7.计算链路健康情况

每次开始执行command、结束执行command以及发生异常等情况时,都会记录执行情况,例如:成功、失败、拒绝以及超时等情况,会定期处理这些数据,再根据设定的条件来判断是否开启断路器。

8.命令失败时执行 Fallback 逻辑

在命令失败时执行用户指定的 Fallback 逻辑。上图中的断路、线程池拒绝、信号量拒绝、执行执行、执行超时都会进入 Fallback 处理。

9.返回执行结果

原始结果将以Observable形式返回,在返回给用户之前,会根据调用方式的不同做一些处理。

熔断器

circuit-breaker: circuit表示电路,大家译为熔断器非常精准。

回想起小时候,家里保险丝突然被烧断,需 手工更换一根新的保险丝;后来,保险丝被取代,电流过大时会跳闸,闸拉上去后立马恢复供电;等到上大学时,只要打开功率高一点的电吹风,砰的一声就断电,但过10分钟就自动来电。在电流过大时,通过熔断机制以保护电路和家电。

Hystrix 属于上面的第三种,一种自动恢复的智能熔断器,区别在于它保护的是系统,且判断 “电流过大” 的方式是:不断收集请求指标信息(sucess、failure、timeout、rejection),当达到设定熔断条件时(默认是请求失败率达到50%)进行熔断。

Hystrix Command 执行过程中,各种情况都以事件形式发出,再封装成特定的数据结构,最后汇入到事件流中(HystrixEventStream)。事件流提供了 observe() 方法,摇身一变,事件流把自己变成了一个数据源(各小溪汇入成河,消费者从河里取水),其他消费者可以从这里获取数据,而 circuit-breaker 就是消费者之一。

原理

在统计中,会使用一定数量的样本,并将样本进行分组,最后进行统计分析。

Hystrix 有点类似,例如:以秒为单位来统计请求的处理情况(成功请求数量、失败请求数、超时请求数、被拒绝的请求数),然后每次取最近10秒的数据来进行计算,如果失败率超过50%,就进行熔断,不再处理任何请求。

这是Hystrix官网的一张图:
在这里插入图片描述
它演示了 Hystrix 滑动窗口 策略,假定以秒为单位来统计请求处理情况,上面每个格子代表1秒,格子中的数据就是1秒内各处理结果的请求数量,格子称为 Bucket(译为桶)。

若每次的决策都以10个Bucket的数据为依据,计算10个Bucket的请求处理情况,当失败率超过50%时就熔断。10个Bucket就是10秒,这个10秒就是一个 滑动窗口(Rolling window)。

为什么叫滑动窗口?因为在没有熔断时,每当收集好一个新的Bucket后,就会丢弃掉最旧的一个Bucket。上图中的深色的(23 5 2 0)就是被丢弃的桶,这和拿着放大镜从左到右看书有点类似,视野永远是放大镜那一部分。

下面是官方完整的流程图,策略是:不断收集数据,达到条件就熔断;熔断后拒绝所有请求一段时间(sleepWindow);然后放一个请求过去,如果请求成功,则关闭熔断器,否则继续打开熔断器。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VbfmYbyo-1578382558941)(https://media.chenyongjun.vip/2018/12/10/370cc71eaf0942febff471fcc15359db.png)]
相关配置

默认配置都在HystrixCommandProperties类中。

先看两个metrics收集的配置。

  • metrics.rollingStats.timeInMilliseconds

表示滑动窗口的时间(the duration of the statistical rolling window),默认10000(10s),也是熔断器计算的基本单位。

  • metrics.rollingStats.numBuckets

滑动窗口的Bucket数量(the number of buckets the rolling statistical window is divided into),默认10. 通过timeInMilliseconds和numBuckets可以计算出每个Bucket的时长。

metrics.rollingStats.timeInMilliseconds % metrics.rollingStats.numBuckets 必须等于 0,否则将抛异常。

再看看熔断器的配置。

  • circuitBreaker.requestVolumeThreshold

滑动窗口触发熔断的最小请求数。如果值是20,但滑动窗口的时间内请求数只有19,那即使19个请求全部失败,也不会熔断,必须达到这个值才行,否则样本太少,没有意义。

  • circuitBreaker.sleepWindowInMilliseconds

这个和熔断器自动恢复有关,为了检测后端服务是否恢复,可以放一个请求过去试探一下。sleepWindow指的发生熔断后,必须隔sleepWindow这么长的时间,才能放请求过去试探下服务是否恢复。默认是5s

  • circuitBreaker.errorThresholdPercentage

错误率阈值,表示达到熔断的条件。比如默认的50%,当一个滑动窗口内,失败率达到50%时就会触发熔断。

熔断器状态变化

熔断器有三种状态,如下:

enum Status {
    CLOSED, OPEN, HALF_OPEN;
}

在Command的执行过程中,会调用HystrixCircuitBreaker的方法来更新状态。下面是几个重要的方法:

命令执行时,判断熔断器是否打开

// 是否允许执行
public boolean attemptExecution() {
    // 熔断器配置了强制打开, 不允许执行命令
    if (properties.circuitBreakerForceOpen().get()) {
        return false;
    }
    // 熔断器配置了强制关闭, 允许执行
    if (properties.circuitBreakerForceClosed().get()) {
        return true;
    }
    // AtomicLong circuitOpened, -1是表示熔断器未打开
    if (circuitOpened.get() == -1) {
        return true;
    } else {
        // 熔断后,会拒绝所有命令一段时间(默认5s), 称为sleepWindow
        if (isAfterSleepWindow()) {
            // 过了sleepWindow后,将熔断器设置为"HALF_OPEN",允许第一个请求过去
            if (status.compareAndSet(Status.OPEN, Status.HALF_OPEN)) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
}

当Command成功执行结束时,会调用HystrixCircuitBreaker.markSuccess()来标记执行成功.

public void markSuccess() {
    // 如果是HALF_OPEN状态,则关闭熔断器
    if (status.compareAndSet(Status.HALF_OPEN, Status.CLOSED)) {
		// 重新开始统计metrics,抛弃所有原先的metrics信息
        metrics.resetStream();
        Subscription previousSubscription = activeSubscription.get();
        if (previousSubscription != null) {
            previousSubscription.unsubscribe();
        }
        Subscription newSubscription = subscribeToStream();
        activeSubscription.set(newSubscription);
        // circuitOpened设置为-1表示关闭熔断器
        circuitOpened.set(-1L);
    }
}

当Command执行失败时, 如果熔断器属于HALF_OPEN状态,也就是熔断器刚过sleepWindow时间,尝试放一个请求过去,结果又失败了,于是马上打开熔断器,继续拒绝sleepWindow的时间。

public void markNonSuccess() {
    if (status.compareAndSet(Status.HALF_OPEN, Status.OPEN)) {
        circuitOpened.set(System.currentTimeMillis());
    }
}

这是调用markNonSuccess()的地方,handleFallback是所有失败情况的处理者.

final Func1<Throwable, Observable<R>> handleFallback = new Func1<Throwable, Observable<R>>() {
    @Override
    public Observable<R> call(Throwable t) {
        circuitBreaker.markNonSuccess();
        Exception e = getExceptionFromThrowable(t);
        executionResult = executionResult.setExecutionException(e);
        // 线程池拒绝
        if (e instanceof RejectedExecutionException) {
            return handleThreadPoolRejectionViaFallback(e);
        // 超时
        } else if (t instanceof HystrixTimeoutException) {
            return handleTimeoutViaFallback();
        // Bad Request    
        } else if (t instanceof HystrixBadRequestException) {
            return handleBadRequestByEmittingError(e);
        } else {
            if (e instanceof HystrixBadRequestException) {
                eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey);
                return Observable.error(e);
            }
            return handleFailureViaFallback(e);
        }
    }
};

总结

Circuit-Breaker的设计、实现都很有意思:

  • 滴水成河,收集每个命令的执行情况,汇总后通过滑动窗口,不断动态计算最新统计数据,基于统计数据来开启熔断器
  • 巧妙的利用RxJava的window()函数来汇总数据,先汇总为Bucket, N Bucket组成Rolling Window
  • 使用sleepWindow + 尝试机制,自动恢复 “供电”
隔离策略

Hystrix 里面核心的一项功能,其实就是所谓的资源隔离,要解决的最最核心的问题,就是将多个依赖服务的调用分别隔离到各自的资源池内。避免说对某一个依赖服务的调用,因为依赖服务的接口调用的延迟或者失败,导致服务所有的线程资源全部耗费在这个服务的接口调用上。一旦说某个服务的线程资源全部耗尽的话,就可能导致服务崩溃,甚至说这种故障会不断蔓延。

Hystrix 实现资源隔离,主要有两种技术:

  • 线程池
  • 信号量

默认情况下,Hystrix 使用线程池模式。
在这里插入图片描述
上图的左边2/3是线程池资源隔离示意图,右边的1/3是信号量资源隔离示意图,我们先来看左边的示意图。

线程池模式

当用户请求服务A和服务I的时候,tomcat的线程(图中蓝色箭头标注)会将请求的任务交给服务A和服务I的内部线程池里面的线程(图中橘色箭头标注)来执行,tomcat的线程就可以去干别的事情去了,当服务A和服务I自己线程池里面的线程执行完任务之后,就会将调用的结果返回给tomcat的线程,从而实现资源的隔离,当有大量并发的时候,服务内部的线程池的数量就决定了整个服务的并发度,例如服务A的线程池大小为10个,当同时有12请求时,只会允许10个任务在执行,其他的任务被放在线程池队列中,或者是直接走降级服务,此时,如果服务A挂了,就不会造成大量的tomcat线程被服务A拖死,服务I依然能够提供服务。整个系统不会受太大的影响。

缺点:

1、请求在线程池中执行,肯定会带来任务调度、排队和上下文切换带来的开销。

2、因为涉及到跨线程,那么就存在ThreadLocal数据的传递问题,比如在主线程初始化的ThreadLocal变量,在线程池线程中无法获取。

信号量模式

信号量的资源隔离只是起到一个开关的作用,例如,服务X的信号量大小为10,那么同时只允许10个tomcat的线程(此处是tomcat的线程,而不是服务X的独立线程池里面的线程)来访问服务X,其他的请求就会被拒绝,从而达到限流保护的作用。

二者的比较

线程池隔离技术,并不是说去控制类似 tomcat 这种 web 容器的线程。更加严格的意义上来说,Hystrix 的线程池隔离技术,控制的是 tomcat 线程的执行。Hystrix 线程池满后,会确保说,tomcat 的线程不会因为依赖服务的接口调用延迟或故障而被 hang 住,tomcat 其它的线程不会卡死,可以快速返回,然后支撑其它的事情。

线程池隔离技术,是用 Hystrix 自己的线程去执行调用;而信号量隔离技术,是直接让 tomcat 线程去调用依赖服务。信号量隔离,只是一道关卡,信号量有多少,就允许多少个 tomcat 线程通过它,然后去执行。

适用场景

线程池技术: 适合绝大多数场景,比如说我们对依赖服务的网络请求的调用和访问、需要对调用的 timeout 进行控制(捕捉 timeout 超时异常)。

信号量技术: 适合说你的访问不是对外部依赖的访问,而是对内部的一些比较复杂的业务逻辑的访问,并且系统内部的代码,其实不涉及任何的网络请求,那么只要做信号量的普通限流就可以了,因为不需要去捕获 timeout 类似的问题。

比如说,我们一般来说,缓存服务,可能会将一些量特别少、访问又特别频繁的数据,放在自己的纯内存中。

举个栗子。一般我们在获取到商品数据之后,都要去获取商品是属于哪个地理位置、省、市、卖家等,可能在自己的纯内存中,比如就一个 Map 去获取。对于这种直接访问本地内存的逻辑,比较适合用信号量做一下简单的隔离。

优点在于,不用自己管理线程池啦,不用 care timeout 超时啦,也不需要进行线程的上下文切换啦。信号量做隔离的话,性能相对来说会高一些。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值