Soul 网关源码阅读之 selector data 及 ip 探活机制

今天,我们继续研究 SelectorData 的数据流向,并以 divide 插件为例,看看数据最终的应用场景,比如ip 探活。

DividePluginDataHandler

有了上篇文章的基础,我们直接打开 CommonPluginDataSubscriber.subscribeDataHandler 方法:

    private <T> void subscribeDataHandler(final T classData, final DataEventTypeEnum dataType) {
        Optional.ofNullable(classData).ifPresent(data -> {
        ...
             else if (data instanceof SelectorData) {
                SelectorData selectorData = (SelectorData) data;
                if (dataType == DataEventTypeEnum.UPDATE) {
                    BaseDataCache.getInstance().cacheSelectData(selectorData);
                    Optional.ofNullable(handlerMap.get(selectorData.getPluginName())).ifPresent(handler -> handler.handlerSelector(selectorData));
                } else if (dataType == DataEventTypeEnum.DELETE) {
                    BaseDataCache.getInstance().removeSelectData(selectorData);
                    Optional.ofNullable(handlerMap.get(selectorData.getPluginName())).ifPresent(handler -> handler.removeSelector(selectorData));
                }
            } 
            ...
        });
    }

除去同样会在 BaseDataCache 中缓存一份之外,还会调用 PluginDataHandler.handlerPlugin(SelectorData selectorData); 方法。查看其实现,最终落到了 UpstreamCacheManager 类中:

    public void handlerSelector(final SelectorData selectorData) {
        UpstreamCacheManager.getInstance().submit(selectorData);
    }

UpstreamCacheManager

针对 divide 插件,SelectorData 信息在 UpstreamCacheManager 类中被解析为一串 DivideUpstream 实例:

    public void submit(final SelectorData selectorData) {
        final List<DivideUpstream> upstreamList = GsonUtils.getInstance().fromList(selectorData.getHandle(), DivideUpstream.class);
        if (null != upstreamList && upstreamList.size() > 0) {
            UPSTREAM_MAP.put(selectorData.getId(), upstreamList);
            UPSTREAM_MAP_TEMP.put(selectorData.getId(), upstreamList);
        } else {
            UPSTREAM_MAP.remove(selectorData.getId());
            UPSTREAM_MAP_TEMP.remove(selectorData.getId());
        }
    }

可以看到,数据最终落入了两个 ConcurrentHashMap 中,继续看此类的源码,可以发现,其中 UPSTREAM_MAP 是用来服务探活的,而 UPSTREAM_MAP_TEMP 是用于存储探活后的 DivideUpstream,并最终被插件所用。
首先,创建定时任务:

    private UpstreamCacheManager() {
    	// 判断用户是否打开了定期探活的配置
        boolean check = Boolean.parseBoolean(System.getProperty("soul.upstream.check", "false"));
        if (check) {
        	// 创建一个定时任务线程池
            new ScheduledThreadPoolExecutor(1, SoulThreadFactory.create("scheduled-upstream-task", false))
                    .scheduleWithFixedDelay(this::scheduled,
                            30, Integer.parseInt(System.getProperty("soul.upstream.scheduledTime", "30")), TimeUnit.SECONDS);
        }
    }

接着定时任务会调用下述逻辑:

    private void scheduled() {
        if (UPSTREAM_MAP.size() > 0) {
        	// 遍历 UPSTREAM_MAP
            UPSTREAM_MAP.forEach((k, v) -> {
            	// 进行探活
                List<DivideUpstream> result = check(v);
                if (result.size() > 0) {
                	// 将探活的结果放入 UPSTREAM_MAP_TEMP 中
                    UPSTREAM_MAP_TEMP.put(k, result);
                } else {
                    UPSTREAM_MAP_TEMP.remove(k);
                }
            });
        }
    }

探活逻辑如下:

    private List<DivideUpstream> check(final List<DivideUpstream> upstreamList) {
        List<DivideUpstream> resultList = 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 detect success the url: {}, host: {} ", divideUpstream.getUpstreamUrl(), divideUpstream.getUpstreamHost());
                }
                resultList.add(divideUpstream);
            } else {
                divideUpstream.setStatus(false);
                log.error("check the url={} is fail ", divideUpstream.getUpstreamUrl());
            }
        }
        return resultList;

    }

	// org.dromara.soul.common.utils.UpstreamCheckUtils.checkUrl
    public static boolean checkUrl(final String url) {
        if (StringUtils.isBlank(url)) {
            return false;
        }
        // 使用 Pattern 判断 URL 格式是否合法
        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);
            }
            // 判断 host 的可连接性
            return isHostConnector(hostPort[0], Integer.parseInt(hostPort[1]));
        } else {
        	// 判断 host 的可达性
            return isHostReachable(url);
        }
    }

    private static boolean isHostConnector(final String host, final int port) {
    	// 创建一个新的 Socket
        try (Socket socket = new Socket()) {
        	// 使用 socket 尝试连接相应的端口
            socket.connect(new InetSocketAddress(host, port));
        } catch (IOException e) {
            return false;
        }
        return true;
    }
    
    private static boolean isHostReachable(final String host) {
        try {
        	// 无端口号的情况下,则直接针对 host 进行可达性检测
            return InetAddress.getByName(host).isReachable(1000);
        } catch (IOException ignored) {
        }
        return false;
    }

小结

至此,我们通过实际的 divide 插件作为例子,看到了 SelectorData 的数据流向,并以此做引,看到了 divide 插件的探活机制。在最后,作为补充,探活后的 DivideUpstream 结果,会在每次进行权重获取时被用到:

	// org.dromara.soul.plugin.divide.balance.spi.AbstractLoadBalance
    protected int getWeight(final DivideUpstream upstream) {
    	// 如果 DivideUpstream 未存活,则权重为 0
        if (!upstream.isStatus()) {
            return 0;
        }
        int weight = getWeight(upstream.getTimestamp(), getWarmup(upstream.getWarmup(), Constants.DEFAULT_WARMUP), upstream.getWeight());
        return weight;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值