soul网关学习15-插件实现1-Divide1-后端节点探活

2021-01-29  本文已影响0人  niuxin

前面几篇我们重点去挖掘了网关配置数据的同步,接下来我们会去分析soul网关的插件体系,就开始吧。

前言

我们知道网关最核心的能力是进行http请求的转发。那在我们的soul网关中又是如何实现这一功能的?这里的实现就是我们今天要分析的主题divide插件。

分析

  1. 先从configuration入手,找到divide插件的配置类DividePluginConfiguration
    DividePluginConfiguration
  2. 从上图中可以看到配置类创建了几个关键的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
  1. 从上图中我们可以继续跟踪两个子逻辑【后端节点列表的获取】以及【负载均衡算法】

后端节点列表的获取

soul-bootstrap后端节点探活

  1. UpstreamCacheManager实现,其中后端节点探活的结构图如下
    UpstreamCacheManager.check
  2. 我们来分解一下这个图
    • 存放后端节点数据有两个 mapUPSTREAM_MAPUPSTREAM_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超时
  3. 以上只是分析了upstream的探活实现,当然还有所有后端节点UPSTREAM_MAP数据的初始化与变更
  4. 这个逻辑就比较简单,通过公共的数据变更机制handler,在选择器selector配置发生变化的时候,进行相应选择器后端节点列表的添加add与移除remove

补充soul-admin端的后端节点探活

  1. 关键类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);
        }
    }
  1. 从上得知启动过程会开启调度线程池,默认每10s执行后端探活的逻辑scheduled方法,探活线程的个数为cpu的核心数
  2. 这里对后端节点的探活逻辑与上述soul-bootstrap端类似,只是在得到存活节点的list之后,会判断是否存在后端节点数目的变化(这里的探活不会增加新节点,只有可能剔除一些不存活的节点,所以只要存在存活节点数与所有节点不一致的情况,则就发生了变更
  3. 如果存在变化,则会执行updateSelectorHandler的逻辑,该逻辑主要会做两件事情:
    • 将存活节点list更新到soul-admin数据库,保存起来
    • 同时发布selector配置变更的事件给到soul-bootstrapsoul-bootstrap端就会更新其后端节点的数据,完成探活节点的同步

负载均衡算法

下篇讲解,To be contined...

上一篇下一篇

猜你喜欢

热点阅读