【高性能网关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 进行删除
- submit方法则只是进行简单的更新和删除
// 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;
}
总结
- 本文跟踪 websocket 的数据同步的后续流程,发现了实际上就是
- BaseDataCache 中存放了几个map,缓存了注册上来的插件、选择器、规则的数据
- PLUGIN_MAP:pluginName -> PluginData.
SELECTOR_MAP:pluginName -> SelectorData.
RULE_MAP:selectorId -> RuleData.
- PLUGIN_MAP:pluginName -> PluginData.
- divide 插件包含了一个 UpstreamCacheManager 用来定时对注册上来的url 进行探活