关于zookeeper
可能有些同学跟我一样,没有用过zookeeper,不过这并没有多大影响来阅读本篇文章。关于zookeeper我们只需要知道它可以存储数据,并且可以广播数据就可以了。如果对zookeeper不是很了解并且感兴趣的话,推荐菜鸟教程的zookeeper教程适合初学者快速了解zookeeper。
Soul网关使用Zookeeper实现数据同步
前面一篇介绍WebSocket数据同步的时候已经大致说过了Soul的数据同步原理,今天就直接来演示了。
-
启动
soul-admin
-
启动
soul-bootstrap
这里需要注意的是,Soul默认使用WebSocket进行数据同步,如果要使用zookeeper,需要先配置:- 首先在 pom.xml 文件中 引入以下依赖:
<!--soul data sync start use zookeeper--> <dependency> <groupId>org.dromara</groupId> <artifactId>soul-spring-boot-starter-sync-data-zookeeper</artifactId> <version>${last.version}</version> </dependency>
- 在 springboot的 yml 文件中进行如下配置:
soul : sync: zookeeper: url: localhost:2181 sessionTimeout: 5000 connectionTimeout: 2000 #url: 配置成你的zk地址,集群环境请使用(,)分隔
- soul-admin 配置, 或在 soul-admin 启动参数中设置 --soul.sync.zookeeper.url=‘你的地址’,然后重启服务。
soul: sync: zookeeper: url: localhost:2181 sessionTimeout: 5000 connectionTimeout: 2000
- 首先在 pom.xml 文件中 引入以下依赖:
-
启动完成后,我们可以在zookeeper的client端看到如下的数据节点,多了五个节点,通过名字就知道这里面是存储我们的插件/选择器/规则/以及元数据的。并且客户端出现如下log信息,开始监视我们/soul下面的数据。
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/soul
-
现在我们看一下
soul-admin
数据同步是调用的哪个方法- 在浏览器的控制台可以看到我们点数据同步按钮时,调用的下面这个方法:
/** * Sync plugin data. * * @param id the id * @return the mono */ @PutMapping("/syncPluginData/{id}") public SoulAdminResult syncPluginData(@PathVariable("id") final String id) { boolean success = syncDataService.syncPluginData(id); if (success) { return SoulAdminResult.success(SoulResultMessage.SYNC_SUCCESS); } else { return SoulAdminResult.error(SoulResultMessage.SYNC_FAIL); } }
- 跟着断点一步步走,会看到进入到下面这个方法,这里做的处理就是根据插件Id找到插件,然后把插件信息发送出去,如果这个插件下面有选择器的话,也会把选择器的信息发送出去。(这里的
eventPublisher
是org.springframework.context.ApplicationEventPublisher
,这个是Spring的一个事件发布器,它的相关介绍可以参照这篇文章,我这里就不详细介绍了。) - 还可以发现这里发送出去的是一个
DataChangedEvent
,这里面就是我们前一篇介绍的数据同步的时候需要的几个参数,(插件类型/操作类型/以及json数据)@Override public boolean syncPluginData(final String pluginId) { PluginVO pluginVO = pluginService.findById(pluginId); eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.PLUGIN, DataEventTypeEnum.UPDATE, Collections.singletonList(PluginTransfer.INSTANCE.mapDataTOVO(pluginVO)))); List<SelectorData> selectorDataList = selectorService.findByPluginId(pluginId); if (CollectionUtils.isNotEmpty(selectorDataList)) { eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, DataEventTypeEnum.REFRESH, selectorDataList)); List<RuleData> allRuleDataList = new ArrayList<>(); for (SelectorData selectData : selectorDataList) { List<RuleData> ruleDataList = ruleService.findBySelectorId(selectData.getId()); allRuleDataList.addAll(ruleDataList); } eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.RULE, DataEventTypeEnum.REFRESH, allRuleDataList)); } return true; }
- 在浏览器的控制台可以看到我们点数据同步按钮时,调用的下面这个方法:
-
通过事件发送器发送给zookeeper后,我们admin的操作就完成了,下面再看一下我们网关是怎么接收数据的。
-
找到数据同步模块下的关于zookeeper的部分
soul-sync-data-zookeeper
,看到里面只有一个ZookeeperSyncDataService
,(不由心里一阵欢喜,好简单。。) -
可以看到这个类里面好多watcher***的方法,猜测就是当zookeeper数据发生变化是,会触发这里,试验一下。
- 先给
watcherPlugin
打个断点,然后在画面上点数据同步按钮,它真的就进来了。而通过调用栈看出watcherData
方法就是入口。
- 先给
-
我们从
watcherData
方法开始,结合watcherPlugin
看一下,可以看出我们这里就是从zk中拿到数据,更新到我们的缓存中。(更新缓存这块在昨天的分享中已经介绍过了,这里是通用的方法,就不再赘述了。)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 watcherPlugin(final String pluginName) { String pluginPath = ZkPathConstants.buildPluginPath(pluginName); if (!zkClient.exists(pluginPath)) { zkClient.createPersistent(pluginPath, true); } cachePluginData(zkClient.readData(pluginPath)); subscribePluginDataChanges(pluginPath, pluginName); }
小结
今天跟着debug走了一下zookeeper数据同步的一个流程,大体一个流程是走完了,只是涉及zookeeper的一些机制我还不是太懂,最好是先了解一下zookeeper的使用方法,再看整个流程会更清晰。