前文在分析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 中设置的选择器已经匹配成功。
接着我们看从缓存中读取服务列表的逻辑。
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_MAP
和UPSTREAM_MAP_TEMP
,否则都把响应选择器id的数据删掉。
盲猜在soul-admin
更新选择器数据时会经过 sumbit
逻辑,在这个方法打上断点,然后在后台项目中新增一条选择器规则。新增成功后,立即进入了断点位置。
可以看到后台更新成功后,通过websocket的机制将选择器信息传到网关。由CommonPluginDataSubscriber
的subscribeDataHandler
方法交给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 将选择器数据同步到网关?以及后台自己有没有类似网关的探活机制?接下来的文章将会继续寻找答案。