个人知识库

引言

上一篇,梳理http 数据同步策略的变更通知机制,本篇开始探究配置变更通知到达后, soul-web 端的处理响应。

不同数据变更的通知机制应当是一致的,故本篇以 selector 配置变更通知为切入点进行深入。

通知处理入口

上回我们说到 HttpSyncDataService 的 doLongPolling,在其内部发起通知订阅并接收响应通知:

private void doLongPolling(final String server) {
...
String listenerUrl = server + "/configs/listener";
...
try {
// 发起监听请求
String json = this.httpClient.postForEntity(listenerUrl, httpEntity, String.class).getBody();
log.debug("listener result: [{}]", json);
groupJson = GSON.fromJson(json, JsonObject.class).getAsJsonArray("data");
} catch (RestClientException e) {
...
}
// 处理变更通知
if (groupJson != null) {
// fetch group configuration async.
ConfigGroupEnum[] changedGroups = GSON.fromJson(groupJson, ConfigGroupEnum[].class);
if (ArrayUtils.isNotEmpty(changedGroups)) {
log.info("Group config changed: {}", Arrays.toString(changedGroups));
// 获取组配置
this.doFetchGroupConfig(server, changedGroups);
}
}
}

在收到变更通知时,若存在配置组变更,则按变更组获取相应配置。

获取配置

获取组配置处理如下:

private void doFetchGroupConfig(final String server, final ConfigGroupEnum... groups) {
...
String url = server + "/configs/fetch?" + StringUtils.removeEnd(params.toString(), "&");
...
try {
json = this.httpClient.getForObject(url, String.class);
} catch (RestClientException e) {
...
}
// update local cache
boolean updated = this.updateCacheWithJson(json);
...
}

内部发起配置获取请求并更新本地缓存。

更新配置组缓存

由 HttpSyncDataService 实现本地缓存更新:

  private boolean updateCacheWithJson(final String json) {
JsonObject jsonObject = GSON.fromJson(json, JsonObject.class);
JsonObject data = jsonObject.getAsJsonObject("data");
// if the config cache will be updated?
return factory.executor(data);
}

转成 Json 对象后交由 DataRefreshFactory 进行处理。

DataRefreshFactory 处理如下:

public boolean executor(final JsonObject data) {
final boolean[] success = {false};
ENUM_MAP.values().parallelStream().forEach(dataRefresh -> success[0] = dataRefresh.refresh(data));
return success[0];
}

调用相应数据刷新类刷新数据。

统一由 AbstractDataRefresh 的 refresh 进行处理:

public Boolean refresh(final JsonObject data) {
boolean updated = false;
JsonObject jsonObject = convert(data);
if (null != jsonObject) {
ConfigData<T> result = fromJson(jsonObject);
if (this.updateCacheIfNeed(result)) {
updated = true;
refresh(result.getData());
}
}
return updated;
}

先更新本地缓存,再调用子类实现的 refresh。

此处的更新本地缓存处理,由子类 SelectorDataRefresh 的 updateCacheIfNeed 实现:

protected boolean updateCacheIfNeed(final ConfigData<SelectorData> result) {
return updateCacheIfNeed(result, ConfigGroupEnum.SELECTOR);
}

向父类 AbstractDataRefresh 的 updateCacheIfNeed 指定更新 selector 配置组。

父类 AbstractDataRefresh 的 updateCacheIfNeed 处理:

protected boolean updateCacheIfNeed(final ConfigData<T> newVal, final ConfigGroupEnum groupEnum) {
// 首次初始化缓存
if (GROUP_CACHE.putIfAbsent(groupEnum, newVal) == null) {
return true;
}
ResultHolder holder = new ResultHolder(false);
GROUP_CACHE.merge(groupEnum, newVal, (oldVal, value) -> {
// 必须比较最后更新时间
if (!StringUtils.equals(oldVal.getMd5(), newVal.getMd5()) && oldVal.getLastModifyTime() < newVal.getLastModifyTime()) {
...
holder.result = true;
return newVal;
}
...
return oldVal;
});
return holder.result;
}

通过比较新老缓存的 MD5 值来判定是否发生变更,存在变更则更新本地缓存(注意还有最后更新时间判定)。

处理刷新事件

SelectorDataRefresh 的 refresh 实现:

