1. 什么是Hystrix

Hystrix是Netflix的一个开源框架,地址如下:https://github.com/Netflix/Hystrix

中文名为“豪猪”,即平时很温顺,在感受到危险的时候,用刺保护自己;在危险过去后,还是一个温顺的肉球。

所以,整个框架的核心业务也就是这2点:

  1. 何时需要保护

  2. 如何保护

2. 何时需要保护

对于一个系统而言,它往往承担着2层角色,服务提供者与服务消费者。对于服务消费者而言最大的痛苦就是如何“明哲保身”,做过网关项目的同学肯定感同身受

上面是一个常见的系统依赖关系,底层的依赖往往很多,通信协议包括 socket、HTTP、Dubbo、WebService等等。当通信层发生网络抖动以及所依赖的系统发生业务响应异常时,我们业务本身所提供的服务能力也直接会受到影响。

这种效果传递下去就很有可能造成雪崩效应,即整个业务联调发生异常,比如业务整体超时,或者订单数据不一致。

那么核心问题就来了,如何检测业务处于异常状态?

成功率!成功率直接反映了业务的数据流转状态,是最直接的业务表现。

当然,也可以根据超时时间做判断,比如 Sentinel 的实现。其实这里概念上可以做一个转化,用时间做超时控制,超时=失败,这依然是一个成功率的概念。

3. 如何保护 

如同豪猪一样,“刺”就是他的保护工具,所有的攻击都会被刺无情的怼回去。

在 Hystrix 的实现中,这就出现了“熔断器”的概念,即当前的系统是否处于需要保护的状态。

当熔断器处于开启的状态时,所有的请求都不会真正的走之前的业务逻辑,而是直接返回一个约定的信息,即 FallBack。通过这种快速失败原则保护我们的系统。

但是,系统不应该永远处于“有刺”的状态,当危险过后需要恢复正常。

于是对熔断器的核心操作就是如下几个功能:

  1. 如果成功率过低,就打开熔断器,阻止正常业务

  2. 随着时间的流动,熔断器处于半打开状态,尝试性放入一笔请求

  熔断器的核心 API 如下图:

4. 限流、熔断、隔离、降级

这四个概念是我们谈起微服务会经常谈到的概念,这里我们讨论的是 Hystrix 的实现方式。

 

限流

  • 这里的限流与 Guava 的 RateLimiter 的限流差异比较大,一个是为了“保护自我”,一个是“保护下游”

  • 当对服务进行限流时,超过的流量将直接 Fallback,即熔断。而 RateLimiter 关心的其实是“流量整形”,将不规整流量在一定速度内规整

熔断

  • 当我的应用无法提供服务时,我要对上游请求熔断,避免上游把我压垮

  • 当我的下游依赖成功率过低时,我要对下游请求熔断,避免下游把我拖垮

降级

  • 降级与熔断紧密相关,熔断后业务如何表现,约定一个快速失败的 Fallback,即为服务降级

隔离

  • 业务之间不可互相影响,不同业务需要有独立的运行空间

  • 最彻底的,可以采用物理隔离,不同的机器部

  • 次之,采用进程隔离,一个机器多个 Tomcat

  • 次之,请求隔离

  • 由于 Hystrix 框架所属的层级为代码层,所以实现的是请求隔离,线程池或信号量

5. 源码分析

先上一个 Hystrix 的业务流程图

可以看到 Hystrix 的请求都要经过 HystrixCommand 的包装,其核心逻辑在 AbstractComman.java 类中。

下面的源码是基于 RxJava 的,看之前最好先了解下 RxJava 的常见用法与逻辑,否则看起来会很迷惑。

简单的说,RxJava 就是基于回调的函数式编程。通俗的说,就等同于策略模式的匿名内部类实现。

5.1 熔断器

首先看信号量是如何影响我们请求的:

