soul源码研究-zookeeper同步数据

438 阅读4分钟

今天我们研究下soul 的zookeeper同步数据到网关这块。

1、把zookeeper启动起来

安装zookeeper

> brew install zookeeper

查看配置文件 缺省的配置文件在以下目录中

> cd /usr/local/etc/zookeeper/

启动服务

> zkServer start

2、启动sole-admin

3、启动sole-bootstrap

  1. Application-local.yml 新增zookeeper 配置

    soul :
     sync:
         zookeeper:
              url: localhost:2181
              sessionTimeout: 5000
              connectionTimeout: 2000
    
  2. 运行 SoulBootstrapApplication


源码解析:

  1. Soul 配置了 websocket, 关于websocket的源码部分可以参考上篇文章(juejin.cn/post/691988…)

  2. 同上篇文章到思路一样, 我们在sole-bootstrap的配置文件application-local.yml 新增了zookeeper的配置,我们查询soul.sync.zookeeper, 共查询到四个类,ZookeeperConfig,ZookeeperProperties, ZookeeperListener, ZookeeperSyncDataConfiguration。其中ZookeeperConfig,ZookeeperProperties是同步配置文件中zookeeper的配置信息,ZookeeperListener, ZookeeperSyncDataConfiguration 是进行zookeeper的监听和数据同步,而ZookeeperListener是当ZookeeperSyncDataConfiguration不存在时执行,所以我们启动soul-bootstrap启动的是ZookeeperSyncDataConfiguration。

@Configuration
@ConditionalOnProperty(prefix = "soul.sync.zookeeper", name = "url")
@Import(ZookeeperConfiguration.class)
static class ZookeeperListener {

    /**
     * 当ZookeeperSyncDataConfiguration不存在时执行.
     *
     * @param zkClient the zk client
     * @return the data changed listener
     */
    @Bean
    @ConditionalOnMissingBean(ZookeeperDataChangedListener.class)
    public DataChangedListener zookeeperDataChangedListener(final ZkClient zkClient) {
        return new ZookeeperDataChangedListener(zkClient);
    }

    /**
     * Zookeeper data init zookeeper data init.
     *
     * @param zkClient        the zk client
     * @param syncDataService the sync data service
     * @return the zookeeper data init
     */
    @Bean
    @ConditionalOnMissingBean(ZookeeperDataInit.class)
    public ZookeeperDataInit zookeeperDataInit(final ZkClient zkClient, final SyncDataService syncDataService) {
        return new ZookeeperDataInit(zkClient, syncDataService);
    }
}
public class ZookeeperSyncDataConfiguration {

