今天我们研究下soul 的zookeeper同步数据到网关这块。
1、把zookeeper启动起来
安装zookeeper
> brew install zookeeper
查看配置文件 缺省的配置文件在以下目录中
> cd /usr/local/etc/zookeeper/
启动服务
> zkServer start
2、启动sole-admin
3、启动sole-bootstrap
-
Application-local.yml 新增zookeeper 配置
soul : sync: zookeeper: url: localhost:2181 sessionTimeout: 5000 connectionTimeout: 2000 -
运行 SoulBootstrapApplication
源码解析:
-
Soul 配置了 websocket, 关于websocket的源码部分可以参考上篇文章(juejin.cn/post/691988…)
-
同上篇文章到思路一样, 我们在sole-bootstrap的配置文件application-local.yml 新增了zookeeper的配置,我们查询soul.sync.zookeeper, 共查询到四个类,ZookeeperConfig,ZookeeperProperties, ZookeeperListener, ZookeeperSyncDataConfiguration。其中ZookeeperConfig,ZookeeperProperties是同步配置文件中zookeeper的配置信息,ZookeeperListener, ZookeeperSyncDataConfiguration 是进行zookeeper的监听和数据同步,而ZookeeperListener是当ZookeeperSyncDataConfiguration不存在时执行,所以我们启动soul-bootstrap启动的是ZookeeperSyncDataConfiguration。
@Configuration
@ConditionalOnProperty(prefix = "soul.sync.zookeeper", name = "url")
@Import(ZookeeperConfiguration.class)
static class ZookeeperListener {
/**
* 当ZookeeperSyncDataConfiguration不存在时执行.
*
* @param zkClient the zk client
* @return the data changed listener
*/
@Bean
@ConditionalOnMissingBean(ZookeeperDataChangedListener.class)
public DataChangedListener zookeeperDataChangedListener(final ZkClient zkClient) {
return new ZookeeperDataChangedListener(zkClient);
}
/**
* Zookeeper data init zookeeper data init.
*
* @param zkClient the zk client
* @param syncDataService the sync data service
* @return the zookeeper data init
*/
@Bean
@ConditionalOnMissingBean(ZookeeperDataInit.class)
public ZookeeperDataInit zookeeperDataInit(final ZkClient zkClient, final SyncDataService syncDataService) {
return new ZookeeperDataInit(zkClient, syncDataService);
}
}
public class ZookeeperSyncDataConfiguration {
/**
* 当ZookeeperSyncDataConfiguration不存在时执行.
*
* @param zkClient the zk client
* @param pluginSubscriber the plugin subscriber
* @param metaSubscribers the meta subscribers
* @param authSubscribers the auth subscribers
* @return the sync data service
*/
@Bean
public SyncDataService syncDataService(final ObjectProvider<ZkClient> zkClient, final ObjectProvider<PluginDataSubscriber> pluginSubscriber,
final ObjectProvider<List<MetaDataSubscriber>> metaSubscribers, final ObjectProvider<List<AuthDataSubscriber>> authSubscribers) {
log.info("you use zookeeper sync soul data.......");
return new ZookeeperSyncDataService(zkClient.getIfAvailable(), pluginSubscriber.getIfAvailable(),
metaSubscribers.getIfAvailable(Collections::emptyList), authSubscribers.getIfAvailable(Collections::emptyList));
}
/**
* register zkClient in spring ioc.
*
* @param zookeeperConfig the zookeeper configuration
* @return ZkClient {@linkplain ZkClient}
*/
@Bean
public ZkClient zkClient(final ZookeeperConfig zookeeperConfig) {
return new ZkClient(zookeeperConfig.getUrl(), zookeeperConfig.getSessionTimeout(), zookeeperConfig.getConnectionTimeout());
}
}
- 我们启动sole-bootstrap, 对上面的类进行debug
我们发现项目启动时,首先进行 ZookeeperSyncDataConfiguration 的SyncDataService 的初始化, 并进行data、appAuth、metaData的发布, 因为初始数据都为空,所以都会从zookeeper先获取初始数据并缓存到本地和zookeeper节点。
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); // 项目启动时,进行各个插件的Plugin、Selector、Rule 缓存到本地缓存 BaseDataCache的 PLUGIN_MAP、SELECTOR_MAP、RULE_MAP并创建对应zk节点
watcherSelector(pluginName); // 项目启动时,将AppAuthData缓存到本地缓存SignAuthDataCache的AUTH_MAP并创建对应zk节点
watcherRule(pluginName); // 项目启动时,将MetaData缓存到本地缓存MetaDataCache的META_DATA_MAP并创建对应zk节点
}
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 cachePluginData(final PluginData pluginData) {
Optional.ofNullable(pluginData).flatMap(data -> Optional.ofNullable(pluginDataSubscriber)).ifPresent(e -> e.onSubscribe(pluginData));
}
@Override
public void onSubscribe(final PluginData pluginData) {
subscribeDataHandler(pluginData, DataEventTypeEnum.UPDATE);
}
public class CommonPluginDataSubscriber implements PluginDataSubscriber {
private <T> void subscribeDataHandler(final T classData, final DataEventTypeEnum dataType) {
Optional.ofNullable(classData).ifPresent(data -> {
if (data instanceof PluginData) {
PluginData pluginData = (PluginData) data;
if (dataType == DataEventTypeEnum.UPDATE) {
BaseDataCache.getInstance().cachePluginData(pluginData);
Optional.ofNullable(handlerMap.get(pluginData.getName())).ifPresent(handler -> handler.handlerPlugin(pluginData));
} else if (dataType == DataEventTypeEnum.DELETE) {
BaseDataCache.getInstance().removePluginData(pluginData);
Optional.ofNullable(handlerMap.get(pluginData.getName())).ifPresent(handler -> handler.removePlugin(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) {
BaseDataCache.getInstance().removeSelectData(selectorData);
Optional.ofNullable(handlerMap.get(selectorData.getPluginName())).ifPresent(handler -> handler.removeSelector(selectorData));
}
} else if (data instanceof RuleData) {
RuleData ruleData = (RuleData) data;
if (dataType == DataEventTypeEnum.UPDATE) {
BaseDataCache.getInstance().cacheRuleData(ruleData);
Optional.ofNullable(handlerMap.get(ruleData.getPluginName())).ifPresent(handler -> handler.handlerRule(ruleData));
} else if (dataType == DataEventTypeEnum.DELETE) {
BaseDataCache.getInstance().removeRuleData(ruleData);
Optional.ofNullable(handlerMap.get(ruleData.getPluginName())).ifPresent(handler -> handler.removeRule(ruleData));
}
}
});
}
、、、
}
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);
}
private List<String> zkClientGetChildren(final String parent) {
if (!zkClient.exists(parent)) {
zkClient.createPersistent(parent, true);
}
return zkClient.getChildren(parent);
}
- 我们在soul-admin的后台对 System-manage -> Plugin 中的插件进行修改, 发现 DataChangedEvent 的监听事件执行的依然是 执行 WebsocketDataChangedListener的插件变更方法,ZookeeperDataChangedListener 没生效。
我们检查soul-admin 的ZookeeperDataChangedListener初始化的代码和配置文件application.yml发现,ZookeeperDataChangedListener 的初始化需要 soul.sync.zookeeper.url有值,不初始化WebsocketListener需要soul.sync.websocket.enabled = false。于是把soul.sync.websocket.enabled 置为false, 添加对应的zookeeper 配置,重启soul-admin、soul-bootstrap。
@Configuration
@ConditionalOnProperty(prefix = "soul.sync.zookeeper", name = "url")
@Import(ZookeeperConfiguration.class)
static class ZookeeperListener {
/**
* Config event listener data changed listener.
*
* @param zkClient the zk client
* @return the data changed listener
*/
@Bean
@ConditionalOnMissingBean(ZookeeperDataChangedListener.class)
public DataChangedListener zookeeperDataChangedListener(final ZkClient zkClient) {
return new ZookeeperDataChangedListener(zkClient);
}
/**
* Zookeeper data init zookeeper data init.
*
* @param zkClient the zk client
* @param syncDataService the sync data service
* @return the zookeeper data init
*/
@Bean
@ConditionalOnMissingBean(ZookeeperDataInit.class)
public ZookeeperDataInit zookeeperDataInit(final ZkClient zkClient, final SyncDataService syncDataService) {
return new ZookeeperDataInit(zkClient, syncDataService);
}
}
@Configuration
@ConditionalOnProperty(name = "soul.sync.websocket.enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(WebsocketSyncProperties.class)
static class WebsocketListener {
/**
* Config event listener data changed listener.
*
* @return the data changed listener
*/
@Bean
@ConditionalOnMissingBean(WebsocketDataChangedListener.class)
public DataChangedListener websocketDataChangedListener() {
return new WebsocketDataChangedListener();
}
...
}
soul:
database:
dialect: mysql
init_script: "META-INF/schema.sql"
init_enable: true
sync:
websocket:
enabled: false
zookeeper:
url: localhost:2181
sessionTimeout: 5000
connectionTimeout: 2000
然后继续进行System-manage -> Plugin 中插件的修改, 发现ZookeeperDataChangedListener生效,更改后页面、本地缓存都生效。
总结
我们验证zookeeper同步数据时,需要先把默认打开的websocket关闭,打开对应的zookeeper配置即可。多分析配置以及配置应用的地方,对应类需要引用哪些配置,再加上debug,就能准确定位问题。