上一篇中介绍了使用websocket同步数据,soul也可以使用zk来同步数据
1.配置
引入 zk 相关的jar包
soul-admin中打开zk配置
soul-bootstrap中打开 zk配置
因为使用zk,需要单独启动一个zk。
引用 soul-spring-boot-starter-sync-data-zookeeper
2. 配置加载
2.1 soul-admin 端
admin端使用spring的事件注册机制在更改配置时发送事件,将修改的配置发送出去。接收事件后将获取的配置修改到zk的节点上。
下面看下源码
2.1.1 发布事件
每个service都会注入 eventPublisher
用来发布配置修改的事件 DataChangedEvent
。
2.1.2 接收事件
DataChangedEventDispatcher
用来接收 DataChangedEvent
事件,该类会加载容器内配置的 DataChangedListener
public class DataChangedEventDispatcher implements ApplicationListener<DataChangedEvent>, InitializingBean {
private ApplicationContext applicationContext;
private List<DataChangedListener> listeners;
public DataChangedEventDispatcher(final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
// 接收 DataChangedEvent事件后使用循环DataChangedListener处理事件
@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());
}
}
}
// 获取容器中的 DataChangedListener
@Override
public void afterPropertiesSet() {
Collection<DataChangedListener> listenerBeans = applicationContext.getBeansOfType(DataChangedListener.class).values();
this.listeners = Collections.unmodifiableList(new ArrayList<>(listenerBeans));
}
}
我们可以看到会根据不同的groupKey执行不同的方法,我们以 RULE 为例,即 ZookeeperDataChangedListener.onRuleChanged()
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);
}
}
由于 soul-boostrap 端对zk节点进行监听,所以admin端对数据节点更新后soul-bootstrap会监听到改节点
2.1 soul-boostrap 配置加载及zk的监听
项目启动后会加载 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();
}
在构造方法中设置了 DataSubscriber 用于操作元数据
并且在 watcherData
watchAppAuth
watchMetaData
中通过监听zk节点来修改数据。 watcherData
方法为例:
private void watcherData() {
// plugin 数据路径
final String pluginParent = ZkPathConstants.PLUGIN_PARENT;
// 子节点路径
List<String> pluginZKs = zkClientGetChildren(pluginParent);
for (String pluginName : pluginZKs) {
// 监控子节点
watcherAll(pluginName);
}
// 监控plugin根节点
zkClient.subscribeChildChanges(pluginParent, (parentPath, currentChildren) -> {
if (CollectionUtils.isNotEmpty(currentChildren)) {
for (String pluginName : currentChildren) {
watcherAll(pluginName);
}
}
});
}
private void watcherAll(final String pluginName) {
// 监控 plugin
watcherPlugin(pluginName);
// 监控 selector
watcherSelector(pluginName);
// 监控 rule
watcherRule(pluginName);
}
watcherRule
方法
private void watcherRule(final String pluginName) {
// rule parent节点路径
String ruleParent = ZkPathConstants.buildRuleParentPath(pluginName);
// 子节点路径
List<String> childrenList = zkClientGetChildren(ruleParent);
// 处理子节点数据
if (CollectionUtils.isNotEmpty(childrenList)) {
childrenList.forEach(children -> {
String realPath = buildRealPath(ruleParent, children);
// 使用不同的 DataSubscriber 缓存zk数据到本地
cacheRuleData(zkClient.readData(realPath));
// 订阅rule数据节点的变化
subscribeRuleDataChanges(realPath);
});
}
// 处理子节点下的节点数据变化
subscribeChildChanges(ConfigGroupEnum.RULE, ruleParent, childrenList);
}
subscribeRuleDataChanges
方法 处理当前节点
private void subscribeRuleDataChanges(final String path) {
zkClient.subscribeDataChanges(path, new IZkDataListener() {
// 订阅更新节点事件
@Override
public void handleDataChange(final String dataPath, final Object data) {
// 缓存本地
cacheRuleData((RuleData) data);
}
// 订阅节点删除事件
@Override
public void handleDataDeleted(final String dataPath) {
// 删除本地缓存
unCacheRuleData(dataPath);
}
});
}
subscribeChildChanges
处理子节点
private void subscribeChildChanges(final ConfigGroupEnum groupKey, final String groupParentPath, final List<String> childrenList) {
switch (groupKey) {
case SELECTOR:
// 订阅 节点更新事件
zkClient.subscribeChildChanges(groupParentPath, (parentPath, currentChildren) -> {
if (CollectionUtils.isNotEmpty(currentChildren)) {
// 这里判断当前更新的节点是不是在传进来的节点集合里,及更新的节点是否为想要监控的节点
List<String> addSubscribePath = addSubscribePath(childrenList, currentChildren);
// 本地缓存节点数据
addSubscribePath.stream().map(addPath -> {
String realPath = buildRealPath(parentPath, addPath);
cacheSelectorData(zkClient.readData(realPath));
return realPath;
}).forEach(this::subscribeSelectorDataChanges); // 继续监听节点(zk监听一次后会取消监听,所以需要再次设置监听)
}
});
break;
// 其他省略
以上就是使用zookeeper进行同步数据的流程,如果那里有错误请指正。