soul网关学习15-插件实现1-Divide1-后端节点探活
2021-01-29 本文已影响0人
niuxin
前面几篇我们重点去挖掘了网关配置数据的同步,接下来我们会去分析soul
网关的插件体系,就开始吧。
前言
我们知道网关最核心的能力是进行http
请求的转发。那在我们的soul
网关中又是如何实现这一功能的?这里的实现就是我们今天要分析的主题divide
插件。
分析
- 先从
configuration
入手,找到divide
插件的配置类DividePluginConfiguration
。
DividePluginConfiguration - 从上图中可以看到配置类创建了几个关键的
bean
。我们先来分析DividePlugin
,核心逻辑doExecute
。
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
// context模式
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
// 获取分流规则handle
final DivideRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), DivideRuleHandle.class);
// 根据当前选择器获取到后台节点list
// TODO 需分析后台节点获取的实现,会涉及到后台节点的探活,增加活剔除节点
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);
}
// 获取ip
final String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
// 根据远程客户端ip获取后台节点,经过负载均衡策略
// TODO 需分析负载均衡的实现
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);
}
// set the http url
// 根据后台节点对象构建服务地址,并将其放于交换器,传递下去,进行转发
String domain = buildDomain(divideUpstream);
String realURL = buildRealURL(domain, soulContext, exchange);
// 调用的几个关键参数:服务地址 请求参数 超时 重试次数
exchange.getAttributes().put(Constants.HTTP_URL, realURL);
// set the http timeout
exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());
exchange.getAttributes().put(Constants.HTTP_RETRY, ruleHandle.getRetry());
return chain.execute(exchange);
}
DividePlugin.doExecute
- 从上图中我们可以继续跟踪两个子逻辑【后端节点列表的获取】以及【负载均衡算法】
后端节点列表的获取
soul-bootstrap
后端节点探活
- 由
UpstreamCacheManager
实现,其中后端节点探活的结构图如下
UpstreamCacheManager.check - 我们来分解一下这个图
- 存放后端节点数据有两个
map
,UPSTREAM_MAP
与UPSTREAM_MAP_TEMP
。至于为什么会有两个map
,主要是前者会存储所有的后端节点,由soul-admin
那边同步过来,不管其是否存活;而后者则只存储探活成功的节点,为网关负载的有效节点。 - 还有个
scheduler
,该scheduler
开启了一个线程scheduled-upstream-task
执行调度任务scheduled
,默认探活调度的间隔为30s
,建议设置为1s
,需手设置 - 该
scheduled
逻辑是遍历所有后端节点UPSTREAM_MAP
,通过挨个探活check
,完成有效后端节点UPSTREAM_MAP_TEMP
的替换与剔除 - 这里探活
check
的实现方式:后端节点upstream
服务地址url
是否为ip
,为ip
则用ip+port
建立socket
连接进行探测;否则直接判断节点host
是否可达;采用socket
方式探活时没有显式的超时设置,而host
是否可达则会1s
超时
- 存放后端节点数据有两个
- 以上只是分析了
upstream
的探活实现,当然还有所有后端节点UPSTREAM_MAP
数据的初始化与变更 - 这个逻辑就比较简单,通过公共的数据变更机制
handler
,在选择器selector
配置发生变化的时候,进行相应选择器后端节点列表的添加add
与移除remove
补充soul-admin
端的后端节点探活
- 关键类
UpstreamCheckService
,我们看到这个类在实例化之后的时候就会去执行后端节点探活的逻辑
@PostConstruct
public void setup() {
// 从数据库中获取到所有的后端节点数据,初始逻辑
PluginDO pluginDO = pluginMapper.selectByName(PluginEnum.DIVIDE.getName());
if (pluginDO != null) {
List<SelectorDO> selectorDOList = selectorMapper.findByPluginId(pluginDO.getId());
for (SelectorDO selectorDO : selectorDOList) {
List<DivideUpstream> divideUpstreams = GsonUtils.getInstance().fromList(selectorDO.getHandle(), DivideUpstream.class);
if (CollectionUtils.isNotEmpty(divideUpstreams)) {
UPSTREAM_MAP.put(selectorDO.getName(), divideUpstreams);
}
}
}
// 如果探活开启,则会执行探活,可配置,默认是开启的
if (check) {
// 开启调度线程池默认每10s执行一次check
new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), SoulThreadFactory.create("scheduled-upstream-task", false))
.scheduleWithFixedDelay(this::scheduled, 10, scheduledTime, TimeUnit.SECONDS);
}
}
- 从上得知启动过程会开启调度线程池,默认每
10s
执行后端探活的逻辑scheduled
方法,探活线程的个数为cpu
的核心数 - 这里对后端节点的探活逻辑与上述
soul-bootstrap
端类似,只是在得到存活节点的list
之后,会判断是否存在后端节点数目的变化(这里的探活不会增加新节点,只有可能剔除一些不存活的节点,所以只要存在存活节点数与所有节点不一致的情况,则就发生了变更
) - 如果存在变化,则会执行
updateSelectorHandler
的逻辑,该逻辑主要会做两件事情:- 将存活节点
list
更新到soul-admin
数据库,保存起来 - 同时发布
selector
配置变更的事件给到soul-bootstrap
,soul-bootstrap
端就会更新其后端节点的数据,完成探活节点的同步
- 将存活节点
负载均衡算法
下篇讲解,To be contined...