protected void refresh(final List<SelectorData> data) {
if (CollectionUtils.isEmpty(data)) {
log.info("clear all selector cache, old cache");
data.forEach(pluginDataSubscriber::unSelectorSubscribe);
pluginDataSubscriber.refreshSelectorDataAll();
} else {
// update cache for UpstreamCacheManager
pluginDataSubscriber.refreshSelectorDataAll();
data.forEach(pluginDataSubscriber::onSelectorSubscribe);
}
}
  • 若最新数据为空,则循环取消订阅并刷新所有选择器数据,实际是清空选择器缓存。
  • 若最新数据不为空,则刷新所有选择器数据并循环响应选择器订阅事件处理,实际是更新上游服务缓存。

取消订阅

CommonPluginDataSubscriber 实现订阅取消:

public void unSelectorSubscribe(final SelectorData selectorData) {
subscribeDataHandler(selectorData, DataEventTypeEnum.DELETE);
}

subscribeDataHandler 对 selectorData 的 delete 处理:

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

从 BaseDataCache 删除目标选择器数据,并移除选择器。

此处由 DividePluginDataHandler 提供 removeSelector 实现:

public void removeSelector(final SelectorData selectorData) {
UpstreamCacheManager.getInstance().removeByKey(selectorData.getId());
}

根据 selector id 移除缓存的上游服务,注意只是从 UPSTREAM_MAP_TEMP 移除

public void removeByKey(final String key) {
UPSTREAM_MAP_TEMP.remove(key);
}

刷新数据

CommonPluginDataSubscriber 实现数据刷新:

public void refreshSelectorDataAll() {
BaseDataCache.getInstance().cleanSelectorData();
}

注意这里的 refresh all 实际是做的 clean 操作。

BaseDataCache 的 cleanSelectorData 处理:

public void cleanSelectorData() {
SELECTOR_MAP.clear();
}

直接清除 SELECTOR_MAP 所有数据。

响应订阅

CommonPluginDataSubscriber 实现订阅响应:

public void onSelectorSubscribe(final SelectorData selectorData) {
subscribeDataHandler(selectorData, DataEventTypeEnum.UPDATE);
}

subscribeDataHandler 对 selectorData 的 update 处理:

private <T> void subscribeDataHandler(final T classData, final DataEventTypeEnum dataType) {
Optional.ofNullable(classData).ifPresent(data -> {
if (data instanceof PluginData) {
...
} 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) {
...
}
} else if (data instanceof RuleData) {
...
}
});
}

缓存选择器数据到 BaseDataCache,并处理选择器。

此处由 DividePluginDataHandler 提供 handlerSelector 实现:

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

提交选择器数据到 UpstreamCacheManager。

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

根据 selector id 更新 UPSTREAM_MAP 和 UPSTREAM_MAP_TEMP。

总结

本篇梳理和分析了配置变更通知到达后 soul-web 端的处理流程,最终处理主要是更新本地配置缓存以及维护上游服务散列表。

soul-web收到变更通知后处理流程如下:

soul-web 端收到响应

  • 若配置组数据存在变更,则发起获取配置请求获取最新配置信息

    • 更新配置组缓存
    • 循环处理配置数据刷新事件
      • 若最新配置数据为空,则删除本地配置数据并移除上游服务
      • 若最新配置数据不为空,则缓存配置组数据并更新上游服务
  • 若配置组数据无变更,不作处理

