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来控制线程池组。
例如有如下代码:
image.png
dashboard为:
image.png
说明:
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 线程池隔离与信号量隔离区别
官方图示:
image.png
image.png
网友评论