ZK同步原理
基于 zookeeper 的同步原理很简单,主要是依赖 zookeeper
的 watch 机制,soul-web
会监听配置的节点,soul-admin
在启动的时候,会将数据全量写入 zookeeper
,后续数据发生变更时,会增量更新 zookeeper
的节点,与此同时,soul-web
会监听配置信息的节点,一旦有信息变更时,会更新本地缓存。
DEMO启动
soul-admin配置
-
在application.yml 注释掉websocket的开关,开启zk同步配置。
soul: database: dialect: mysql init_script: "META-INF/schema.sql" init_enable: true sync: # websocket: # enabled: true zookeeper: url: localhost:2181 sessionTimeout: 5000 connectionTimeout: 2000
soul-bootstrap配置
- 在application-local.yml,注释掉websocket的配置,开启zk同步方式。
soul :
file:
enabled: true
corss:
enabled: true
dubbo :
parameter: multi
sync:
# websocket :
# urls: ws://localhost:9095/websocket
zookeeper:
url: localhost:2181
sessionTimeout: 5000
connectionTimeout: 2000
- pom.yml 引入依赖(默认引入)
<!--soul data sync start use zookeeper-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-sync-data-zookeeper</artifactId>
<version>${project.version}</version>
</dependency>
源码分析
-
数据发生变动
ApplicationEvent
触发DataChanged
事件 -
zk监听器接收到数据发生变化,就判断事件类型 ,如果是DELETE,还要删除对应的Selector,Rule数据,
否则创建或更新节点。public ZookeeperDataChangedListener(final ZkClient zkClient) { this.zkClient = zkClient; } 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); } }
-
zkclient
客户端判断节点是否存在,不存在创建节点再写数据。private void upsertZkNode(final String path, final Object data) { if (!zkClient.exists(path)) { zkClient.createPersistent(path, true); } zkClient.writeData(path, data); }
-
soul-bootstrap 引入
soul-spring-boot-starter-sync-data-zookeeper
,里面的自动配置类ZookeeperSyncDataConfiguration
,创建了同步数据的syncDataService
类,往spring容器注册了zkClient
。@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)); } @Bean public ZkClient zkClient(final ZookeeperConfig zookeeperConfig) { return new ZkClient(zookeeperConfig.getUrl(), zookeeperConfig.getSessionTimeout(), zookeeperConfig.getConnectionTimeout()); }
-
ZookeeperSyncDataService
类实例化一个新的Zookeeper缓存管理器。监听数据(插件数据、元数据、权限数据、选择器、规则)的变化。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 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); }