【Soul网关探秘】http数据同步-Web端处理变更通知的更多相关文章

  1. 【Soul网关探秘】http数据同步-Admin通知前处理

    引言 本篇开始研究 Soul 网关 http 数据同步,将分为三篇进行分析: <Admin通知前处理> <变更通知机制> <Bootstrap处理变更通知> 希望三 ...

  2. SSE(Server-sent events)技术在web端消息推送和实时聊天中的使用

    最近在公司闲着没事研究了几天,终于搞定了SSE从理论到实际应用,中间还是有一些坑的. 1.SSE简介 SSE(Server-sent events)翻译过来为:服务器发送事件.是基于http协议,和W ...

  3. Django ORM记录的增删改查结合web端

    模版语法分配变量 在views.py文件中定义一个视图函数show_data: def show_data(request): # 定义一个字典 并将它展示在前端HTML文件 user_dic = { ...

  4. Soul 网关 Nacos 数据同步源码解析

    学习目标: 学习Soul 网关 Nacos 数据同步源码解析 学习内容: 环境配置 Soul 网关 Nacos 数据同步基本概念 源码分析 学习时间:2020年1月28号 早7点 学习产出: 环境配置 ...

  5. 实现web数据同步的四种方式

    http://www.admin10000.com/document/6067.html 实现web数据同步的四种方式 1.nfs实现web数据共享 2.rsync +inotify实现web数据同步 ...

  6. 使用HslCommunication实现PLC数据的远程客户端监视,以及web端实时监视,远程操作设备示例

    前言 本文主要是演示一个例子,服务器后台程序从PLC采集数据,并推送给在线客户端显示,以及推送给web端进行实时的显示,还支持远程操作,支持安卓端的同步监视和远程操作,关于HslCommunicati ...

  7. linux下实现web数据同步的四种方式(性能比较)

    实现web数据同步的四种方式 ======================================= 1.nfs实现web数据共享2.rsync +inotify实现web数据同步3.rsyn ...

  8. 利用websocket实现手机扫码登陆后,同步登陆信息到web端页面

    新手必看 广播系统 事件系统 准备工作 初始化项目 引入 laravel-websockets 软件包 启动 websocket 监听 主要流程 创建两个页面 建立 socket 连接 手机端扫码登陆 ...

  9. Web端的Tab控件在切换Tab时Load数据出错的处理

    我们在应用Web端的Tab控件时,不管是Jquery easyui的还是Ext的Tab控件都会遇到一个问题,在Tab1正在加载数据的时候我们切换到Tab2,再切换回来,Load数据的控件就会出错,出错 ...

随机推荐

  1. [leetcode]39combinationsum回溯法找几个数的和为目标值

    import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Given a set of can ...

  2. Arduino IDE 开发 ESP-01S/ESP-01物联网实战检测温度湿度上传MQTT服务器

    一.硬件准备 USB转ESP8266两块.DHT11温度湿度传感器.ESP8266-01/ESP8266-01一块(如果学习的话多买几块,ESP-01/ESP-01S的区别) USB转ESP8266 ...

  3. 学习笔记之Python人机交互小项目二:名片管理系统

    继上次利用列表相关知识做了简单的人机交互的小项目名字管理系统后,当学习到字典时,老师又让我们结合列表和字典的知识,结合一起做一个名片管理系统,这里分享给在学习Python的伙伴! 1.不使用函数 1 ...

  4. SQL操作符的优化

    操作符优化        IN 操作符 用IN写出来的SQL的优点是比较容易写及清晰易懂,这比较适合现代软件开发的风格. 但是用IN的SQL性能总是比较低的,从ORACLE执行的步骤来分析用IN的SQ ...

  5. 敏捷史话(三):笃定前行的勇者——Ken Schwaber

    很多人之所以平凡,并不在于能力的缺失,而是因为缺乏迈出一步的勇气.只有少部分的人可以带着勇气和坚持,走向不凡.Ken Schwaber 就是这样的人,他带着他的勇气和坚持在敏捷的道路上不断前行,以实现 ...

  6. 容器编排系统K8s之crd资源

    前文我们了解了k8s节点污点和pod的对节点污点容忍度相关话题,回顾请参考:https://www.cnblogs.com/qiuhom-1874/p/14255486.html:今天我们来聊一下扩展 ...

  7. Docker 镜像仓库使用(六)

    阿里云docker 容器镜像服务: www.aliyun.com 1 服务开通 (开通的时候要求创建密码请牢记此密码) 2 创建命名空间 3 创建镜像仓库 4 linux 客户端登录 登录: dock ...

  8. Kubernetes学习笔记之安装minikube并运行个简单应用程序

    前言:本笔记仅记录学习记录,可能存在错误!!!使用的环境是Ubuntu Desktop 20.04,也有用Windows 10 操作的,根据的文档是minikube的文档教程,链接:https://m ...

  9. MyBatis初级实战之二:增删改查

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  10. oracle RAC和RACOneNode之间的转换

    Convert RAC TO RACOneNode 1.查看资源状态 [grid@rac01 ~]$ crsctl status res -t 从这里看到,数据库的名字叫racdb 2.查看实例 [o ...