一、准备工作
已经到了第7点,可以不用那么啰嗦,简洁一下,准备工作要做的是
- 启动zk
- 打开
soul-admin即控制台(后文用控制台代替)和soul-bootstrap即网关服务(后文用网关服务代替)配置中的ZooKeeper配置 - 启动控制台和网关服务
二、ZooKeeper方式同步数据
先抄一段Soul官方文档
基于 zookeeper 的同步原理很简单,主要是依赖 zookeeper 的 watch 机制,soul-web 会监听配置的节点,soul-admin 在启动的时候,会将数据全量写入 zookeeper,后续数据发生变更时,会增量更新 zookeeper 的节点,与此同时,soul-web 会监听配置信息的节点,一旦有信息变更时,会更新本地缓存。
然后继续上这幅图,依旧从 DataChangedEventDispatcher 类讲起
DataChangedEventDispatcher#onApplicationEvent 方法会遍历注入进来 DataChangedListener 接口的实现类,此处因为打开了zk相关配置,注入了 ZookeeperDataChangedListener bean
1、从监听事件往下看
此处都是实现细节了,举一个具体的发送 Rule 数据,使用的 ZookeeperDataChangedListener#onRuleChanged 方法给节点更新数据
@Override
public void onRuleChanged(final List<RuleData> changed, final DataEventTypeEnum eventType) {
if (eventType == DataEventTypeEnum.REFRESH) {
final String selectorParentPath = ZkPathConstants.buildRuleParentPath(changed.get(0).getPluginName());
deleteZkPathRecursive(selectorParentPath);
}
for (RuleData data : changed) {
final String ruleRealPath = ZkPathConstants.buildRulePath(data.getPluginName(), data.getSelectorId(), data.getId());
if (eventType == DataEventTypeEnum.DELETE) {
deleteZkPath(ruleRealPath);
continue;
}
final String ruleParentPath = ZkPathConstants.buildRuleParentPath(data.getPluginName());
createZkNode(ruleParentPath);
//create or update
upsertZkNode(ruleRealPath, data);
}
}
2、从发布事件往上看
又到了昨天的 Spring 框架中的事件发布类 ApplicationEventPublisher,看一下有哪些调用者。
其实依旧是昨天看到的那些,大部分是控制台的 REST 接口更新数据,然后发布事件发送数据给 zkNode。这里有一个ZookeeperDataInit#run方法
public class ZookeeperDataInit implements CommandLineRunner {
private final ZkClient zkClient;
private final SyncDataService syncDataService;
/**
* Instantiates a new Zookeeper data init.
*
* @param zkClient the zk client
* @param syncDataService the sync data service
*/
public ZookeeperDataInit(final ZkClient zkClient, final SyncDataService syncDataService) {
this.zkClient = zkClient;
this.syncDataService = syncDataService;
}
@Override
public void run(final String... args) {
String pluginPath = ZkPathConstants.PLUGIN_PARENT;
String authPath = ZkPathConstants.APP_AUTH_PARENT;
String metaDataPath = ZkPathConstants.META_DATA;
if (!zkClient.exists(pluginPath) && !zkClient.exists(authPath) && !zkClient.exists(metaDataPath)) {
syncDataService.syncAll(DataEventTypeEnum.REFRESH);
}
}
}
我们可以看到 ZookeeperDataInit 类实现接口 CommandLineRunner,CommandLineRunner 的作用是在 Spring Main方法执行 SpringApplication.run() 之前,执行它的实现类的run方法,适合做一些启动数据加载。我们又看到昨天的老朋友同步全部数据方法 syncDataService.syncAll(DataEventTypeEnum.REFRESH),只不过参数变成了DataEventTypeEnum.REFRESH,为什么是这个参数,答案其实都在 ZookeeperDataChangedListener 处理各种事件的方法里,根据事件类型做不同的处理,REFRESH 这个参数基本上就是全量更新或者删除再全量更新。
3、从客户端看 ZooKeeper 通信
通过依赖的soul-spring-boot-starter-sync-data-zookeeper 可以生成 SyncDataService 接口的实现类 ZookeeperSyncDataService bean,来看一下 ZookeeperSyncDataService 类的构造方法。
public ZookeeperSyncDataService(final ZkClient zkClient, final PluginDataSubscriber pluginDataSubscriber,
final List<MetaDataSubscriber> metaDataSubscribers, final List<AuthDataSubscriber> authDataSubscribers) {
this.zkClient = zkClient;
this.pluginDataSubscriber = pluginDataSubscriber;
this.metaDataSubscribers = metaDataSubscribers;
this.authDataSubscribers = authDataSubscribers;
watcherData();
watchAppAuth();
watchMetaData();
}
private void watcherData() {
final String pluginParent = ZkPathConstants.PLUGIN_PARENT;
List<String> pluginZKs = zkClientGetChildren(pluginParent);
for (String pluginName : pluginZKs) {
watcherAll(pluginName);
}
zkClient.subscribeChildChanges(pluginParent, (parentPath, currentChildren) -> {
if (CollectionUtils.isNotEmpty(currentChildren)) {
for (String pluginName : currentChildren) {
watcherAll(pluginName);
}
}
});
}
private void watcherAll(final String pluginName) {
watcherPlugin(pluginName);
watcherSelector(pluginName);
watcherRule(pluginName);
}
private void watcherPlugin(final String pluginName) {
String pluginPath = ZkPathConstants.buildPluginPath(pluginName);
if (!zkClient.exists(pluginPath)) {
zkClient.createPersistent(pluginPath, true);
}
cachePluginData(zkClient.readData(pluginPath));
subscribePluginDataChanges(pluginPath, pluginName);
}
private void watcherSelector(final String pluginName) {
String selectorParentPath = ZkPathConstants.buildSelectorParentPath(pluginName);
List<String> childrenList = zkClientGetChildren(selectorParentPath);
if (CollectionUtils.isNotEmpty(childrenList)) {
childrenList.forEach(children -> {
String realPath = buildRealPath(selectorParentPath, children);
cacheSelectorData(zkClient.readData(realPath));
subscribeSelectorDataChanges(realPath);
});
}
subscribeChildChanges(ConfigGroupEnum.SELECTOR, selectorParentPath, childrenList);
}
private void watcherRule(final String pluginName) {
String ruleParent = ZkPathConstants.buildRuleParentPath(pluginName);
List<String> childrenList = zkClientGetChildren(ruleParent);
if (CollectionUtils.isNotEmpty(childrenList)) {
childrenList.forEach(children -> {
String realPath = buildRealPath(ruleParent, children);
cacheRuleData(zkClient.readData(realPath));
subscribeRuleDataChanges(realPath);
});
}
subscribeChildChanges(ConfigGroupEnum.RULE, ruleParent, childrenList);
}
private void watchAppAuth() {
final String appAuthParent = ZkPathConstants.APP_AUTH_PARENT;
List<String> childrenList = zkClientGetChildren(appAuthParent);
if (CollectionUtils.isNotEmpty(childrenList)) {
childrenList.forEach(children -> {
String realPath = buildRealPath(appAuthParent, children);
cacheAuthData(zkClient.readData(realPath));
subscribeAppAuthDataChanges(realPath);
});
}
subscribeChildChanges(ConfigGroupEnum.APP_AUTH, appAuthParent, childrenList);
}
private void watchMetaData() {
final String metaDataPath = ZkPathConstants.META_DATA;
List<String> childrenList = zkClientGetChildren(metaDataPath);
if (CollectionUtils.isNotEmpty(childrenList)) {
childrenList.forEach(children -> {
String realPath = buildRealPath(metaDataPath, children);
cacheMetaData(zkClient.readData(realPath));
subscribeMetaDataChanges(realPath);
});
}
subscribeChildChanges(ConfigGroupEnum.APP_AUTH, metaDataPath, childrenList);
}
代码贴的有点多,其实就是订阅 ZooKeeper 各种目录,获取各类数据的更新,也就是在网关启动的时候做好订阅 ZooKeeper 各类目录。
网关服务通过 ZooKeeper 获取数据的代码很少,只有 ZookeeperSyncDataService 一个类。
三、网关本地缓存什么样
昨天和今天对于网关服务获取数据,都只是讲到得到了数据,并没有接着往下讲得到数据后再怎么处理。代码结构不负责,只是有点多。最终都是到了各个订阅类的静态常量 Map 里。主要有三大类 Map 缓存,都已经在 ZookeeperSyncDataService 的属性里定义了。
-
PluginDataHandler 插件类数据,Soul使用的各类插件数据
-
MetaDataSubscriber 元数据类,接入需要元数据的RPC框架,例如Dubbo、SOFA、Tars
-
AuthDataSubscriber 权限类,其实我也没细看