目录
1.前情回顾
2.配置
2.1 soul-admin
2.2 soul-bootstrap
3.启动
3.1 启动 zookeeper
3.2 启动 soul-admin
3.2.1 注入 ZookeeperDataChangedListener 对象
3.2.2 注入 ZookeeperDataInit 对象
1.前情回顾
紧接着昨天的 websocket,今天来看下 zookeeper。
想知道websocket 相关的出门左转【Soul源码阅读】9.soul-admin 与 soul-bootstrap 同步机制之 websocket 解析
2.配置
数据同步策略官网链接 https://dromara.org/zh-cn/docs/soul/user-dataSync.html
因为数据同步方式是双方规定的,必须保持一致,所以修改配置也是 soul-admin 和 soul-bootstrap 都要改。
其实配置都是类似的,之前默认是 websocket,需要把 websocket 相关的配置注释掉,打开 zookeeper 相关配置。
2.1 soul-admin
# 修改前
soul:
sync:
websocket:
enabled: true
# zookeeper:
# url: localhost:2181
# sessionTimeout: 5000
# connectionTimeout: 2000
# 修改后
soul:
sync:
# websocket:
# enabled: true
zookeeper:
url: localhost:2181
sessionTimeout: 5000
connectionTimeout: 2000
配置文件对应 Bean:
@Data
@ConfigurationProperties(prefix = "soul.sync.zookeeper")
public class ZookeeperProperties {
private String url;
private Integer sessionTimeout;
private Integer connectionTimeout;
private String serializer;
}
2.2 soul-bootstrap
application-local.yml
# 修改前
soul:
sync:
websocket :
urls: ws://localhost:9095/websocket
# zookeeper:
# url: localhost:2181
# sessionTimeout: 5000
# connectionTimeout: 2000
# 修改后
soul:
sync:
# websocket :
# urls: ws://localhost:9095/websocket
zookeeper:
url: localhost:2181
sessionTimeout: 5000
connectionTimeout: 2000
配置文件对应 Bean:ZookeeperConfig
@Data
@ConfigurationProperties(prefix = "soul.sync.zookeeper")
public class ZookeeperConfig {
private String url;
private Integer sessionTimeout;
private Integer connectionTimeout;
private String serializer;
}
3.启动
3.1 启动 zookeeper
3.2 启动 soul-admin
基于昨天的分析,启动前可以在 DataSyncConfiguration.ZookeeperListener 里2个方法都打上断点,启动时一定会进。
// DataSyncConfiguration.java
@Configuration
public class DataSyncConfiguration {
...
/**
* The type Zookeeper listener.
*/
@Configuration
@ConditionalOnProperty(prefix = "soul.sync.zookeeper", name = "url")
@Import(ZookeeperConfiguration.class)
static class ZookeeperListener {
@Bean
@ConditionalOnMissingBean(ZookeeperDataChangedListener.class)
public DataChangedListener zookeeperDataChangedListener(final ZkClient zkClient) {
// 1.返回基于 zookeeper 的监听器对象
return new ZookeeperDataChangedListener(zkClient);
}
@Bean
@ConditionalOnMissingBean(ZookeeperDataInit.class)
public ZookeeperDataInit zookeeperDataInit(final ZkClient zkClient, final SyncDataService syncDataService) {
// 2.返回 zookeeper 初始化对象
return new ZookeeperDataInit(zkClient, syncDataService);
}
}
...
}
3.2.1 注入 ZookeeperDataChangedListener 对象
1.返回基于 zookeeper 的监听器对象,并且注册到 spring 容器中,方便我们后续处理监听事件时使用。
2. ZookeeperDataInit,看名字是 zookeeper 数据的初始化,进到类里一探究竟。
3.2.2 注入 ZookeeperDataInit 对象
/**
* The type Zookeeper data init.
*
* @author xiaoyu
*/
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)) {
// 只有3个节点都不存在时,才同步全量数据
syncDataService.syncAll(DataEventTypeEnum.REFRESH);
}
}
}
这个类实现了 CommandLineRunner 这个接口,并覆写了 run 方法。
run 方法会在 spring 启动容器后被调用。
此时会判断 zk 中是否存在3个节点,只有3个节点都不存在时,会同步全量数据。(第一次使用 zk,会同步全量数据)
// SyncDataServiceImpl.java
@Override
public boolean syncAll(final DataEventTypeEnum type) {
// 同步 appAuth 数据
appAuthService.syncData();
// 同步 plugin 数据
// 从 plugin 表中获取所有数据
List<PluginData> pluginDataList = pluginService.listAll();
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.PLUGIN, type, pluginDataList));
// 同步 selector 数据
// 从 selector 表中获取所有数据
List<SelectorData> selectorDataList = selectorService.listAll();
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, type, selectorDataList));
// 同步 rule 数据
// 从 rule 表中获取所有数据
List<RuleData> ruleDataList = ruleService.listAll();
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.RULE, type, ruleDataList));
// 同步 metaData 数据
metaDataService.syncData();
return true;
}
1. 同步 appAuth 数据
// AppAuthServiceImpl.java
@Override
public SoulAdminResult syncData() {
// 从 app_auth 表中获取所有数据
List<AppAuthDO> appAuthDOList = appAuthMapper.selectAll();
if (CollectionUtils.isNotEmpty(appAuthDOList)) {
List<AppAuthData> dataList = appAuthDOList.stream().map(this::buildByEntity).collect(Collectors.toList());
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.APP_AUTH,
DataEventTypeEnum.REFRESH,
dataList));
}
return SoulAdminResult.success();
}
如果数据不为空,发布事件(ConfigGroupEnum.APP_AUTH + DataEventTypeEnum.REFRESH)
2. 同步 pulgin 数据
发布事件(ConfigGroupEnum.PLUGIN + DataEventTypeEnum.REFRESH)
3. 同步 selector 数据
发布事件(ConfigGroupEnum.SELECTOR + DataEventTypeEnum.REFRESH)
4. 同步 rule 数据
发布事件(ConfigGroupEnum.RULE + DataEventTypeEnum.REFRESH)
5. 同步 metaData 数据
// MetaDataServiceImpl.java
@Override
public void syncData() {
// 从 meta_data 表中获取所有数据
List<MetaDataDO> all = metaDataMapper.findAll();
if (CollectionUtils.isNotEmpty(all)) {
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.META_DATA, DataEventTypeEnum.REFRESH, MetaDataTransfer.INSTANCE.mapToDataAll(all)));
}
}
发布事件(ConfigGroupEnum.META_DATA + DataEventTypeEnum.REFRESH)
发布事件后,就又回到了昨天就分析过的事件处理分发中心 DataChangedEventDispatcher,而这里的 listeners 就包含上面 3.2 中实例化的 ZookeeperDataChangedListener 对象。
// DataChangedEventDispatcher.java
@Override
@SuppressWarnings("unchecked")
public void onApplicationEvent(final DataChangedEvent event) {
for (DataChangedListener listener : listeners) {
switch (event.getGroupKey()) {
case APP_AUTH:
listener.onAppAuthChanged((List<AppAuthData>) event.getSource(), event.getEventType());
break;
case PLUGIN:
listener.onPluginChanged((List<PluginData>) event.getSource(), event.getEventType());
break;
case RULE:
listener.onRuleChanged((List<RuleData>) event.getSource(), event.getEventType());
break;
case SELECTOR:
listener.onSelectorChanged((List<SelectorData>) event.getSource(), event.getEventType());
break;
case META_DATA:
listener.onMetaDataChanged((List<MetaData>) event.getSource(), event.getEventType());
break;
default:
throw new IllegalStateException("Unexpected value: " + event.getGroupKey());
}
}
}
在这里,虽然 websocket 配置文件注释掉了,但是 websocket 作为默认策略,即使没有配置也会注入。(@ConditionalOnProperty(name = "soul.sync.websocket.enabled", havingValue = "true", matchIfMissing = true) )
// DataSyncConfiguration.java
@Configuration
@ConditionalOnProperty(name = "soul.sync.websocket.enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(WebsocketSyncProperties.class)
static class WebsocketListener
所以在 listeners 里会有2个实例,一个是 websocket 的,一个是 zookeeper 的。
这里 websocket 没有长连接,所以即使处理,在最后执行时由于 Set<Session> SESSION_SET 为空,也不会发送什么信息。
我们着重看下 zookeeper 是怎么处理的,ZookeeperDataChangedListener 实现了 DataChangedListener 接口,所以对于5种数据变更的处理都有其方法。
/**
* Use zookeeper to push data changes.
*
* @author huangxiaofeng
* @author xiaoyu
*/
public class ZookeeperDataChangedListener implements DataChangedListener {
private final ZkClient zkClient;
public ZookeeperDataChangedListener(final ZkClient zkClient) {
this.zkClient = zkClient;
}
@Override
public void onAppAuthChanged(final List<AppAuthData> changed, final DataEventTypeEnum eventType) {
for (AppAuthData data : changed) {
final String appAuthPath = ZkPathConstants.buildAppAuthPath(data.getAppKey());
// delete
if (eventType == DataEventTypeEnum.DELETE) {
deleteZkPath(appAuthPath);
continue;
}
// create or update
upsertZkNode(appAuthPath, data);
}
}
@SneakyThrows
@Override
public void onMetaDataChanged(final List<MetaData> changed, final DataEventTypeEnum eventType) {
for (MetaData data : changed) {
final String metaDataPath = ZkPathConstants.buildMetaDataPath(URLEncoder.encode(data.getPath(), "UTF-8"));
// delete
if (eventType == DataEventTypeEnum.DELETE) {
deleteZkPath(metaDataPath);
continue;
}
// create or update
upsertZkNode(metaDataPath, data);
}
}
@Override
public void onPluginChanged(final List<PluginData> changed, final DataEventTypeEnum eventType) {
for (PluginData data : changed) {
final String pluginPath = ZkPathConstants.buildPluginPath(data.getName());
// delete
if (eventType == DataEventTypeEnum.DELETE) {
deleteZkPathRecursive(pluginPath);
final String selectorParentPath = ZkPathConstants.buildSelectorParentPath(data.getName());
deleteZkPathRecursive(selectorParentPath);
final String ruleParentPath = ZkPathConstants.buildRuleParentPath(data.getName());
deleteZkPathRecursive(ruleParentPath);
continue;
}
//create or update
upsertZkNode(pluginPath, data);
}
}
@Override
public void onSelectorChanged(final List<SelectorData> changed, final DataEventTypeEnum eventType) {
if (eventType == DataEventTypeEnum.REFRESH && !changed.isEmpty()) {
final String selectorParentPath = ZkPathConstants.buildSelectorParentPath(changed.get(0).getPluginName());
deleteZkPathRecursive(selectorParentPath);
}
for (SelectorData data : changed) {
final String selectorRealPath = ZkPathConstants.buildSelectorRealPath(data.getPluginName(), data.getId());
if (eventType == DataEventTypeEnum.DELETE) {
deleteZkPath(selectorRealPath);
continue;
}
final String selectorParentPath = ZkPathConstants.buildSelectorParentPath(data.getPluginName());
createZkNode(selectorParentPath);
//create or update
upsertZkNode(selectorRealPath, data);
}
}
@Override
public void onRuleChanged(final List<RuleData> changed, final DataEventTypeEnum eventType) {
if (eventType == DataEventTypeEnum.REFRESH && !changed.isEmpty()) {
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);
}
}
private void createZkNode(final String path) {
if (!zkClient.exists(path)) {
zkClient.createPersistent(path, true);
}
}
/**
* create or update zookeeper node.
* @param path node path
* @param data node data
*/
private void upsertZkNode(final String path, final Object data) {
if (!zkClient.exists(path)) {
zkClient.createPersistent(path, true);
}
zkClient.writeData(path, data);
}
private void deleteZkPath(final String path) {
if (zkClient.exists(path)) {
zkClient.delete(path);
}
}
private void deleteZkPathRecursive(final String path) {
if (zkClient.exists(path)) {
zkClient.deleteRecursive(path);
}
}
}
逻辑基本类似,都是先把节点路径拼接好,然后判断节点路径是否存在,如果不存在就先创建节点,然后在写数据。
到这里,就把数据库中已存在的节点及数据都写到初始化到 zookeeper 里了,如下图所示:
soul-bootstrap 端分析,见下篇。