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. 给vs2015添加EF

    今天做EF的小例子时,发现需要添加实体数据模型,但是不管怎么找在新建项中都找不到这个选项,这是怎么回事,于是就开始百度吧,有的说可能是VS安装时没有全选,也有的人说可能是重装VS时,没有将注册表清除, ...

  2. WebApi系列~不支持put和delete请求的解决方法

    回到目录 原因 由于安装了webDAV模块引起的,在web.config里的system.webserver节点,将webdav模块移动 将http请求的权限开放 实现 <system.webS ...

  3. [总结] NOIP 前的考试记录

    sb博主又犯sb错误了! 他觉得以往模拟赛因为犯sb错误扔的分足足有1k分了! 于是他想记录一下自己犯的sb错误看看自己到底有多sb! 嗯就从今天开始吧 2018.9.28 1. 二分边界写错.骚什么 ...

  4. 一统江湖的大前端(1)——PPT制作库impress.js

    <一统江湖的大前端>系列是自己的学习笔记,旨在介绍javascript在非网页开发领域的应用案例和发现各类好玩的js库,不定期更新.如果你对前端的理解还是写写页面绑绑事件,那你真的是有点O ...

  5. 第43章 添加更多API端点 - Identity Server 4 中文文档(v1.0.0)

    您可以向托管IdentityServer4的应用程序添加更多API端点. 您通常希望通过它们所托管的IdentityServer实例来保护这些API.这不是问题.只需将令牌验证处理程序添加到主机(请参 ...

  6. python爬虫项目-爬取雪球网金融数据(关注、持续更新)

    (一)python金融数据爬虫项目 爬取目标:雪球网(起始url:https://xueqiu.com/hq#exchange=CN&firstName=1&secondName=1_ ...

  7. PostGIS计算矢量切片(一)--渲染数据

        没写错,是使用postgis计算出来矢量切片.在这之前先准备一个数据:一个GIS数据表(本例中数据为一百万的点数据,坐标:4326),并在表中添加x,y字段,方便后面的数据筛选.sql中用到了 ...

  8. TLS / SSL密码强化的建议

    TLS / SSL密码强化的建议 传输层安全性(TLS)及其前身安全套接字层(SSL)是广泛使用的协议,旨在通过身份验证,加密和完整性来保护客户端和服务器之间的数据传输安全. 与常见假设相反,TLS ...

  9. 解决ViewGroup不调用onDraw()的问题

    今天在做项目的时候自定义了一个View,继承了LinearLayout,结果,里面的onDraw()方法一直无法被调用. 后来发现ViewGroup是默认不调用onDraw()方法的. 原因我们暂且不 ...

  10. C++ 浅拷贝与深拷贝探究

    C++浅拷贝与深拷贝探究 浅拷贝与深拷贝的概念是在类的复制/拷贝构造函数中出现的. 拷贝构造函数使用场景 对象作为参数,以值传递方式传入函数(要调用拷贝构造函数将实参拷贝给函数栈中的形参) 对象作为返 ...