引言

在《配置同步》一篇我们提到,Soul 支持 zookeeper 同步策略,今天我们就来探究 Soul 是如何实现该同步策略的。

启用策略

soul-admin 端:

1)开启 zookeeper 同步配置

  1. soul:
  2. sync:
  3. zookeeper:
  4. url: localhost:2181
  5. sessionTimeout: 5000
  6. connectionTimeout: 2000

soul-bootstrap 端:

1)引入 zookeeper 同步依赖:

 <dependency>
      <groupId>org.dromara</groupId>
       <artifactId>soul-spring-boot-starter-sync-data-zookeeper</artifactId>
       <version>2.2.0</version>
 </dependency>

2)添加 zookeeper 同步配置:

zookeeper:
     url: localhost:2181
     sessionTimeout: 5000
     connectionTimeout: 2000

同步原理

zookeeper 策略的同步原理:

soul-admin 端:启动时向 zookeeper 写入全量数据,后续数据变更时增量更新。

soul-web 端:依赖 zookeeper 的 watch 机制,监听配置信息的节点,节点变更时更新本地缓存。

源码实现

soul-admin 更新配置节点

ZookeeperDataChangedListener 类实现了 DataChangedListener 接口,监听数据的变化,并通过 zkClient 将数据的变化增量更新到 zookeeper的节点。

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());
        }
    }
}

Zookeeper 监听器的初始化:

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

    @Bean
    @ConditionalOnMissingBean(ZookeeperDataChangedListener.class)
    public DataChangedListener zookeeperDataChangedListener(final ZkClient zkClient) {
        return new ZookeeperDataChangedListener(zkClient);
    }

    @Bean
    @ConditionalOnMissingBean(ZookeeperDataInit.class)
    public ZookeeperDataInit zookeeperDataInit(final ZkClient zkClient, final SyncDataService syncDataService) {
        return new ZookeeperDataInit(zkClient, syncDataService);
    }
}

soul-web watch配置节点变更

1、通过 soul-bootstrap 引入 soul-spring-boot-starter-sync-data-zookeeper 依赖

2、spring.factories 指定自动配置类 ZookeeperSyncDataConfiguration:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.dromara.soul.spring.boot.sync.data.zookeeper.ZookeeperSyncDataConfiguration

3、ZookeeperSyncDataConfiguration 自动装载bean时,创建 ZkClient 和 ZookeeperSyncDataService

4、ZookeeperSyncDataService 监听 zookeeper 中选择器、规则、元数据、用户权限等数据的变更。

如何监听:通过 ZkClient 订阅 zookeeper 数据的变化,实现监听并处理变化的数据。

public void subscribeDataChanges(String path, IZkDataListener listener) {
    synchronized(this._dataListener) {
        Set<IZkDataListener> listeners = (Set)this._dataListener.get(path);
        if (listeners == null) {
            listeners = new CopyOnWriteArraySet();
            this._dataListener.put(path, listeners);
        }
        ((Set)listeners).add(listener);
    }

    this.watchForData(path);
    LOG.debug("Subscribed data changes for " + path);
}