总结
1. 线程池模式 vs 信号量模式
线程池隔离 | 信号量隔离 | |
---|---|---|
线程 | 与调用线程非相同线程 | 与调用线程相同(tomcat/jetty线程) |
开销 | 排队、调度、上下文开销等 | 无线程切换,开销低 |
异步 | 可以是异步,也可以是同步。看调用的方法 | 同步调用,不支持异步 |
并发支持 | 支持(最大线程池大小hystrix.threadpool.default.maximumSize) | 支持(最大信号量上限maxConcurrentRequests) |
是否超时 | 支持,可直接返回 | 不支持,如果阻塞,只能通过调用协议(如:socket超时才能返回) |
是否支持熔断 |
支持,当线程池到达maxSize后,并且工作队列也满,再请求会触发fallback接口进行熔断 |
支持,当信号量达到maxConcurrentRequests后。再请求会触发fallback |
隔离原理 | 每个服务单独用线程池 | 通过信号量的计数器 |
资源开销 | 大,大量线程的上下文切换,容易造成机器负载高 | 小,只是个计数器 |
使用场景 | 当请求的服务网络开销比较大的时候,或者是请求比较耗时的时候。为了保证可以大量的容器(tomcat)线程可用,不会由于服务原因,一直处于阻塞或等待状态,会pick this | 当请求不耗时,返回通常很快,不会占用容器线程太长的时间;pick this 同时也减少了线程切换的开销。 |
下图的左边2/3是线程池资源隔离示意图,右边的1/3是信号量资源隔离示意图,我们先来看左边的示意图。
我们先来看左边的示意图。
当用户请求服务A和服务I的时候,tomcat的线程(图中蓝色箭头标注)会将请求的任务交给服务A和服务I的内部线程池里面的线程(图中橘色箭头标注)来执行,tomcat的线程就可以去干别的事情去了,当服务A和服务I自己线程池里面的线程执行完任务之后,就会将调用的结果返回给tomcat的线程,从而实现资源的隔离,当有大量并发的时候,服务内部的线程池的数量就决定了整个服务的并发度,例如服务A的线程池大小为10个,当同时有12请求时,只会允许10个任务在执行,其他的任务被放在线程池队列中,或者是直接走降级服务,此时,如果服务A挂了,就不会造成大量的tomcat线程被服务A拖死,服务I依然能够提供服务。整个系统不会受太大的影响。
1.1 线程池模式的优缺点
优点:
- 一个依赖可以给予一个线程池,这个依赖的异常不会影响其他的依赖。
- 使用线程池模式可以完全隔离第三方代码,请求线程(客户端的线程)可以快速放回。
- 当一个失败的依赖再次变成可用时,线程池将清理,并立即恢复可用,而不是一个长时间的恢复。
- 可以完全模拟异步调用,方便异步编程。
- 使用线程池,可以有效的进行实时监控、统计和封装。
缺点:
- 使用线程池的缺点主要是增加了计算的开销。每一个依赖调用都会涉及到队列,调度,上下文切换,而这些操作都有可能在不同的线程中执行。
- Netflix 更偏向于使用线程池来隔离依赖服务,因为经过计算,线程切换的消耗在可接受范围之内。并且能支持包括超时在内的所有功能。
2. 设置隔离模式:线程池/信号量
execution.isolation.strategy: "THREAD"/"SEMAPHORE"
@HystrixCommand( commandProperties = { //利用commandProperties更改线程池的一些默认配置 //选择“线程池”模式、"信号量"模式 @HystrixProperty(name="execution.isolation.strategy",value = "THREAD"/"SEMAPHORE"), //超时 @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000"), //信号量大小为10,那么同时只允许10个tomcat的线程(此处是tomcat的线程,而不是服务的独立线程池里面的线程)来访问服务,其他的请求就会被拒绝,从而达到限流保护的作用 @HystrixProperty(name="execution.isolation.semaphore.maxConcurrentRequests",value = "10"), }, ) public List<License> getLicensesByOrg(String organizationId){ //.... }
3. 设置隔离线程池、线程数量、队列长度、fallback方法
默认情况下,Hystrix不做线程隔离,因此容易造成服务雪崩...
如果想设置隔离线程池,需要:
@HystrixCommand( //设置一个隔离的线程池 threadPoolKey = "licenseByOrgThreadPool", threadPoolProperties = { //设置该线程池的线程数 @HystrixProperty(name = "coreSize",value="30"), //设置队列的容量,该队列的作用是当线程池中的线程都处于工作状态,接下来的请求会进入该队列 @HystrixProperty(name="maxQueueSize", value="10") } //一旦队列中也满了,再来的请求就执行“服务降级”fallback方法 fallbackMethod = "buildFallbackLicenseList" ) public List<License> getLicensesByOrg(String organizationId){ //... }