Hystrx权威指南--Hystrix是什么

 随着互联网发展,传统的大型机单块应用到后来的分布式计算及发展到现在的微服务,Docker的发展更是让微服务如虎添翼。微服务的优点不必细说,网上一搜一大把,这里就聊一下微服务带来的问题和如何去解决这些问题。通过这些问题分析下Hystrix是如何成为设计微服务利器的。
          微服务带来的问题:
          一、增加服务依赖复杂度:单块应用拆分成微服务之后,原来运行在同一个JVM进程内的功能,可能要分到多个JVM进程中,拆分后的微服务之间通过RPC调用来共同完成之前的功能,这就必然会增加服务依赖的复杂度;

          二、不能FailFast : 原来系统是在同一个JVM进程内的,不存在网络调用,异常就会直接失败,不会导致处理hang住。微服务之后,逻辑功能则是由网络上的另一个服务提供的,如果提供者处理速度很慢,甚至最后会失败(超时),服务的请求端在返回失败之前就会一直hang在这里;另外在一段时间内90%的请求都返回失败,第91个请求就不需要再发送给服务端以增加服务端的压力了,直接fast fail,走后续流程即可。一段时间之后,再次尝试请求。

          三、不能平均分配资源:微服务之后,服务的依赖会增多,每个依赖的服务端性能和吞吐量都不尽相同,这时就要隔离每个依赖所占有的资源。比如某个机器P1有三个服务SA、SB、SC,分别依赖于A、B、C三个依赖,一段时间之后,A依赖的服务端响应速度开始变慢,这时S1所在机器的CPU、线程、IO等资源都会耗尽到A依赖上,而没有空闲CPU、线程、IO等资源去处理B、C的依赖。以至于S1不仅不能对外正常提供SA服务,连SB、SC服务都会受到影响,最后甚至会导致机器P1宕机。

以上提到的这三点主要问题也是Hystrix提供的核心功能:监控、熔断器、隔离。

          Hystrix简介
          Hystrix天生就是为微服务提供的,是由Netflix公司开发,目前为Netflix公司每天处理着数百亿的线程隔离和更多的信号隔离请求,代码托管在Github(https://github.com/Netflix/Hystrix)上。Hystrix的监控可以随时查看一个微服务运行的健康情况,内部线程、信号占用率;熔断器则可以实现快速失败,同时还能保护后端服务;隔离机制更是为微服务的高效运行保驾护航,当某一个依赖出现异常时能够及时进行降级保护。

隔离
          上面提到计算及的CPU、线程、IO等资源都是非常宝贵而且是有限的,当被使用达到上限时,那么距离该机器宕机也就不远了。另外一个容器对外提供的服务会有多个,可能每个服务都有一个依赖,当某一个依赖运行出现异常时,只能使该依赖对应的那个服务对外不可用,而其他的服务都应该是正常运行的。
          而现实中恰恰是当某一个服务不可用时,紧接着第二个第三个服务都会出现问题。这就是没有对依赖进行隔离的原因。因为有一个依赖运行异常,该机器上的所有CPU、线程等资源都过来处理这个依赖资源的请求了,没有空闲的资源去处理其他服务的请求。这时就需要对每个依赖进行资源隔离当某个依赖出现问题,只会有分配给该依赖的线程、IO等处于忙碌状态,其他线程、IO等资源应该正常处理其他的依赖
          Hystrix针对依赖资源的隔离提供了两种策略,分别是:线程池隔离和信号隔离。


为什么用Hystrix       
          在一个复杂的分布式架构系统中,依赖服务是避免不了的,特别是在一个微服务化的时代。如果一个系统不能对相关的依赖进行隔离,那该系统就会有被拖垮被宕机的风险。在一个高并发高访问量的系统中,当一个被依赖的服务出现网络连接缓慢、处理延迟等情况,在几分钟甚至几秒内,所有的线程都会block在被依赖的资源上,进而导致所有资源被耗尽,系统被拖垮。
     所有依赖服务都正常时:
     当其中一个依赖出现延迟时,服务器的大部分线程都会block在依赖I上,在一个高流量的系统中,几秒内所有的线程资源都被blocking在了依赖上,从而导致系统的其他服务也不可用,甚至整个系统被拖垮,不可用。
   Hystrix对依赖进行隔离和控制,通常这些依赖都是网络调用。Hystrix通过线程池和信号进行隔离,给每一个依赖分配指定的资源,当该依赖的资源不足(线程池爆满、信号tryAcquire) 就会直接返回失败,不会占用其他依赖的资源。
   同时,Hystrix还提供了CircuitBreaker机制,当失败率达到某一个阀值时,Hystrix会Fast Fail 并迅速恢复,或者优雅的降级。

环境搭建    
       使用Maven搭建的项目,在pom.xml文件中引入hystrix-core坐标:
       <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-core</artifactId>
            <version>1.4.18</version>
        </dependency>  
      环境搭建好之后就可以开发了,接下来会写如何开发Hystrix应用。

Hystrix开发
       首先还从HelloWorld 开始,首先定义一个Command类,继承 HystrixCommand 类,在构造函数中设置相关参数。重写run() 和 getFallback() ,业务逻辑实现放在run方法里。所有非 HystrixBadRequestException 异常都会调用 getFallback方法,降级方案一般在getFallback方法中实现。
    class HelloWorldCommand extends HystrixCommand<String> {

        protected HelloWorldCommand(String name) {
            super(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(name)));
        }

        @Override
        protected String run() throws Exception {
            System.out.println("run success " + Thread.currentThread().getName());
            return "run success " + Thread.currentThread().getName();
        }
    }
        很简单,在run方法里面返回了当前的线程名字。执行下 TestCase  ,观察打印数据
