Soul网关源码探秘《十一》 - 服务探活

前文在分析DividePlugin插件执行插件逻辑的过程中,有看到是从缓存中取出可用服务列表,然后经过负载均衡算法选定最终的服务,之后转发到真实服务。

那么缓存中的服务列表是如何维护的?何时新增?通过什么方式新增?当服务不可用时,又是如何同步的?接下来就研究一下这一块的逻辑。

准备工作

启动后台(soul-admin)和网关(soul-bootstrap)两个项目。不再启动HTTP测试服务。

源码解析

再次进行HTTP请求http://localhost:9195/http/order/findById?id=1返回报错信息

{
    "code": 500,
    "message": "Internal Server Error",
    "data": "finishConnect(..) failed: Connection refused: /192.168.23.167:8190"
}

DividePlugin从缓存中获取所有服务列表的地方打上断点(仅展示必要代码)。

protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
        // ...
        /** 通过选择器ID获取所有提供服务的列表信息 */
        final List<DivideUpstream> upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
        // ...
}

从日志可以看出请求与 divide 中设置的选择器已经匹配成功。
divide匹配成功

接着我们看从缓存中读取服务列表的逻辑。

public final class UpstreamCacheManager {

    private static final UpstreamCacheManager INSTANCE = new UpstreamCacheManager();

    private static final Map<String, List<DivideUpstream>> UPSTREAM_MAP = Maps.newConcurrentMap();

    private static final Map<String, List<DivideUpstream>> UPSTREAM_MAP_TEMP = Maps.newConcurrentMap();

    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);
        }
    }

    public static UpstreamCacheManager getInstance() {
        return INSTANCE;
    }
    
    public List<DivideUpstream> findUpstreamListBySelectorId(final String selectorId) {
        return UPSTREAM_MAP_TEMP.get(selectorId);
    }
}

服务缓存器维护了UPSTREAM_MAP_TEMP作为服务的内存缓存。另外服务缓存器使用了单例模式。如果配置soul.upstream.check=true,那么会在实例化的过程中启动一个具有一个线程的线程池,每隔30秒执行scheduled方法。UPSTREAM_MAP_TEMP也是在这个方法中赋值的。

private void scheduled() {
        // 如果缓存服务不为空
        if (UPSTREAM_MAP.size() > 0) {
        // 以储存的选择器ID去循环
           UPSTREAM_MAP.forEach((k, v) -> {
        // 循环该服务列表,依次去检查是否可用
        List<DivideUpstream> result = check(v);
        if (result.size() > 0) {
            UPSTREAM_MAP_TEMP.put(k, result);
        } else {
            UPSTREAM_MAP_TEMP.remove(k);
        }
    }

可以看到,每隔30秒执行一次的定时任务里,网关的服务缓存器会对UPSTREAM_MAP中现有的所有服务进行检查探活。当然前提是得在配置里面设置soul.upstream.check=true。每次都将UPSTREAM_MAP_TEMP中放入最新的检查结果。

接着查看真正执行检查探活操作的check方法。

private List<DivideUpstream> check(final List<DivideUpstream> upstreamList) {
        // ...
        // 执行检查探活操作
        final boolean pass = UpstreamCheckUtils.checkUrl(divideUpstream.getUpstreamUrl());
        // ...
    }

继续查找UpstreamCheckUtils.checkUrl的逻辑。

	public static boolean checkUrl(final String url) {
        // ...
        if {
        	// ...
            return isHostConnector(hostPort[0], Integer.parseInt(hostPort[1]));
        } else {
            return isHostReachable(url);
        }
    }
    
	private static boolean isHostConnector(final String host, final int port) {
        try (Socket socket = new Socket()) {
        	// 使用socket连接进行探活
            socket.connect(new InetSocketAddress(host, port));
        } catch (IOException e) {
            return false;
        }
        return true;
    }
    
    // 使用 InetAddress 判断 host 是否可以连接
	private static boolean isHostReachable(final String host) {
        try {
            return InetAddress.getByName(host).isReachable(1000);
        } catch (IOException ignored) {
        }
        return false;
    }

最终是使用 socket 或者 InetAddress 执行最后的检查探活连接操作,然后返回连接成功或者失败。

至此,在网关层面,自动探活更新或者删除UPSTREAM_MAP_TEMP的流程就清晰了。那服务缓存信息的源头UPSTREAM_MAP中的信息又是何时初始化的?以及何时更新和删除呢?

服务缓存信息何时新增

UpstreamCacheManager 中还有一个 submit 方法。

	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());
        }
    }

这个方法接收一个选择器数据为参数,如果该选择器数据里服务列表不为空,则将服务列表和选择器id加入UPSTREAM_MAPUPSTREAM_MAP_TEMP,否则都把响应选择器id的数据删掉。

盲猜在soul-admin更新选择器数据时会经过 sumbit 逻辑,在这个方法打上断点,然后在后台项目中新增一条选择器规则。新增成功后,立即进入了断点位置。
sumbit
可以看到后台更新成功后,通过websocket的机制将选择器信息传到网关。由CommonPluginDataSubscribersubscribeDataHandler方法交给DividePluginDataHandler执行 submit 方法

private <T> void subscribeDataHandler(final T classData, final DataEventTypeEnum dataType) {
   // ...
  Optional.ofNullable(handlerMap.get(selectorData.getPluginName())).ifPresent(handler -> handler.handlerSelector(selectorData));
  // ...
}
	public void handlerSelector(final SelectorData selectorData) {
        UpstreamCacheManager.getInstance().submit(selectorData);
    }

总结:

在配置了soul.upstream.check=true的情况下,网关会每隔30秒执行定时任务,通过 socket 或者 InetAddress 去连接已记录的服务,进行检查探活的工作。并根据结果进行更新和删除的操作。

而在网关新增服务,需要在后台配置选择器后,后台通过 websocket 与网关同步选择器配置。之后由网关执行新增缓存的逻辑。

那么后台是如何通过 websocket 将选择器数据同步到网关?以及后台自己有没有类似网关的探活机制?接下来的文章将会继续寻找答案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

rughru

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值