private Observable applyHystrixSemantics(final AbstractCommand _cmd) {
       // 自定义扩展
       executionHook.onStart(_cmd);        //判断熔断器是否允许请求过来
       if (circuitBreaker.attemptExecution()) {
       //获得分组信号量,如果没有采用信号量分组,返回默认通过的信号量实现
           final TryableSemaphore executionSemaphore = getExecutionSemaphore();
           final AtomicBoolean semaphoreHasBeenReleased = new AtomicBoolean(false);
       //调用终止的回调函数
           final Action0 singleSemaphoreRelease = new Action0() {
               @Override
               public void call() {
                   if (semaphoreHasBeenReleased.compareAndSet(false, true)) {
                       executionSemaphore.release();
                   }
               }
           };
       //调用异常的回调函数
           final Action1 markExceptionThrown = new Action1() {
               @Override
               public void call(Throwable t) {
                   eventNotifier.markEvent(HystrixEventType.EXCEPTION_THROWN, commandKey);
               }
           };
       //根据信号量尝试竞争信号量
           if (executionSemaphore.tryAcquire()) {
               try {
                   //竞争成功,注册执行参数
                   executionResult = executionResult.setInvocationStartTime(System.currentTimeMillis());
                   return executeCommandAndObserve(_cmd)
                           .doOnError(markExceptionThrown)
                           .doOnTerminate(singleSemaphoreRelease)
                           .doOnUnsubscribe(singleSemaphoreRelease);
               } catch (RuntimeException e) {
                   return Observable.error(e);
               }
           } else {
          //竞争失败,进入fallback
               return handleSemaphoreRejectionViaFallback();
           }
       } else {
        //熔断器已打开,进入fallback
           return handleShortCircuitViaFallback();
       }
   }

什么时候熔断器可以放请求进来:

@Override
       public boolean attemptExecution() {
       //动态属性判断,熔断器是否强制开着,如果强制开着,就不允许请求
           if (properties.circuitBreakerForceOpen().get()) {
               return false;
           }
       //如果强制关闭,就允许请求
           if (properties.circuitBreakerForceClosed().get()) {
               return true;
           }
       //如果当前是关闭,就允许请求
           if (circuitOpened.get() == -1) {
               return true;
           } else {
          //如果当前开着,就看是否已经过了"滑动窗口",过了就可以请求,不过就不可以
               if (isAfterSleepWindow()) {
                   //only the first request after sleep window should execute
                   //if the executing command succeeds, the status will transition to CLOSED
                   //if the executing command fails, the status will transition to OPEN
                   //if the executing command gets unsubscribed, the status will transition to OPEN
            //这里使用CAS的方式,只有一个请求能过来,即"半关闭"状态
                   if (status.compareAndSet(Status.OPEN, Status.HALF_OPEN)) {
                       return true;
                   } else {
                       return false;
                   }
               } else {
                   return false;
               }
           }
       }
   }

这里有个重要概念就是"滑动窗口":

private boolean isAfterSleepWindow() {
           final long circuitOpenTime = circuitOpened.get();
           final long currentTime = System.currentTimeMillis();
           final long sleepWindowTime = properties.circuitBreakerSleepWindowInMilliseconds().get();
       //滑动窗口的判断就是看看熔断器打开的时间与现在相比是否超过了配置的滑动窗口
           return currentTime > circuitOpenTime + sleepWindowTime;
       }

5.2 隔离

如果将业务请求进行隔离?

