微服务利器--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宕机。这里引用两张图来表示:
下面这张图是当所有依赖都能正常工作时:

当其中一个依赖I的响应速度开始变慢时,所以的线程都会分配给依赖I,并且都会被hang在这里,这时其他的功能也无法提供服务。


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

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

         隔离
          上面提到计算及的CPU、线程、IO等资源都是非常宝贵而且是有限的,当被使用达到上限时,那么距离该机器宕机也就不远了。另外一个容器对外提供的服务会有多个,可能每个服务都有一个依赖,当某一个依赖运行出现异常时,只能使该依赖对应的那个服务对外不可用,而其他的服务都应该是正常运行的。
          而现实中恰恰是当某一个服务不可用时,紧接着第二个第三个服务都会出现问题。这就是没有对依赖进行隔离的原因。因为有一个依赖运行异常,该机器上的所有CPU、线程等资源都过来处理这个依赖资源的请求了,没有空闲的资源去处理其他服务的请求。这时就需要对每个依赖进行资源隔离。当某个依赖出现问题,只会有分配给该依赖的线程、IO等处于忙碌状态,其他线程、IO等资源应该正常处理其他的依赖。
          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在保证成员变量的原子操作前提下,又提高了性能。
         监控
                 

更多精彩博文,关注



  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值