【Soul源码阅读】11.soul-admin 与 soul-bootstrap 同步机制之 zookeeper 解析(下)

目录

3.3 启动 soul-bootstrap


接上篇 【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 网关,插件能起作用跟这些缓存中的处理器一定脱不开关系,等后面分析到了再来关联上。

今天就先到这里吧。

猜你喜欢

转载自blog.csdn.net/hellboy0621/article/details/113122120