Hystrix-资源隔离策略(线程、信号量)

1.为什么要进行资源隔离

        比如我们现在有3个业务调用分别是查询订单、查询商品、查询用户,且这三个业务请求都是依赖第三方服务-订单服务、商品服务、用户服务。三个服务均是通过RPC调用。当依赖的订单服务变慢了,而这个时候后续有大量的查询订单请求过来,那么容器中的线程数量则会持续增加直致CPU资源耗尽到100%,整个服务对外不可用,集群环境下就是雪崩。所以,有必要将多个依赖服务的调用分别隔离到各自自己的资源池内,不对其他服务造成影响。如下图:

2. 两种隔离方式 

2.1 线程隔离

        适用场景:适合绝大多数的场景,对依赖服务的网络调用timeout,TPS要求高的,这种问题

        执行依赖代码的线程与请求线程(比如Tomcat线程)分离,请求线程可以自由控制离开的时间,这也是我们通常说的异步编程,Hystrix是结合RxJava来实现的异步编程。通过为每个包裹了HystrixCommand的API接口设置独立的、固定大小的线程池(hystrix.threadpool.default.coreSize)来控制并发访问量,当线程饱和的时候可以拒绝服务(走fallback方法),防止依赖问题扩散。

        线上建议线程池不要设置过大,否则大量堵塞线程有可能会拖慢服务器。 

2.1.1 线程池隔离的优缺点

优点

  • 一个依赖调用可以给予一个线程池,这个依赖的异常不会影响其他的依赖。
  • 使用线程可以完全隔离业务代码,请求线程可以快速返回。
  • 可以完全模拟异步调用,方便异步编程。

缺点

  • 使用线程池的缺点主要是增加了计算的开销。每一个依赖调用都会涉及到队列,调度,上下文切换,而这些操作都有可能在不同的线程中执行。

2.1.2 线程池隔离相关参数

让我们来逐个介绍下@HystrixCommand注解的各个参数:

1:commandKey:配置全局唯一标识服务的名称,比如,库存系统有一个获取库存服务,那么就可以为这个服务起一个名字来唯一识别该服务,如果不配置,则默认是@HystrixCommand注解修饰的函数的函数名。

2:groupKey:一个比较重要的注解,配置全局唯一标识服务分组的名称,比如,库存系统就是一个服务分组。通过设置分组,Hystrix会根据组来组织和统计命令的告、仪表盘等信息。Hystrix命令默认的线程划分也是根据命令组来实现。默认情况下,Hystrix会让相同组名的命令使用同一个线程池,所以我们需要在创建Hystrix命令时为其指定命令组来实现默认的线程池划分。此外,Hystrix还提供了通过设置threadPoolKey来对线程池进行设置。建议最好设置该参数,使用threadPoolKey来控制线程池组。
例如有如下代码:

dashboard为: 

说明:

findById - HystrixCommandKey(默认为Controller下的方法名)
MovieController - HystrixThreadPoolKey(不配置的情况下就是commandGroupKey,HystrixCommandGroupKey默认为类名)

3:threadPoolKey:对线程池进行设定,细粒度的配置,相当于对单个服务的线程池信息进行设置,也可多个服务设置同一个threadPoolKey构成线程组。

4:fallbackMethod:@HystrixCommand注解修饰的函数的回调函数,@HystrixCommand修饰的函数必须和这个回调函数定义在同一个类中,因为定义在了同一个类中,所以fackback method可以是public/private均可。

5:commandProperties:配置该命令的一些参数,如executionIsolationStrategy配置执行隔离策略,默认是使用线程隔离,此处我们配置为THREAD,即线程池隔离。参见:com.netflix.hystrix.HystrixCommandProperties中各个参数的定义。

6:threadPoolProperties:线程池相关参数设置,具体可以设置哪些参数请见:com.netflix.hystrix.HystrixThreadPoolProperties

7:ignoreExceptions:调用服务时,除了HystrixBadRequestException之外,其他@HystrixCommand修饰的函数抛出的异常均会被Hystrix认为命令执行失败而触发服务降级的处理逻辑(调用fallbackMethod指定的回调函数),所以当需要在命令执行中抛出不触发降级的异常时来使用它,通过这个参数指定,哪些异常抛出时不触发降级(不去调用fallbackMethod),而是将异常向上抛出。

8:observableExecutionMode:定义hystrix observable command的模式;

9:raiseHystrixExceptions:任何不可忽略的异常都包含在HystrixRuntimeException中;

10:defaultFallback:默认的回调函数,该函数的函数体不能有入参,返回值类型与@HystrixCommand修饰的函数体的返回值一致。如果指定了fallbackMethod,则fallbackMethod优先级更高。

2.2 信号量隔离

        用于隔离本地代码或可快速返回的远程调用(如memcached,redis)可以直接使用信号量隔离,降低线程隔离的上下文切换开销。

        线程隔离会带来线程开销,有些场景(比如无网络请求场景)可能会因为用开销换隔离得不偿失,为此hystrix提供了信号量隔离。

        主要适用场景: 并发需求不大的依赖调用(因为如果并发需求较大,相应的信号量的数量就要设置得够大,因为Tomcat线程与处理线程为同一个线程,那么这个依赖调用就会占用过多的Tomcat线程资源,有可能会影响到其他服务的接收)

        和线程池隔离类似,同一个HystrixCommandGroupKey共用一个信号量(默认为类名)

public class CommandUsingSemaphoreIsolation extends HystrixCommand<String> {
    private final int id;
    public CommandUsingSemaphoreIsolation(int id) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
                // since we're doing an in-memory cache lookup we choose SEMAPHORE isolation
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)));
        this.id = id;
    }
    @Override
    protected String run() {
        // a real implementation would retrieve data from in memory data structure
        return "ValueFromHashMap_" + id;
   }
}

2.3 线程池隔离与信号量隔离区别

官方图示:

猜你喜欢

转载自blog.csdn.net/u010277958/article/details/88766425