private Observable executeCommandWithSpecifiedIsolation(final AbstractCommand _cmd) {
     //判断隔离策略是什么,是线程池隔离还是信号量隔离    
       if (properties.executionIsolationStrategy().get() == ExecutionIsolationStrategy.THREAD) {
           // mark that we are executing in a thread (even if we end up being rejected we still were a THREAD execution and not SEMAPHORE)
       //线程池隔离的运行逻辑如下
           return Observable.defer(new Func0<observable>() {
               @Override
               public Observable call() {
                   executionResult = executionResult.setExecutionOccurred();
                   if (!commandState.compareAndSet(CommandState.OBSERVABLE_CHAIN_CREATED, CommandState.USER_CODE_EXECUTED)) {
                       return Observable.error(new IllegalStateException("execution attempted while in state : " + commandState.get().name()));
                   }
            //按照配置生成监控数据
                   metrics.markCommandStart(commandKey, threadPoolKey, ExecutionIsolationStrategy.THREAD);                    if (isCommandTimedOut.get() == TimedOutStatus.TIMED_OUT) {
                       // the command timed out in the wrapping thread so we will return immediately
                       // and not increment any of the counters below or other such logic
                       return Observable.error(new RuntimeException("timed out before executing run()"));
                   }
                   if (threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.STARTED)) {
                       //we have not been unsubscribed, so should proceed
                       HystrixCounters.incrementGlobalConcurrentThreads();
                       threadPool.markThreadExecution();
                       // store the command that is being run
                       endCurrentThreadExecutingCommand = Hystrix.startCurrentThreadExecutingCommand(getCommandKey());
                       executionResult = executionResult.setExecutedInThread();
                       /**
                        * If any of these hooks throw an exception, then it appears as if the actual execution threw an error
                        */
                       try {
                 //执行扩展点逻辑
                           executionHook.onThreadStart(_cmd);
                           executionHook.onRunStart(_cmd);
                           executionHook.onExecutionStart(_cmd);
                           return getUserExecutionObservable(_cmd);
                       } catch (Throwable ex) {
                           return Observable.error(ex);
                       }
                   } else {
                       //command has already been unsubscribed, so return immediately
                       return Observable.empty();
                   }
               }
        //注册各种场景的回调函数
           }).doOnTerminate(new Action0() {
               @Override
               public void call() {
                   if (threadState.compareAndSet(ThreadState.STARTED, ThreadState.TERMINAL)) {
                       handleThreadEnd(_cmd);
                   }
                   if (threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.TERMINAL)) {
                       //if it was never started and received terminal, then no need to clean up (I don't think this is possible)
                   }
                   //if it was unsubscribed, then other cleanup handled it
               }
           }).doOnUnsubscribe(new Action0() {
               @Override
               public void call() {
                   if (threadState.compareAndSet(ThreadState.STARTED, ThreadState.UNSUBSCRIBED)) {
                       handleThreadEnd(_cmd);
                   }
                   if (threadState.compareAndSet(ThreadState.NOT_USING_THREAD, ThreadState.UNSUBSCRIBED)) {
                       //if it was never started and was cancelled, then no need to clean up
                   }
                   //if it was terminal, then other cleanup handled it
               }
        //将逻辑放在线程池的调度器上执行,即将上述逻辑放入线程池中
           }).subscribeOn(threadPool.getScheduler(new Func0() {
               @Override
               public Boolean call() {
                   return properties.executionIsolationThreadInterruptOnTimeout().get() && _cmd.isCommandTimedOut.get() == TimedOutStatus.TIMED_OUT;
               }
           }));
       } else {
        //走到这里就是信号量隔离,在当前线程中执行,没有调度器
           return Observable.defer(new Func0<observable>() {
               @Override
               public Observable call() {
                   executionResult = executionResult.setExecutionOccurred();
                   if (!commandState.compareAndSet(CommandState.OBSERVABLE_CHAIN_CREATED, CommandState.USER_CODE_EXECUTED)) {
                       return Observable.error(new IllegalStateException("execution attempted while in state : " + commandState.get().name()));
                   }                    metrics.markCommandStart(commandKey, threadPoolKey, ExecutionIsolationStrategy.SEMAPHORE);
                   // semaphore isolated
                   // store the command that is being run
                   endCurrentThreadExecutingCommand = Hystrix.startCurrentThreadExecutingCommand(getCommandKey());
                   try {
                       executionHook.onRunStart(_cmd);
                       executionHook.onExecutionStart(_cmd);
                       return getUserExecutionObservable(_cmd);  //the getUserExecutionObservable method already wraps sync exceptions, so this shouldn't throw
                   } catch (Throwable ex) {
                       //If the above hooks throw, then use that as the result of the run method
                       return Observable.error(ex);
                   }
               }
           });
       }
   }

5.3 核心运行流程

