soul网关源码学习17-hystrix插件解析(下)
目标:
- 了解hystrix的底层实现
实现步骤
- 使用命令模式将所有对外部服务(或依赖关系)的调用包装在HystrixCommand或HystrixObservableCommand对象中,并将该对象放在单独的线程中执行;
- 每个依赖都维护着一个线程池(或信号量),线程池被耗尽则拒绝请求(而不是让请求排队)。
- 记录请求成功,失败,超时和线程拒绝。
- 服务错误百分比超过了阈值,熔断器开关自动打开,一段时间内停止对该服务的所有请求。
- 请求失败,被拒绝,超时或熔断时执行降级逻辑。
- 近实时地监控指标和配置的修改。
处理流程
Hystrix整个工作流如下:
- 构造一个 HystrixCommand或HystrixObservableCommand对象,用于封装请求,并在构造方法配置请求被执行需要的参数;
- 执行命令,Hystrix提供了4种执行命令的方法;
- 判断是否使用缓存响应请求,若启用了缓存,且缓存可用,直接使用缓存响应请求。Hystrix支持请求缓存,但需要用户自定义启动;
- 判断熔断器是否打开,如果打开,跳到第8步;
- 判断线程池/队列/信号量是否已满,已满则跳到第8步;
- 执行HystrixObservableCommand.construct()或HystrixCommand.run(),如果执行失败或者超时,跳到第8步;否则,跳到第9步;
- 统计熔断器监控指标;
- 走Fallback备用逻辑;
- 返回请求响应。
注意:第5步线程池/队列/信号量已满时,还会执行第7步逻辑,更新熔断器统计信息,而第6步无论成功与否,都会更新熔断器统计信息。
Hystrix容错
Hystrix的容错主要是通过添加容许延迟和容错方法,帮助控制这些分布式服务之间的交互。 还通过隔离服务之间的访问点,阻止它们之间的级联故障以及提供回退选项来实现这一点,从而提高系统的整体弹性。
资源隔离
资源隔离主要指对线程的隔离。Hystrix提供了两种线程隔离方式:线程池和信号量。
Command command = fetchCommand(hystrixHandle, exchange, chain);
//通过不同的隔离方式构造不同的Command
private Command fetchCommand(final HystrixHandle hystrixHandle, final ServerWebExchange exchange, final SoulPluginChain chain) {
if (hystrixHandle.getExecutionIsolationStrategy() == HystrixIsolationModeEnum.SEMAPHORE.getCode()) {
return new HystrixCommand(HystrixBuilder.build(hystrixHandle),
exchange, chain, hystrixHandle.getCallBackUri());
}
return new HystrixCommandOnThread(HystrixBuilder.buildForHystrixCommand(hystrixHandle),
exchange, chain, hystrixHandle.getCallBackUri());
}
- 线程隔离方式构造Command
HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds((int) hystrixHandle.getTimeout())
.withCircuitBreakerEnabled(true)
//错误率达到50开启熔断保护
.withCircuitBreakerErrorThresholdPercentage(hystrixHandle.getErrorThresholdPercentage())
//至少有20个请求,熔断器才进行错误率的计算
.withCircuitBreakerRequestVolumeThreshold(hystrixHandle.getRequestVolumeThreshold())
//熔断器中断请求5秒后会进入半打开状态,放部分流量过去重试
.withCircuitBreakerSleepWindowInMilliseconds(hystrixHandle.getSleepWindowInMilliseconds());
- 信号量方式构造Command
HystrixCommandProperties.Setter propertiesSetter =
HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds((int) hystrixHandle.getTimeout())
.withCircuitBreakerEnabled(true)
//默认是线程隔离,需要手动修改为ExecutionIsolationStrategy.SEMAPHORE
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
//最大并发请求量
.withExecutionIsolationSemaphoreMaxConcurrentRequests(hystrixHandle.getMaxConcurrentRequests())
//错误率达到50开启熔断保护
.withCircuitBreakerErrorThresholdPercentage(hystrixHandle.getErrorThresholdPercentage())
//至少有20个请求,熔断器才进行错误率的计算
.withCircuitBreakerRequestVolumeThreshold(hystrixHandle.getRequestVolumeThreshold())
//熔断器中断请求5秒后会进入半打开状态,放部分流量过去重试
.withCircuitBreakerSleepWindowInMilliseconds(hystrixHandle.getSleepWindowInMilliseconds());
线程隔离总结
线程池和信号量都支持熔断和限流。相比线程池,信号量不需要线程切换,因此避免了不必要的开销。但是信号量不支持异步,也不支持超时,也就是说当所请求的服务不可用时,信号量会控制超过限制的请求立即返回,但是已经持有信号量的线程只能等待服务响应或从超时中返回,即可能出现长时间等待。线程池模式下,当超过指定时间未响应的服务,Hystrix会通过响应中断的方式通知线程立即结束并返回。
熔断器工作原理

第一步,调用allowRequest()判断是否允许将请求提交到线程池
- 如果熔断器强制打开,circuitBreaker.forceOpen为true,不允许放行,返回。
- 如果熔断器强制关闭,circuitBreaker.forceClosed为true,允许放行。此外不必关注熔断器实际状态,也就是说熔断器仍然会维护统计数据和开关状态,只是不生效而已。
第二步,调用isOpen()判断熔断器开关是否打开
- 如果熔断器开关打开,进入第三步,否则继续;
- 如果一个周期内总的请求数小于circuitBreaker.requestVolumeThreshold的值,允许请求放行,否则继续;
- 如果一个周期内错误率小于circuitBreaker.errorThresholdPercentage的值,允许请求放行。否则,打开熔断器开关,进入第三步。
第三步,调用allowSingleTest()判断是否允许单个请求通行,检查依赖服务是否恢复
- 如果熔断器打开,且距离熔断器打开的时间或上一次试探请求放行的时间超过circuitBreaker.sleepWindowInMilliseconds的值时,熔断器器进入半开状态,允许放行一个试探请求;否则,不允许放行。
此外,为了提供决策依据,每个熔断器默认维护了10个bucket,每秒一个bucket,当新的bucket被创建时,最旧的bucket会被抛弃。其中每个blucket维护了请求成功、失败、超时、拒绝的计数器,Hystrix负责收集并统计这些计数器。
总结
参考网上的资料了解了一下,略懂了一点大概的流程,想一下子搞懂有点难。
参考:https://blog.youkuaiyun.com/loushuiyifan/article/details/82702522
本文介绍了Hystrix插件的工作原理及其实现步骤。包括如何通过命令模式封装对外部服务调用,利用线程池或信号量进行资源隔离,以及熔断器的工作流程等关键内容。
261

被折叠的 条评论
为什么被折叠?