@Test
    public void helloWorldCommand() {
        HelloWorldCommand helloWorldCommand = new HelloWorldCommand("klov");
        String result = helloWorldCommand.execute();
        System.out.println("【HelloWorldCommand】 result = "+result);
    }
    
       打印数据如下:
      run success hystrix-klov-1
    【HelloWorldCommand】 result = run success hystrix-klov-1
     由输出数据可知线程池的名字默认是 hystrix+commandGroupKey+inde。

     到此,Hystrix的开发环境以及基本的开发已经完成,后面会继续介绍Hystrix两个隔离方式的开发及原理。

  线程池隔离
          使用线程会在以下三个场景带来性能消耗:
           1、线程的创建和销毁;
           2、线程上下文空间的切换,线程池调度需要操作系统介入,系统需要从用户态空间切换到内核态空间,调度完成后又需要切回到用户态空间。
          Hystrix通过使用固定线程池大小的方式解决了第一个问题,在创建线程池时,Hystrix只允许你设置CoreSize,而不允许你设置MaxSize。在系统初始化线程池时,MaxSize等于CoreSize。这样就避免了线程的频繁创建和销毁带来的性能消耗。Hystrix的线程池创建源码是在 HystrixConcurrencyStrategy类getThreadPool方法 中实现的:
public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize, HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        return new ThreadPoolExecutor(corePoolSize.get(), maximumPoolSize.get(), keepAliveTime.get(), unit, workQueue, new ThreadFactory() {

            protected final AtomicInteger threadNumber = new AtomicInteger(0);

            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r, "hystrix-" + threadPoolKey.name() + "-" + threadNumber.incrementAndGet());
                thread.setDaemon(true);
                return thread;
            }
        });
    }
跟普通的创建线程池没什么区别,根据传进来的线程池参数创建一个线程池。 但是玄机出现在调用的地方,传递的corePoolSize和maximumPoolSize是同一个值 。调用方是HystrixThreadPool类的173行

