Divide插件实现原理分析
Divide插件实现原理分析
divide插件的执行流程
divide插件的调用使用了一个模板模式和责任链模式,在AbstractSoulPlugin
抽象方法中实现了模板方法,为所有的插件实现了统一的选择器过滤和规则过滤的算法;
每个单独的插件通过继承AbstractSoulPlugin
抽象类,并且实现doExecute方法来实现行插件的功能;
// 选择器和过滤器的操作
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
// 获取plugin name
String pluginName = named();
// 获取插件的信息
final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
if (pluginData != null && pluginData.getEnabled()) {
// obtainSelectorData会通过plugin 名称去获取该插件的选择器
final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
if (CollectionUtils.isEmpty(selectors)) {
// 如果选择器为空, 首先判断是否为waf插件(防火墙),再判断是否为divide, dubbo, spring-cloud,
// 只要是其中一种,就直接执行插件的处理方法
return handleSelectorIsNull(pluginName, exchange, chain);
}
// 更具响应的uri和selector进行过滤,拿到对应的selectorData,如果有多条直接默认第一条选择器
final SelectorData selectorData = matchSelector(exchange, selectors);
if (Objects.isNull(selectorData)) {
return handleSelectorIsNull(pluginName, exchange, chain);
}
selectorLog(selectorData, pluginName);
// 通过选择器ID拿到规则列表
final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
// 如果规则为空,判断插件是否是waf,divide,dubbo,spring cloud,如果是的话直接执行插件操作
// 如果不是上述插件,将包 "Can not match plugin" 的异常
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);
}
// 如果规则为空,判断插件是否是waf,divide,dubbo,spring cloud,如果是的话直接执行插件操作
// 如果不是上述插件,将包 "Can not match plugin" 的异常
if (Objects.isNull(rule)) {
return handleRuleIsNull(pluginName, exchange, chain);
}
ruleLog(rule, pluginName);
// 执行插件操作
return doExecute(exchange, chain, selectorData, rule);
}
return chain.execute(exchange);
}
@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;
// 获取规则处理器
final DivideRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), DivideRuleHandle.class);
final List<DivideUpstream> upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
if (CollectionUtils.isEmpty(upstreamList)) {
log.error("divide upstream configuration error: {}", rule.toString());
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
final String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
// 负载均衡
DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
if (Objects.isNull(divideUpstream)) {
log.error("divide has no upstream");
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
// 设置转发的url
String domain = buildDomain(divideUpstream);
String realURL = buildRealURL(domain, soulContext, exchange);
exchange.getAttributes().put(Constants.HTTP_URL, realURL);
// 设置超时时间
exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());
exchange.getAttributes().put(Constants.HTTP_RETRY, ruleHandle.getRetry());
return chain.execute(exchange);
}
ip端口探活
在org.dromara.soul.common.utils.UpstreamCheckUtils
工具类下实现了ip的探活,它的底层实现其实就是创建一个socket连接,如果能够连接上,那么服务器处于正常状态,反之服务器连接异常;
public static boolean checkUrl(final String url) {
if (StringUtils.isBlank(url)) {
return false;
}
if (checkIP(url)) {
String[] hostPort;
if (url.startsWith(HTTP)) {
final String[] http = StringUtils.split(url, "\\/\\/");
hostPort = StringUtils.split(http[1], Constants.COLONS);
} else {
hostPort = StringUtils.split(url, Constants.COLONS);
}
return isHostConnector(hostPort[0], Integer.parseInt(hostPort[1]));
} else {
return isHostReachable(url);
}
}
private static boolean checkIP(final String url) {
return PATTERN.matcher(url).matches();
}
private static boolean isHostConnector(final String host, final int port) {
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(host, port));
} catch (IOException e) {
return false;
}
return true;
}
private static boolean isHostReachable(final String host) {
try {
return InetAddress.getByName(host).isReachable(1000);
} catch (IOException ignored) {
}
return false;
}
心跳检测的服务由admin端中的org.dromara.soul.admin.service.impl.UpstreamCheckService
实现;
在setup()方法中实现了一个定时任务,每10s中进行一次心跳检测;
...
// 通过配置check 为true 或false来开启和关闭心跳检测;
if (check) {
new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), SoulThreadFactory.create("scheduled-upstream-task", false))
.scheduleWithFixedDelay(this::scheduled, 10, scheduledTime, TimeUnit.SECONDS);
}
...
private void check(final String selectorName, final List<DivideUpstream> upstreamList) {
List<DivideUpstream> successList = Lists.newArrayListWithCapacity(upstreamList.size());
for (DivideUpstream divideUpstream : upstreamList) {
final boolean pass = UpstreamCheckUtils.checkUrl(divideUpstream.getUpstreamUrl());
if (pass) {
if (!divideUpstream.isStatus()) {
divideUpstream.setTimestamp(System.currentTimeMillis());
divideUpstream.setStatus(true);
log.info("UpstreamCacheManager check success the url: {}, host: {} ", divideUpstream.getUpstreamUrl(), divideUpstream.getUpstreamHost());
}
successList.add(divideUpstream);
} else {
divideUpstream.setStatus(false);
log.error("check the url={} is fail ", divideUpstream.getUpstreamUrl());
}
}
if (successList.size() == upstreamList.size()) {
return;
}
if (successList.size() > 0) {
UPSTREAM_MAP.put(selectorName, successList);
updateSelectorHandler(selectorName, successList);
} else {
UPSTREAM_MAP.remove(selectorName);
updateSelectorHandler(selectorName, null);
}
}
负载均衡
在项目soul-plugin-divide中实现了三种负载均衡策略,程序通过传过来的负载均衡名称通过spi方式选择相应的负载均衡策略;
目前divide支持的负载均衡策略有以下三种:
1. 轮询;
2. 哈希算法;
3. 带权随机;
如果想要自定义负载均衡策略,只需要继承AbstractLoadBalance
抽象类,实现里面的抽象方法即可;另外在org.dromara.soul.plugin.divide.balance.LoadBalance文件下添加自己的负载均衡算法路径即可;