目录
接上篇 【Soul源码阅读】10.soul-admin 与 soul-bootstrap 同步机制之 zookeeper 解析(上)
3.3 启动 soul-bootstrap
在上篇 2.2 小节中,提到了 ZookeeperConfig 配置类,在代码中搜索使用的地方,找到 ZookeeperSyncDataConfiguration,源码如下:
/**
* The type Zookeeper configuration.
*
* @author xiaoyu(Myth)
*/
@Data
@ConfigurationProperties(prefix = "soul.sync.zookeeper")
public class ZookeeperConfig {
private String url;
private Integer sessionTimeout;
private Integer connectionTimeout;
private String serializer;
}
/**
* Zookeeper sync data configuration for spring boot.
*
* @author xiaoyu(Myth)
*/
@Configuration
// 要完成自动配置,需要在类路径中存在 ZookeeperSyncDataService.class 这个类
@ConditionalOnClass(ZookeeperSyncDataService.class)
// 检测属性配置,当存在属性 soul.sync.zookeeper.url 时,才会启动这个类作为配置文件
@ConditionalOnProperty(prefix = "soul.sync.zookeeper", name = "url")
// @EnableConfigurationProperties 注解的作用是使 @ConfigurationProperties 注解生效。
@EnableConfigurationProperties(ZookeeperConfig.class)
@Slf4j
public class ZookeeperSyncDataConfiguration {
/**
* Sync data service sync data service.
*
* @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());
}
}
zkClient 方法把配置装配到 ZKClient 里,并作为 Bean 注册到 Spring 容器中。
syncDataService 方法会调用 ZookeeperSyncDataService 的构造方法:
// ZookeeperSyncDataService.java
/**
* Instantiates a new Zookeeper cache manager.
*
* @param zkClient the zk client
* @param pluginDataSubscriber the plugin data subscriber
* @param metaDataSubscribers the meta data subscribers
* @param authDataSubscribers the auth data subscribers
*/
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();
}
3.3.1 设置属性
这里要注入的截个图留个证据,具体干啥的,后面分析应该用得上
3.3.2 watcherData
// ZookeeperSyncDataService.java
private void watcherData() {
// "/soul/plugin"
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 List<String> zkClientGetChildren(final String parent) {
// 判断 zk 里有没有这个节点
if (!zkClient.exists(parent)) {
// 如果没有,就创建持久节点
zkClient.createPersistent(parent, true);
}
// 返回这个节点的所有子节点
return zkClient.getChildren(parent);
}
private void watcherAll(final String pluginName) {
watcherPlugin(pluginName);
watcherSelector(pluginName);
watcherRule(pluginName);
}
3.3.2.1. watcherPlugin 先以 plugin 为例跟踪:
// ZookeeperSyncDataService.java
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));
}
private void subscribePluginDataChanges(final String pluginPath, final String pluginName) {
zkClient.subscribeDataChanges(pluginPath, new IZkDataListener() {
// 订阅后,修改数据会走这里的逻辑
@Override
public void handleDataChange(final String dataPath, final Object data) {
Optional.ofNullable(data)
.ifPresent(d -> Optional.ofNullable(pluginDataSubscriber).ifPresent(e -> e.onSubscribe((PluginData) d)));
}
@Override
public void handleDataDeleted(final String dataPath) {
final PluginData data = new PluginData();
data.setName(pluginName);
Optional.ofNullable(pluginDataSubscriber).ifPresent(e -> e.unSubscribe(data));
}
});
}
最终都会到 CommonPluginDataSubscriber 的 subscribeDataHandler 方法中:
/**
* The interface Plugin data subscriber.
*/
public interface PluginDataSubscriber {
...
/**
* On subscribe.
*
* @param pluginData the plugin data
*/
default void onSubscribe(PluginData pluginData) {
}
...
}
/**
* The type Common plugin data subscriber.
*
* @author xiaoyu
*/
public class CommonPluginDataSubscriber implements PluginDataSubscriber {
...
@Override
public void onSubscribe(final PluginData pluginData) {
subscribeDataHandler(pluginData, DataEventTypeEnum.UPDATE);
}
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);
// 这里会涉及到具体插件对应的 handler,后续再具体分析 TODO
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));
}
}
});
}
...
}
PluginDataHandler 都有默认空实现,不同的方法有不同的实现类覆写(具体的逻辑后面有时间再具体分析,先记录一下):
启动初始化过程就分析到这里。
4. 改动数据同步过程
4.1 页面操作
关闭 sofa 插件:
可以看到前台向后台发送了一个 PUT 请求:http://localhost:9095/plugin/11
这个接口跟 【Soul源码阅读】9.soul-admin 与 soul-bootstrap 同步机制之 websocket 解析 中调用的是一个。
PluginController.updatePlugin() -> PluginService.createOrUpdate()
// PluginServiceImpl.java
/**
* create or update plugin.
*
* @param pluginDTO {@linkplain PluginDTO}
* @return rows
*/
@Override
@Transactional(rollbackFor = Exception.class)
public String createOrUpdate(final PluginDTO pluginDTO) {
final String msg = checkData(pluginDTO);
if (StringUtils.isNoneBlank(msg)) {
return msg;
}
PluginDO pluginDO = PluginDO.buildPluginDO(pluginDTO);
DataEventTypeEnum eventType = DataEventTypeEnum.CREATE;
if (StringUtils.isBlank(pluginDTO.getId())) {
pluginMapper.insertSelective(pluginDO);
} else {
eventType = DataEventTypeEnum.UPDATE;
pluginMapper.updateSelective(pluginDO);
}
// publish change event.
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.PLUGIN, eventType,
Collections.singletonList(PluginTransfer.INSTANCE.mapToData(pluginDO))));
return StringUtils.EMPTY;
}
4.2 发布事件
这里发布事件,找到监听事件,这里有2个监听器,websocket 上一篇已经介绍了,这里就不赘述了,关注第二个 ZookeeperDataChangedListener。
// ZookeeperDataChangedListener.java
@Override
public void onPluginChanged(final List<PluginData> changed, final DataEventTypeEnum eventType) {
for (PluginData data : changed) {
String pluginPath = ZkPathConstants.buildPluginPath(data.getName());
// delete
if (eventType == DataEventTypeEnum.DELETE) {
deleteZkPathRecursive(pluginPath);
String selectorParentPath = ZkPathConstants.buildSelectorParentPath(data.getName());
deleteZkPathRecursive(selectorParentPath);
String ruleParentPath = ZkPathConstants.buildRuleParentPath(data.getName());
deleteZkPathRecursive(ruleParentPath);
continue;
}
//create or update
// UPDATE 会插入或更新节点信息
insertZkNode(pluginPath, data);
}
}
private void insertZkNode(final String path, final Object data) {
// 不存在节点,插入持久化节点
createZkNode(path);
zkClient.writeData(path, data);
}
private void createZkNode(final String path) {
if (!zkClient.exists(path)) {
zkClient.createPersistent(path, true);
}
}
写入数据前,查询一下
使用工具查询zk节点数据
4.3 zk 数据变动后走订阅处理的逻辑
就是上面3.3.2.1里提到的,我再粘过来。
zk数据变动后会走到 handleDataChange 方法中:
// ZookeeperSyncDataService.java
private void subscribePluginDataChanges(final String pluginPath, final String pluginName) {
zkClient.subscribeDataChanges(pluginPath, new IZkDataListener() {
@Override
public void handleDataChange(final String dataPath, final Object data) {
Optional.ofNullable(data)
.ifPresent(d -> Optional.ofNullable(pluginDataSubscriber).ifPresent(e -> e.onSubscribe((PluginData) d)));
}
@Override
public void handleDataDeleted(final String dataPath) {
final PluginData data = new PluginData();
data.setName(pluginName);
Optional.ofNullable(pluginDataSubscriber).ifPresent(e -> e.unSubscribe(data));
}
});
}
// ZookeeperSyncDataService.java
@Override
public void onSubscribe(final PluginData pluginData) {
subscribeDataHandler(pluginData, DataEventTypeEnum.UPDATE);
}
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);
// 插件处理,这里是改的 sofa 插件,会进到 SofaPluginDataHandler 里
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));
}
}
});
}
// SofaPluginDataHandler.java
@Override
public void handlerPlugin(final PluginData pluginData) {
// pluginData 不为空,并且是开启状态,否则不进
if (null != pluginData && pluginData.getEnabled()) {
SofaRegisterConfig sofaRegisterConfig = GsonUtils.getInstance().fromJson(pluginData.getConfig(), SofaRegisterConfig.class);
// INST 相当于 JVM 缓存
SofaRegisterConfig exist = Singleton.INST.get(SofaRegisterConfig.class);
if (Objects.isNull(sofaRegisterConfig)) {
return;
}
// 如果缓存为空,或是缓存不为空但修改值了,进去初始化
if (Objects.isNull(exist) || !sofaRegisterConfig.equals(exist)) {
// If it is null, initialize it
ApplicationConfigCache.getInstance().init(sofaRegisterConfig);
ApplicationConfigCache.getInstance().invalidateAll();
}
// 更新缓存,感觉这里可以增加个判断,如果没变化就不用更新了
Singleton.INST.single(SofaRegisterConfig.class, sofaRegisterConfig);
}
}
// Singleton.java
private static final Map<String, Object> SINGLES = new ConcurrentHashMap<>();
public void single(final Class clazz, final Object o) {
SINGLES.put(clazz.getName(), o);
}
虽然没搞清楚这些缓存具体哪里用的,但可以猜想,后面发送请求到 soul 网关,插件能起作用跟这些缓存中的处理器一定脱不开关系,等后面分析到了再来关联上。
今天就先到这里吧。