书籍地址: Spring Cloud 微服务架构开发实战
5.4 服务隔离
Hystrix
的核心就是 提供服务容错保护,并且设计原则中就有一条:防止任何单一依赖用掉整个容器(如Tomcat)的全部用户线程。
那么Hystrix
是如何实现的呢?
答案就是 舱壁隔离模式(Bulkhead Isolation Pattern)
Hystrix
使用该模式,可以对资源或失败单元进行隔离,避免一个服务的失效导致整个系统垮掉(雪崩效应).
Hystrix
实现服务隔离的思路如下:
- 使用命令模式(HystrixCommand/HystrixObservableCommand) 对服务调用进行封装,使每个命令在单独线程中/信号授权下执行。
- 为每一个命令的执行提供一个小的线程池/信号量,当线程池/信号量已满时,立即拒绝执行该命令,直接转入服务降级处理。
- 为每一个命令的执行提供超时处理,当调用超时时,直接转入服务降级处理。
- 提供断路器组件,通过设置相关配置及实时的命令执行数据统计,完成服务健康数据分析,使得在命令执行过程中可以快速判断是否可以执行,还是执行服务降级处理。
5.4.1 线程池隔离与信号量隔离
Hystrix
提供了线程池隔离(Thread Pools) 和信号量隔离(Semaphores) 两种服务隔离策略。
- 线程池隔离:不同服务的执行使用不同的线程池,同时将用户请求的线程与具体业务执行的线程分开,业务执行的线程池可以控制在指定大小的范围内,从而使业务之间不受影响,达到隔离的效果。
- 信号量隔离:用户请求线程和业务执行线程是同一线程,通过设置信号量的大小限制用户请求对业务的并发访问量,从而达到限流的保护效果。
使用线程池隔离的优点:
- 应用系统会被完全保护起来,即使其中一个服务线程池满了,也不会影响到应用的其他部分。
- 当引入一个新的客户端的时候,如果发生问题,只会影响到新的服务,并不会影响其他服务。
- 当一个失败的服务恢复正常时,系统会立即恢复正常的性能。
- 如果我们的应用系统一些参数配置错误,那么线程池的运行状况将会很快被检测出来,比如延迟,超时,拒绝等。同时可以通过动态属性是实质性来处理纠正错误的参数配置。
- 如果服务的性能有变化需要调整,比如增加或者减少超时时间,更改重试次数,就可以通过线程池指标动态属性修改,而且不会影响其他服务请求。
- 除了隔离优势外,
Hystrix
拥有专门的线程池可提供内置的并发功能,可以再同步调用之上构建异步外观模式,这样能很方便的做异步编程
使用线程池隔离的缺点:
使用线程池隔离主要缺陷就是,它增加了计算的开销,每个业务请求在执行的时候,会涉及请求排队,线程调度,上下文切换等处理,但是线程隔离的开销,却是在可接收范围之内的,不会产生重大的成本或性能影响。
5.4.2 服务隔离的颗粒度
服务隔离在默认情况下会根据 Command, Group 和 Thread-Pool的命令来控制。
Group从业务逻辑上划分某些Command 为一组,每个独立的外部依赖放置于一个独立的Command中,拥有唯一的Command名称,每个独立的Command和Thread Pool是一对一的关系,从而达到资源隔离的目的。
服务隔离颗粒度控制策略:
- 服务分组+线程池:实现服务分组隔离的粗粒度控制,一个服务分组/系统配置一个隔离线程池即可。也可以不配置线程池名称或者配置为相同的线程池名称。
- 服务分组+服务+线程池:实现服务隔离的细粒度控制,一个服务分组中的每一个服务 配置一个隔离线程池,为不同的命令实现配置不同的线程池名称即可。
- 混合实现:一个服务分组配置一个隔离线程池,然后对重要服务单独设置隔离线程池。
5.4.3 服务隔离配置
- execution.isolation.strategy:设定服务隔离策略. THREAD为线程池隔离,SEMAPHORE为信号量隔离,默认为THREAD
- execution.isolation.thread.timeoutInMillseconds:用来设置线程池隔离和信号量隔离两种隔离策略的超时时间,单位为毫秒,默认值是 1000ms. 该值根据相应的业务和服务器所能承受的负载来设置,一般设置为比业务平均响应时间大20%~100%即可。
- execution.isolation.semaphore.maxConcurrentRequests:该值设置使用信号量隔离时最大的信号量大小。当请求达到或超过该设置值后,其余就会被降级处理,默认值时10。
- execution.timeout.enabled: 是否开启业务服务超时处理,默认值是 true。
- execution.isolation.thread.interruptOnTimeout:当业务服务超时时,是否中断线程,默认值是 true。
- execution.isolation.thread.interruptOnCancel:取消时是否终端业务服务的执行,默认值是false.
5.5 服务降级模式
5.5.1 快速失败
快速失败模式是指在服务降级处理逻辑中不提供任何处理,直接抛出一个异常。
public class CommandThatFailsFast extends HystrixCommand<String>{
private final boolean throwException;
//通过构造函数设置是否需要抛出异常
public CommandThatFailsFast(boolean throwException){
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup));
this.throwException = throwException;
}
@Override
protected String run() {
//这里不做任何处理,直接抛出一个异常
if(throwException) {
throw new RuntimeException("failure from CommandThatFailsFast");
}else{
return "success";
}
}
}
5.5.2 静默失败
静默失败即当进行服务降级处理时返回空的结果,针对返回值类型,返回的可能是null, 空List 或者 空Map等
@Override
protected String getFallback(){
return null;
}
//针对列表,返回空列表
@Override
protected List<String> getFallback(){
return Collections.emptyList();
}
//针对HystrixObservableCommand,返回一个空Observable对象
@Override
protected Observable<String> getFallback(){
return Observable.empty();
}
5.5.3 返回默认值
服务降级时返回静态的在代码中固定的值,这样就不会像静默失败处理方式一样导致功能或服务被删除,用户不会像静默失败那么看不到任何功能,而是现实一个默认的值或功能。
5.5.4 返回组装的值
当我们执行的结果返回的是一个包含多个字段的复杂对象时,就可以通过服务请求中的值及一些默认值来组装这个返回结果。
我们可以从以下几个地方获取返回结果需要的值:
- cookies;
- 服务请求的参数及Header;
- 之前成功返回的结果中。
5.5.5 返回远程缓存
返回远程缓存是指在服务处理失败的情况下再发起一次远程请求,不过这次请求的是一个缓存,比如读取Redis中的缓存结果。
当使用远程缓存时,需要注意的是获取远程缓存又是一个远程调用,所以需要重新封装为Command进行调用。这时候需要注意,执行fallback的线程一定要与主线程区分开。否则可能会造成主线程休眠,线程池被耗光,也就是说在执行fallback的命令时,需要重新命名ThreadPoolKey;
5.5.6 主/从降级模式
开发者当开发一个系统时可能会为系统设置双通道架构—主/从模式或者主模式和故障转移。
有时候从模式或故障转移只是用来做失败处理。
有时候开发者在部署新功能时,为了防止发生错误,可以将原来的旧代码作为从模式,当新功能出现错误时就降级使用原功能。
5.6 请求缓存
对于缓存的重要性不言自明,Hystrix
所提供的请求缓存可以在 CommandKey/CommandGroup相同的情况下,直接共享命令执行的结果,降低依赖调用次数,在高并发和CacheKey命中率高的场景下可以提升服务性能。
Hystrix
的请求缓存处理是在 construct() 或 run() 方法调用之前,可以有效减少业务服务请求数,降低了服务的并发。
由于请求缓存,对于相同的Hystrix
命令,在执行时不可能返回不同的值,因为第一个响应的结果被缓存,也就是说此时会真实的调用一次run()方法,并且仅执行一次。同一请求中后续所有相同的Hystrix
调用都将直接返回该缓存中的值,从而保证了数据的一致性。
开启Hystrix
请求缓存功能很简单,只需要在实现 HystrixCommand 或 HystrixObservableCommand 时,实现 getCacheKey() 方法即可。
或者通过配置属性 requestCache.enabled,启用来配置是否启用请求缓存,默认是true
5.6.1 清除缓存
可以通过Hystrix
的 HystrixRequestCache.clear() 方法清楚缓存
5.6.2 判断是否时从缓存中返回
HystrixCommand 中提供了 isResponseFromCache() 方法,可以用来判断是否从请求缓存中返回。
此外,注解
- @CacheResult: 标记返回结果需要进行缓存,该注解要与**@hystrixCommand** 注解一起使用。
- @CacheKey:用来标记如果构建缓存的值,其功能类似于 getCacheKey() 方法。
- @CacheRemove : 用来标记在方法执行完毕后清除指定的缓存。
5.7 请求合并
Hystrix
支持将多个请求自动合并为一个请求,通过合并可以减少 HystrixCommand 并发执行所需的线程和网络连接数量,极大的节省了开销,提高了系统效率。
请求合并是由Hystrix
自动合并进行的,并不需要在开发时进行代码介入
这里只是说,合并请求不需要代码介入,但对于批量请求的处理,还是需要写一些代码的。
多个请求能够自动合并的前提是,请求之间要足够 “近”,即执行的间隔时长要足够小,默认为10ms,可以通过 hystrix.collapser.default.timerDelayInMilliseconds 进行设置。即执行间隔超过 10ms 的请求不会合并执行。
5.8 Hystrix
监控
关于Dashboard 和 Turbine ,笔者在学习过程中遇到了很深的坑,至今还未解决,就不误人子弟了. 仅推荐两篇博文,供大家参考
Dashboard
https://blog.csdn.net/WYA1993/article/details/82419131
Turbine
https://blog.csdn.net/chengqiuming/article/details/81588477