public HystrixThreadPoolDefault(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter propertiesDefaults) {
            this.properties = HystrixPropertiesFactory.getThreadPoolProperties(threadPoolKey, propertiesDefaults);
            HystrixConcurrencyStrategy concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
            this.queueSize = properties.maxQueueSize().get();
            this.queue = concurrencyStrategy.getBlockingQueue(queueSize);
            this.metrics = HystrixThreadPoolMetrics.getInstance(
                    threadPoolKey,
                    //这里是调用线程池创建的地方,corePoolSize和maximumPoolSize传递的都是coreSize()这个值
                    concurrencyStrategy.getThreadPool(threadPoolKey,properties.coreSize(), properties.coreSize(), properties.keepAliveTimeMinutes(), TimeUnit.MINUTES, queue),
                    properties);
            this.threadPool = metrics.getThreadPool();

            /* strategy: HystrixMetricsPublisherThreadPool */
            HystrixMetricsPublisherFactory.createOrRetrievePublisherForThreadPool(threadPoolKey, this.metrics, this.properties);
        }

  信号量隔离
          为解决线程池隔离带来的第二性能开销的场景,Hystrix使用了信号量隔离。信号量隔离通过原子操作类AtoInteger实现Permits的管理,AtoInteger类使用的是CAS操作,相对于锁,CAS是通过硬件实现的原子操作,减小了性能开销。下面是获取Permit的源码分析:
public boolean tryAcquire() {
          //count是AtoInteger类型的成员变量
            int currentCount = count.incrementAndGet();
            if (currentCount > numberOfPermits.get()) {
                count.decrementAndGet();
                return false;
            } else {
                return true;
            }
        }
 
tryAcquire获取Permit成功,调用方要在finally中释放Permit,调用release方法。
 public void release() {
          //对Permit进行减一操作
            count.decrementAndGet();
        }       

  建议不同的业务之间通过线程池隔离,同一个业务不同的依赖资源则可以通过信号量隔离,以提高吞吐量和性能

熔断器
          熔断器的功能等同于家庭电路中的自动跳闸器,当电路中流经的电流负荷过高或者有漏电等非安全用电情况时,跳闸器就会自动跳闸,起到对家用电器保护的作用。软件系统中的熔断器和跳闸器是类似的,当请求回路中发生异常时,熔断器打开。只是Hystrix会在一段时间后试着关闭回路,让部分请求发送成功,以检测异常是否恢复。
  熔断器机制是指,在后端服务可用率降低到阀值以下时,新来的请求不再发给后端服务,直接返回请求失败即可,以实现 Fail-Fast机制,快速给用户请求,执行后续的失败流程。这样既可以拦截不必要的请求,减少对后端本来就异常的服务的压力,还可以实现Fail-Fast机制。  Hystrix默认熔断器机制是开启的 ,可以在 HystrixCommand的run方法中设置为开启 ,设置代码如下:
HystrixCommandProperties.Setter().withCircuitBreakerEnabled(true)

开启熔断器机制后, Hystrix默认的阀值是50% 。如果在 10秒内请求失败率 达到50%及以上,Hystrix会自动断开回路,后面的请求不会再被发往后端的服务器中,会直接返回给客户端。 5秒之后 ,Hystrix会试着关闭回路,放一部分请求过去,以检测异常是否恢复。

HystrixCircuitBreakerImpl类的allowSingleTest方法中实现该功能,源码如下:

public boolean allowSingleTest() {
            long timeCircuitOpenedOrWasLastTested = circuitOpenedOrLastTestedTime.get();
            // 1) if the circuit is open
            // 2) and it's been longer than 'sleepWindow' since we opened the circuit
            if (circuitOpen.get() && System.currentTimeMillis() > timeCircuitOpenedOrWasLastTested +properties.circuitBreakerSleepWindowInMilliseconds().get()) {
                // We push the 'circuitOpenedTime' ahead by 'sleepWindow' since we have allowed one request to try.
                // If it succeeds the circuit will be closed, otherwise another singleTest will be allowed at the end of the 'sleepWindow'.
                if (circuitOpenedOrLastTestedTime.compareAndSet(timeCircuitOpenedOrWasLastTested, System.currentTimeMillis())) {
                    // if this returns true that means we set the time so we'll return true to allow the singleTest
                    // if it returned false it means another thread raced us and allowed the singleTest before we did
                    return true;
                }
            }
            return false;
        }
properties.circuitBreakerSleepWindowInMilliseconds().get()取的值就是设置的间隔时间,默认是5秒 。在每次打开熔断器时都要保存当前时间,在下次决策是否需要关闭回路时,判断距离上次开启时间是否达到设置的值。在Hystrix中大量使用了CAS在保证成员变量的原子操作前提下,又提高了性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值