private Observable executeCommandAndObserve(final AbstractCommand _cmd) {
       final HystrixRequestContext currentRequestContext = HystrixRequestContext.getContextForCurrentThread();
     //执行发生的回调
       final Action1 markEmits = new Action1() {
           @Override
           public void call(R r) {
               if (shouldOutputOnNextEvents()) {
                   executionResult = executionResult.addEvent(HystrixEventType.EMIT);
                   eventNotifier.markEvent(HystrixEventType.EMIT, commandKey);
               }
               if (commandIsScalar()) {
                   long latency = System.currentTimeMillis() - executionResult.getStartTimestamp();
                   eventNotifier.markEvent(HystrixEventType.SUCCESS, commandKey);
                   executionResult = executionResult.addEvent((int) latency, HystrixEventType.SUCCESS);
                   eventNotifier.markCommandExecution(getCommandKey(), properties.executionIsolationStrategy().get(), (int) latency, executionResult.getOrderedList());
                   circuitBreaker.markSuccess();
               }
           }
       };
     //执行成功的回调,标记下状态,熔断器根据这个状态维护熔断逻辑
       final Action0 markOnCompleted = new Action0() {
           @Override
           public void call() {
               if (!commandIsScalar()) {
                   long latency = System.currentTimeMillis() - executionResult.getStartTimestamp();
                   eventNotifier.markEvent(HystrixEventType.SUCCESS, commandKey);
                   executionResult = executionResult.addEvent((int) latency, HystrixEventType.SUCCESS);
                   eventNotifier.markCommandExecution(getCommandKey(), properties.executionIsolationStrategy().get(), (int) latency, executionResult.getOrderedList());
                   circuitBreaker.markSuccess();
               }
           }
       };
     //执行失败的回调
       final Func1<throwable, observable<r="">> handleFallback = new Func1<throwable, observable<r="">>() {
           @Override
           public Observable call(Throwable t) {
               circuitBreaker.markNonSuccess();
               Exception e = getExceptionFromThrowable(t);
               executionResult = executionResult.setExecutionException(e);
          //各种回调进行各种fallback
               if (e instanceof RejectedExecutionException) {
                   return handleThreadPoolRejectionViaFallback(e);
               } else if (t instanceof HystrixTimeoutException) {
                   return handleTimeoutViaFallback();
               } else if (t instanceof HystrixBadRequestException) {
                   return handleBadRequestByEmittingError(e);
               } else {
                   /*
                    * Treat HystrixBadRequestException from ExecutionHook like a plain HystrixBadRequestException.
                    */
                   if (e instanceof HystrixBadRequestException) {
                       eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey);
                       return Observable.error(e);
                   }                    return handleFailureViaFallback(e);
               }
           }
       };        final Action1<notification<? <span="" class="hljs-keyword">super R>> setRequestContext = new Action1<notification<? <span="" class="hljs-keyword">super R>>() {
           @Override
           public void call(Notificationsuper R> rNotification) {
               setRequestContextIfNeeded(currentRequestContext);
           }
       };        Observable execution;
       if (properties.executionTimeoutEnabled().get()) {
           execution = executeCommandWithSpecifiedIsolation(_cmd)
                   .lift(new HystrixObservableTimeoutOperator(_cmd));
       } else {
           execution = executeCommandWithSpecifiedIsolation(_cmd);
       }
     //注册各种回调函数
       return execution.doOnNext(markEmits)
               .doOnCompleted(markOnCompleted)
               .onErrorResumeNext(handleFallback)
               .doOnEach(setRequestContext);
   }

6. 小结

  • Hystrix 是基于单机应用的熔断限流框架

  • 根据熔断器的滑动窗口判断当前请求是否可以执行

  • 线程竞争实现“半关闭”状态,拿一个请求试试是否可以关闭熔断器

  • 线程池隔离将请求丢到线程池中运行,限流依靠线程池拒绝策略

  • 信号量隔离在当前线程中运行,限流依靠并发请求数

  • 当信号量竞争失败/线程池队列满,就进入限流模式,执行 Fallback

  • 当熔断器开启,就熔断请求,执行 Fallback 

  • 整个框架采用的 RxJava 的编程模式,回调函数满天飞 

