【Hystrix权威指南二】Hystrix隔离策略
微服务组件
·
隔离服务
计算机的线程、内存等资源是有上限的,达到上限时,离系统被拖垮宕机的时间就不短了。特别是访问网络资源,由于网络的不稳定性,被依赖资源的不稳定性都可能出现处理延迟。在一个高并发高流量的互联网系统中,一旦其中有一个依赖处理延迟,瞬间系统的所有线程和内存都会被这一个依赖所占用,导致其他服务也没有资源处理,甚至整个系统被宕机。
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)));
//设置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)));
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
)这个参数和线程池的核心线程数是同样的意义,允许有多少个请求同时请求资源。
到此,讲解了如何开发一个线程池隔离的服务,和信号隔离的服务,接下来从源码层面讲解隔离的设计实现。
更多推荐
已为社区贡献4条内容
所有评论(0)