隔离服务
          计算机的线程、内存等资源是有上限的,达到上限时,离系统被拖垮宕机的时间就不短了。特别是访问网络资源,由于网络的不稳定性,被依赖资源的不稳定性都可能出现处理延迟。在一个高并发高流量的互联网系统中,一旦其中有一个依赖处理延迟,瞬间系统的所有线程和内存都会被这一个依赖所占用,导致其他服务也没有资源处理,甚至整个系统被宕机。
          Hystrix提供了对访问资源的隔离机制,对每一个依赖分配合理的资源。如果一个依赖处理延迟,也只是分配给他的资源被占用,不会影响其他服务应有的资源。从而保证了系统能够高效稳定运行,继续提供其他服务。
          Hystrix组件提供了两种隔离的解决方案:线程池隔离和信号量隔离。两种隔离方式都是限制对共享资源的并发访问量,线程在就绪状态、运行状态、阻塞状态、终止状态间转变时需要由操作系统调度,占用很大的性能消耗;而信号量是在访问共享资源时,进行tryAcquire,tryAcquire成功才允许访问共享资源。

线程池隔离
     客户端(lib库,网络调用等等)都是在单独的线程上执行。从调用线程(Tomcat线程池)上隔离他们,以便用户可以直接响应一个耗时的依赖调用。
     
    

     线程池隔离一般用于不同业务间的隔离,防止相互间的影响。线程池隔离,同样是继承HystrixCommand , 重写 run方法,在里面实现业务逻辑。
           protected HelloCommandIsolateThreadPool(String name) {
            super(HystrixCommand.Setter.
                    //设置GroupKey 用于dashboard 分组展示
                            withGroupKey(HystrixCommandGroupKey.Factory.asKey("hello"))
                            //设置commandKey 用户隔离线程池,不同的commandKey会使用不同的线程池
                    .andCommandKey(HystrixCommandKey.Factory.asKey("hello" + name))
                            //设置线程池名字的前缀,默认使用commandKey
                    .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("hello$Pool" + name))
                            //设置线程池相关参数
                    .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
                            .withCoreSize(15)
                            .withMaxQueueSize(10)
                            .withQueueSizeRejectionThreshold(2))
                            //设置command相关参数
                    .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                            //是否开启熔断器机制
                            .withCircuitBreakerEnabled(true)
                                    //舱壁隔离策略
                            .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
                                    //circuitBreaker打开后多久关闭
                            .withCircuitBreakerSleepWindowInMilliseconds(5000)));
        }

            //舱壁隔离策略
            .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD) 设置隔离策略是关键,默认是信号隔离,如果不设置为线程池隔离,上面设置的线程池相关的参数都无意义。开启熔断器机制,如果在10秒内50%以上的请求都失败,回路就会被断开,后面的请求都会直接返回失败,即 Fast Fail 策略。 withCircuitBreakerSleepWindowInMilliseconds 5秒后会尝试闭合电路 。
          在系统内部,线程池存放在一个ConcurrentHashMap中,key是commandKey ,value就是线程池。线程池的名字是 ThreadPoolKey值。
          为避免在系统运行过程中,频繁的创建新的线程,过段时间又销毁线程,在Hystrix系统内部,线程池的最大线程数和核心线程数是同样大小,所以设置时,只有一个CoreSize参数需要设置。
 
信号隔离      
          线程计算机中有限的宝贵资源,线程的调度也需要由用户空间切换到操作系统空间。可以通过Semaphore或者counts限制对依赖资源的并发访问量。 如果是同一个业务部同资源的隔离,建议使用信号隔离。在需要申请资源时,先去try获取一个permit。在获取的过程中不需要操作系统参与,所以相比于线程来说,信号是个轻量级的隔离方式。
          Hystrix库中的信号类 TryableSemaphore 是JDK库的优化版,内部是通过一个AtomicInteger类型的变量来存储permitCount的。之所以说是JDK的优化版,TryableSemaphore 在tryAcquire时不会阻塞,每次申请一个permit成功后,permitCount就会incrementAndGet,释放资源时,permitCount就会decrementAndGet。而JDK版本的Semaphore 是通过AQS实现的,内部逻辑复杂,并且在tryAcquire时,会阻塞。为什么不用JDK版本的Semaphore官方给的答案是:
Semaphore that only supports tryAcquire and never blocks and that supports a dynamic permit count.
Using AtomicInteger increment/decrement instead of java.util.concurrent.Semaphore since we don't need blocking and need a custom implementation to get the dynamic permit count and since AtomicInteger achieves the same behavior and performance without the more complex implementation of the actual Semaphore class using AbstractQueueSynchronizer.

     在开发时,跟线程池隔离类似,同样是继承HystrixCommand类,在run方法中实现业务逻辑,通过getFallback 实现优雅降级。只是在设置隔离策略及相关参数数有较小的变化:
      protected HelloCommandIsolateSemaphore(String key, int semaphoreCount) {
            super(HystrixCommand.Setter
                    //设置GroupKey 用于dashboard 分组展示
                    .withGroupKey(HystrixCommandGroupKey.Factory.asKey("hello"))
                            //设置CommandKey 用于Semaphore分组,相同的CommandKey属于同一组隔离资源
                    .andCommandKey(HystrixCommandKey.Factory.asKey("hello" + key))
                            //设置隔离级别:Semaphore
                    .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                            //是否开启熔断器机制
                            .withCircuitBreakerEnabled(true)
                                    //舱壁隔离策略
                            .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
                                    //设置每组command可以申请的permit最大数
                            .withExecutionIsolationSemaphoreMaxConcurrentRequests(50)
                                    //circuitBreaker打开后多久关闭
                            .withCircuitBreakerSleepWindowInMilliseconds(5000)));
        }

   .withExecutionIsolationSemaphoreMaxConcurrentRequests(50 )这个参数和线程池的核心线程数是同样的意义,允许有多少个请求同时请求资源。

   到此,讲解了如何开发一个线程池隔离的服务,和信号隔离的服务,接下来从源码层面讲解隔离的设计实现。

   
      



       
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