Hystrix是个什么玩意儿的更多相关文章

  1. .NET Core微服务之基于Polly+AspectCore实现熔断与降级机制

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.熔断.降级与AOP 1.1 啥是熔断? 在广义的解释中,熔断主要是指为控制股票.期货或其他金融衍生产品的交易风险,为其单日价格波动幅度 ...

  2. Polly+AspectCore实现熔断与降级机制

    Polly+AspectCore实现熔断与降级机制 https://www.cnblogs.com/edisonchou/p/9159644.html 一.熔断.降级与AOP 1.1 啥是熔断? 在广 ...

  3. .NET Core微服务之基于Steeltoe使用Hystrix熔断保护与监控

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 =>  Steeltoe目录快速导航: 1. 基于Steeltoe使用Spring Cloud Eureka 2. 基于Steelt ...

  4. 使用Hystrix提高系统可用性

    今天稍微复杂点的互联网应用,服务端基本都是分布式的,大量的服务支撑起整个系统,服务之间也难免有大量的依赖关系,依赖都是通过网络连接起来. (图片来源:https://github.com/Netfli ...

  5. Hystrix框架5--请求缓存和collapser

    简介 在Hystrix中有个Request的概念,有一些操作需要在request中进行 缓存 在Hystrix调用服务时,如果只是查询接口,可以使用缓存进行优化,从而跳过真实访问请求. 应用 需要启用 ...

  6. Hystrix框架4--circuit

    circuit 在Hystrix调用服务时,难免会遇到异常,如对方服务不可用,在这种情况下如果仍然不停地调用就是不必要的,在Hystrix中可以配置使用circuit,当达到一定程度错误,就会自动调用 ...

  7. Hystrix框架3--线程池

    线程池 在Hystrix中Command默认是运行在一个单独的线程池中的,线程池的名称是根据设定的ThreadPoolKey定义的,如果没有设置那么会使用CommandGroupKey作为线程池. 这 ...

  8. Hystrix框架2--超时

    timeout 在调用第三方服务时有些情况需要对服务响应时间进行把控,当超时的情况下进行fallback的处理 下面来看下超时的案例 public class CommandTimeout exten ...

  9. Hystrix框架1--入门

    介绍 在开发应用中或多或少会依赖各种外界的服务,利用各个服务来完成自己的业务需求,现在流行的微服务架构更是离不开各个服务之间的调用,这就导致整体应用的可用性依赖于各个依赖服务的可用性. 比如一个依赖3 ...

随机推荐

  1. asp.net core 系列 1 概述

    一.   概述 ASP.NET Core 是一个跨平台的高性能开源框架,可以用来:建置 Web 应用程序和服务.IoT应用和移动后端.在 Windows macOS 和 Linux 上使用喜爱的开发工 ...

  2. Chapter 5 Blood Type——2

    The rest of the morning passed in a blur. 早上剩下的时间都在模糊中度过了. It was difficult to believe that I hadn't ...

  3. xxl-job入门实践

    源码在码云上(git@gitee.com:lynch168/spring-boot.git) 1.下载xxl-job源码 xxl-job源码地址:https://github.com/xuxueli/ ...

  4. SQL语句查询表结构

    SQL语句查询表结构   刚刚在做一个小项目,数据库中一张表有20来个字段,用我以前做的一个.NET实体类生成器一个一个的输入还是闲麻烦,于是打算找个时间来重新的改造一个那个.NET实体类,能够通过选 ...

  5. Disconf源码分析之启动过程分析下(2)

    接上文,下面是第二次扫描的XML配置. <bean id="disconfMgrBean2" class="com.baidu.disconf.client.Dis ...

  6. SpringBoot基础系列-使用日志

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9996897.html SpringBoot基础系列-使用日志 概述 SpringBoot ...

  7. formData批量上传的多种实现

    前言 最近项目需要批量上传附件,查了下资料,网上很多但看着一脸懵,只贴部分代码,介绍也不详细,这里记录一下自己的采坑与多种实现,以免以后忘记. 这里先介绍下FormData对象,以下内容摘自:http ...

  8. 常见MQTT服务器搭建[转载]

    简介 MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,它比较适合于在低带宽.不可靠的网络的进行远程传感器和控制设备通 ...

  9. 设计模式-建造者模式(Builder)

    简介: 将一个复杂的对象的构建与它的表示分离,使得同样的构建过程创建出不同的表示. 结构图: 优点: 使得创建代码和表示代码分离,建造者隐藏了该产品是如何组装的,所以若需要改变一个产品内部的表示,只需 ...

  10. winform中获取指定文件夹下的所有图片

    方法一: C#的IO自带了一个方法DirectoryInfo dir = new DirectoryInfo("文件夹名称");dir.getFiles();//这个方法返回值就是 ...