Soul 中的容错保护
Hystrix
Hystrix简介
Hystrix 由 Netflix 发布的针对微服务分布式系统的熔断保护中间件。
soul 中的 Hystrix 插件
在「反应式编程&插件调用链」一文中,已经分析过插件的调用链,请求进来会默认走以下 3 个前置插件,其中就包括 Hystrix 插件
HystrixPlugin
Resilience4JPlugin
SentinelPlugin
这里设置了插件优先级:
/**
* Hystrix plugin enum.
*/
HYSTRIX(45, 0, "hystrix"),
/**
* Sentinel plugin enum.
*/
SENTINEL(45, 0, "sentinel"),
/**
* Resilence4J plugin enum.
*/
RESILIENCE4J(45, 0, "resilience4j"),
请求经过这3个插件时都会执行 AbstractSoulPlugin 类的 execute() 方法:
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
// 获取插件名称
String pluginName = named();
// 根据插件名获取该插件的基本数据,如 PluginData(id=9, name=hystrix, config=null, role=0, enabled=true)
final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
// 要在 soul-admin 中将插件打开,否则web请求不会被此插件处理
if (pluginData != null && pluginData.getEnabled()) {
// 选择器数据
final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
if (CollectionUtils.isEmpty(selectors)) {
return handleSelectorIsNull(pluginName, exchange, chain);
}
final SelectorData selectorData = matchSelector(exchange, selectors);
if (Objects.isNull(selectorData)) {
return handleSelectorIsNull(pluginName, exchange, chain);
}
selectorLog(selectorData, pluginName);
// 规则列表
final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
if (CollectionUtils.isEmpty(rules)) {
return handleRuleIsNull(pluginName, exchange, chain);
}
RuleData rule;
if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {
//get last
rule = rules.get(rules.size() - 1);
} else {
rule = matchRule(exchange, rules);
}
if (Objects.isNull(rule)) {
return handleRuleIsNull(pluginName, exchange, chain);
}
ruleLog(rule, pluginName);
// 熔断逻辑
return doExecute(exchange, chain, selectorData, rule);
}
return chain.execute(exchange);
}
doExecute()方法中的断路器是判断熔断的关键:
@Override
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
//
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
// 从当前web请求匹配上的规则取出熔断配置
final HystrixHandle hystrixHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), HystrixHandle.class);
if (StringUtils.isBlank(hystrixHandle.getGroupKey())) {
hystrixHandle.setGroupKey(Objects.requireNonNull(soulContext).getModule());
}
if (StringUtils.isBlank(hystrixHandle.getCommandKey())) {
// 没有配置 ‘命令Key’ 时,设置一个默认的
hystrixHandle.setCommandKey(Objects.requireNonNull(soulContext).getMethod());
}
Command command = fetchCommand(hystrixHandle, exchange, chain);
return Mono.create(s -> {
Subscription sub = command.fetchObservable().subscribe(s::success,
s::error, s::success);
s.onCancel(sub::unsubscribe);
// 断路器 判断是否熔断
if (command.isCircuitBreakerOpen()) {
log.error("hystrix execute have circuitBreaker is Open! groupKey:{},commandKey:{}", hystrixHandle.getGroupKey(), hystrixHandle.getCommandKey());
}
}).doOnError(throwable -> {
log.error("hystrix execute exception:", throwable);
exchange.getAttributes().put(Constants.CLIENT_RESPONSE_RESULT_TYPE, ResultEnum.ERROR.getName());
chain.execute(exchange);
}).then();
}
Hystrix处理详解:
-
跳闸最小请求数量 :最小的请求量,至少要达到这个量才会触发熔断
-
错误半分比阀值 : 这段时间内,发生异常的百分比。
-
最大并发量 : 最大的并发量
-
跳闸休眠时间(ms) :熔断以后恢复的时间。
-
分组Key: 一般设置为:contextPath
-
命令Key: 一般设置为具体的 路径接口。
可以在 soul-admin 的 hystrix 插件中设置熔断规则:
总结
Hystrix 的工作原理
第 1 步:构建了一个 HystrixCommand 或者 HystrixObservableCommand 对象,将请求包装到 Command 对象之中;
第 2 步:执行构建好的命令;
第 3 步:判断当前请求是否有缓存,如果有缓存,就直接返回缓存内容。
第 4 步:判断断路器是否处于打开的状态,如果打开,那么 Hystrix 就不再会去执行命令,直接跳到第 8 步,获取 fallback 方法,执行回退逻辑;
如果断路器没有打开,则继续执行第 5 步:判断是否能够执行该命令,如果是线程池隔离模式,会判断线程池队列的容量,如果是信号量隔离模式,会判断信号量的值是否已被使用完。如果线程池和信号量都满了,那么请求不会再执行,直接跳到第 8 步。
如果容量满足执行条件,则继续第 6 步,执行 HystrixObservableCommand.construct() 或者 HystrixCommand.run() 方法,正在执行的请求逻辑就封装在 construct() 或者 run() 方法中。
在请求执行过程中,如果出现异常或超时等情况,则直接到第 8 步,执行成功就返回结果,执行的结果数据会上报给断路器,断路器会根据上报的数据来判断断路器是否打开。