目的
最终目的是搞清楚网关中服务节点缓存在各种情况下的变动, 这样在请求网关转发 HTTP 服务时, 才能做到心中有数.
拆分任务
看到最终目的, 我会有几个想法:
网关的缓存节点信息应该在何时变动 ?
后台数据会同步网关, 那后台数据如何变动, 如何通知网关 ?
根据这些问题, 就得到了要进行的任务 ==>
HTTP 服务注册时
HTTP 服务下线时 (探活)
UpstreamCheckService 的 UPSTREAM_MAP 中存放着后台服务节点信息
public class UpstreamCheckService {
private static final Map<String, List<DivideUpstream>> UPSTREAM_MAP = Maps.newConcurrentMap();
}
UPSTREAM_MAP 中的数据有两个来源, 一是启动时从数据库拿, 二是服务注册时传入.
数据库获取方式相对简单, 在 UpstreamCheckService#setup 中有体现.
服务注册路径图:
SpringMvcClientBeanPostProcessor(http服务端): 收集Controller层信息, 请求后台的路径: /soul-client/springmvc-register
SoulController: 后台对外暴露的 Http api, 供请求服务注册
SoulClientRegisterServiceImpl: 直接调用 UpstreamCheckService#submit 传入要新增的节点数据
后台通知路径图 (Websocket模式):
SoulClientRegisterServiceImpl: 发送自定义事件, 由订阅事件中心处理 (Spring 发布订阅模式)
DataChangedEventDispatcher: 事件接收和分发类, 根据事件类型调用监听类的对应方法
WebsocketCollector: 管理 Websocket 通信, 维护连接的 session 会话.
UpstreamCheckService 的 UPSTREAM_MAP 中存放着网关的 divide 插件服务节点信息
public final class UpstreamCacheManager {
private static final Map<String, List<DivideUpstream>> UPSTREAM_MAP = Maps.newConcurrentMap();
}
服务信息变动路径图 (Websocket模式):
SoulWebsocketClient: 后台 wesocket 信息在这里被监听, 并发送给 WebsocketDataHandler 处理.
WebsocketDataHandler: 根据事件类型, 选择对应处理器 (PluginDataHandler、RuleDataHandler等)
AbstractDataHandler: 根据事件变动类型(refresh、update等), 调用处理器对应方法, 具体实现类会调用到 CommonPluginDataSubscriber 订阅器
CommonPluginDataSubscriber: 这里存有所有注册为 Bean 的事件处理器, 这些处理器来自各个扩展插件, 会调用他们的 handlerXXX() 方法
DividePluginDataHandler: 更新或移除缓存管理器中服务节点信息
探活路径图:
后台缓存管理器在初始化时, 会启动定时器 (间隔10秒) 检测缓存中节点服务的活性, 方式是尝试 Socket#connect 连接.
通知路径图 (Websocket模式):
这块与 HTTP 服务注册时后台通知网关很相似, 最终都会流入到 DataChangedEventDispatcher 事件分发器, 并通过 Websocket 通信发出.
不同的是发起点, HTTP 服务注册时, 由具体的 HTTP 服务发起后台请求, 而 HTTP 服务下线时, 是由后台缓存管理器自身的定时探活模块发起.
网关的缓存更新有两种方式, 一个是接收到后台信息变更通知, 这点与服务注册时一致, 不再赘述.
第二种是 divide 的缓存管理器, 可开启如同后台的定时探活, 也是通过 Socket#connect 去判断服务是否可用, 但这块默认配置是关闭的, 且检测间隔时间为 30S.
HTTP 服务注册时, 后台会将服务信息写入数据库.
public class SoulClientRegisterServiceImpl implements SoulClientRegisterService {
private String registerSelector(final String contextPath, final String rpcType, final String appName, final String uri) {
// ...
if (RpcTypeEnum.DUBBO.getName().equals(rpcType)) {
} else {
//... is divide
// 通知缓存管理器节点信息
upstreamCheckService.submit(selectorDTO.getName(), divideUpstream);
}
// ...
// 这里会将数据写入数据库
return selectorService.register(selectorDTO);
}
}
引用 “进击的巨人” 里常看见的一句话, 根据现在可以公开的情报:
一点思考 : Socket 连接有些过重, 改进的话参考一些注册中心的实现, 比如 Eureka 会让服务节点引入 EurekaClient , 与服务端 EurekaServer 做心跳检测, 是一种比较轻的探活方式.