【高性能网关soul学习】6. Websocket 方式数据同步流程后续

【高性能网关soul学习】6. Websocket 方式数据同步流程后续

上文 中我们讲到PluginDataSubscriber,本文则主要介绍 boot-strap 中对同步过来的数据的处理逻辑

  • CommonPluginDataSubscriber的核心方法为 subscribeDataHandler(), 针对不同的数据类型和操作类型,执行不同的逻辑,下文主要以选择器的更新为例
    • 下面方法只copy了选择器部分的代码
private <T> void subscribeDataHandler(final T classData, final DataEventTypeEnum dataType) {
        Optional.ofNullable(classData).ifPresent(data -> {
            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
  • 选择器的更新
public void cacheSelectData(final SelectorData selectorData) {
		Optional.ofNullable(selectorData).ifPresent(this::selectorAccept);
}

// 以插件名为key,选择器的数据为value 的一个本地内存cache
private static final ConcurrentMap<String, List<SelectorData>> SELECTOR_MAP = Maps.newConcurrentMap();

private void selectorAccept(final SelectorData data) {
    String key = data.getPluginName();
    if (SELECTOR_MAP.containsKey(key)) {
        List<SelectorData> existList = SELECTOR_MAP.get(key);
      	// 更新相同低的选择器:(过滤掉相同id的选择器,然后插入新的选择器数据,最后进行排序,然后重新放入缓存中)
        final List<SelectorData> resultList = existList.stream().filter(r -> !r.getId().equals(data.getId())).collect(Collectors.toList());
        resultList.add(data);
        final List<SelectorData> collect = resultList.stream().sorted(Comparator.comparing(SelectorData::getSort)).collect(Collectors.toList());
        SELECTOR_MAP.put(key, collect);
    } else {
        SELECTOR_MAP.put(key, Lists.newArrayList(data));
    }
}
  • 选择器的删除
    • 逻辑很简单,根据 pluginName 找到选择器列表,然后删除id相同的选择器
public void removeSelectData(final SelectorData selectorData) {
        Optional.ofNullable(selectorData).ifPresent(data -> {
            final List<SelectorData> selectorDataList = SELECTOR_MAP.get(data.getPluginName());
            Optional.ofNullable(selectorDataList).ifPresent(list -> list.removeIf(e -> e.getId().equals(data.getId())));
        });
    }
PluginDataHandler

CommonPluginDataSubscriber 中的 handlerMap 则存放了各个插件的数据处理

PluginDataHandler 的实现类

AlibabaDubboPluginDataHandler
ApacheDubboPluginDataHandler
DividePluginDataHandler
HystrixPluginDataHandler
MonitorPluginDataHandler
RateLimiterPluginDataHandler
Resilience4JHandler
SentinelRuleHandle
SofaPluginDataHandler
TarsPluginDataHandler
WafPluginDataHandler

这里我们直接进入到DividePluginDataHandler#handlerSelector (因为只有 DividePluginDataHandler 重写了 handlerSelector 方法)

  • UpstreamCacheManager.getInstance().submit(selectorData); 因此我们直接看 UpstreamCacheManager 的逻辑
    • submit方法则只是进行简单的更新和删除
      • 客户端服务器下线时,也会触发一个event,此时传入的 upstreamList 为null,因此自然进行了下线
    • UpstreamCacheManager 启动的时候,创建了一个定时器线程,每隔30秒(默认)进行对 UPSTREAM_MAP 中的所有注册的对象进行主动探活检查
    • check:遍历每一个DivideUpstream,对upstreamUrl进行格式检测以及尝试建立连接进行探活,
      • 如果检测成功则放入 UPSTREAM_MAP_TEMP 中
      • 如果检测全部失败,则在 UPSTREAM_MAP_TEMP 中根据该选择器 id 进行删除
// UpstreamCacheManager

private static final Map<String, List<DivideUpstream>> UPSTREAM_MAP = 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 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());
    }
}

private List<DivideUpstream> check(final List<DivideUpstream> upstreamList) {
    List<DivideUpstream> resultList = Lists.newArrayListWithCapacity(upstreamList.size());
    for (DivideUpstream divideUpstream : upstreamList) {
      	// 通过对 upstreamUrl进行格式检测和 尝试socket链接进行探活
        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;
}

public class DivideUpstream implements Serializable {
    private String upstreamHost;
    private String protocol;
    private String upstreamUrl;
    private int weight;
  
    @Builder.Default
    private boolean status = true;
    private long timestamp;
    private int warmup;
}

// UpstreamCheckUtils
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);
        }
				// 如果是ip格式,则尝试进行建立socket连接
        return isHostConnector(hostPort[0], Integer.parseInt(hostPort[1]));
    } else {
      	// 如果非ip格式,则直接判断是否可达
        return isHostReachable(url);
    }
}

// Pattern.compile("(http://|https://)?(?:(?:[0,1]?\\d?\\d|2[0-4]\\d|25[0-5])\\.){3}(?:[0,1]?\\d?\\d|2[0-4]\\d|25[0-5]):\\d{0,5}");
private static boolean checkIP(final String url) {
    return PATTERN.matcher(url).matches();
}

private static boolean isHostConnector(final String host, final int port) {
  	// 根据socket建立是否成功
    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;
}
总结
  1. 本文跟踪 websocket 的数据同步的后续流程,发现了实际上就是
  2. BaseDataCache 中存放了几个map,缓存了注册上来的插件、选择器、规则的数据
    • PLUGIN_MAP:pluginName -> PluginData.
      SELECTOR_MAP:pluginName -> SelectorData.
      RULE_MAP:selectorId -> RuleData.
  3. divide 插件包含了一个 UpstreamCacheManager 用来定时对注册上来的url 进行探活
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值