    /**
     * 当ZookeeperSyncDataConfiguration不存在时执行.
     *
     * @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());
    }

}
  1. 我们启动sole-bootstrap, 对上面的类进行debug

我们发现项目启动时,首先进行 ZookeeperSyncDataConfiguration 的SyncDataService 的初始化, 并进行data、appAuth、metaData的发布, 因为初始数据都为空,所以都会从zookeeper先获取初始数据并缓存到本地和zookeeper节点。

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 watcherAll(final String pluginName) {
    watcherPlugin(pluginName);  // 项目启动时,进行各个插件的Plugin、Selector、Rule 缓存到本地缓存 BaseDataCache的																	 PLUGIN_MAP、SELECTOR_MAP、RULE_MAP并创建对应zk节点
    watcherSelector(pluginName); // 项目启动时,将AppAuthData缓存到本地缓存SignAuthDataCache的AUTH_MAP并创建对应zk节点
    watcherRule(pluginName);  // 项目启动时,将MetaData缓存到本地缓存MetaDataCache的META_DATA_MAP并创建对应zk节点
}
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));
}

@Override
public void onSubscribe(final PluginData pluginData) {
		subscribeDataHandler(pluginData, DataEventTypeEnum.UPDATE);
}
public class CommonPluginDataSubscriber implements PluginDataSubscriber {
  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);
                  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));
              }
          }
      });
  }
  、、、
}
private void watchAppAuth() {
    final String appAuthParent = ZkPathConstants.APP_AUTH_PARENT;
    List<String> childrenList = zkClientGetChildren(appAuthParent);
    if (CollectionUtils.isNotEmpty(childrenList)) {
        childrenList.forEach(children -> {
            String realPath = buildRealPath(appAuthParent, children);
            cacheAuthData(zkClient.readData(realPath));
            subscribeAppAuthDataChanges(realPath);
        });
    }
    subscribeChildChanges(ConfigGroupEnum.APP_AUTH, appAuthParent, childrenList);
}
private void watchMetaData() {
    final String metaDataPath = ZkPathConstants.META_DATA;
    List<String> childrenList = zkClientGetChildren(metaDataPath);
    if (CollectionUtils.isNotEmpty(childrenList)) {
        childrenList.forEach(children -> {
            String realPath = buildRealPath(metaDataPath, children);
            cacheMetaData(zkClient.readData(realPath));
            subscribeMetaDataChanges(realPath);
        });
    }
    subscribeChildChanges(ConfigGroupEnum.APP_AUTH, metaDataPath, childrenList);
}
private List<String> zkClientGetChildren(final String parent) {
    if (!zkClient.exists(parent)) {
        zkClient.createPersistent(parent, true);
    }
    return zkClient.getChildren(parent);
}
  1. 我们在soul-admin的后台对 System-manage -> Plugin 中的插件进行修改, 发现 DataChangedEvent 的监听事件执行的依然是 执行 WebsocketDataChangedListener的插件变更方法,ZookeeperDataChangedListener 没生效。

我们检查soul-admin 的ZookeeperDataChangedListener初始化的代码和配置文件application.yml发现,ZookeeperDataChangedListener 的初始化需要 soul.sync.zookeeper.url有值,不初始化WebsocketListener需要soul.sync.websocket.enabled = false。于是把soul.sync.websocket.enabled 置为false, 添加对应的zookeeper 配置,重启soul-admin、soul-bootstrap。

@Configuration
@ConditionalOnProperty(prefix = "soul.sync.zookeeper", name = "url")
@Import(ZookeeperConfiguration.class)
static class ZookeeperListener {

    /**
     * Config event listener data changed listener.
     *
     * @param zkClient the zk client
     * @return the data changed listener
     */
    @Bean
    @ConditionalOnMissingBean(ZookeeperDataChangedListener.class)
    public DataChangedListener zookeeperDataChangedListener(final ZkClient zkClient) {
        return new ZookeeperDataChangedListener(zkClient);
    }

    /**
     * Zookeeper data init zookeeper data init.
     *
     * @param zkClient        the zk client
     * @param syncDataService the sync data service
     * @return the zookeeper data init
     */
    @Bean
    @ConditionalOnMissingBean(ZookeeperDataInit.class)
    public ZookeeperDataInit zookeeperDataInit(final ZkClient zkClient, final SyncDataService syncDataService) {
        return new ZookeeperDataInit(zkClient, syncDataService);
    }
}
@Configuration
@ConditionalOnProperty(name = "soul.sync.websocket.enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(WebsocketSyncProperties.class)
static class WebsocketListener {

    /**
     * Config event listener data changed listener.
     *
     * @return the data changed listener
     */
    @Bean
    @ConditionalOnMissingBean(WebsocketDataChangedListener.class)
    public DataChangedListener websocketDataChangedListener() {
        return new WebsocketDataChangedListener();
    }
    ...
}
soul:
  database:
    dialect: mysql
    init_script: "META-INF/schema.sql"
    init_enable: true
  sync:
    websocket:
      enabled: false
    zookeeper:
      url: localhost:2181
      sessionTimeout: 5000
      connectionTimeout: 2000

然后继续进行System-manage -> Plugin 中插件的修改, 发现ZookeeperDataChangedListener生效,更改后页面、本地缓存都生效。

总结

我们验证zookeeper同步数据时,需要先把默认打开的websocket关闭,打开对应的zookeeper配置即可。多分析配置以及配置应用的地方,对应类需要引用哪些配置,再加上debug,就能准确定位问题。