学习目标:

学习Soul 网关 Nacos 数据同步源码解析

学习内容:

  1. 环境配置
  2. Soul 网关 Nacos 数据同步基本概念
  3. 源码分析

学习时间:2020年1月28号 早7点

学习产出:

环境配置:

  1. 引入依赖
    在 soul-bootstrap项目的 pom.xml 文件中引入了 soul-spring-boot-starter-sync-data-nacos 这个 starter 。
  <dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-sync-data-nacos</artifactId>
<version>${last.version}</version>
</dependency>
  1. 环境配置

在 soul-bootstrap 项目的 application-local.yml 文件中,配置

soul :
sync:
nacos:
url: localhost:8848
namespace: 1c10d748-af86-43b9-8265-75f487d20c6c
acm:
enabled: false
endpoint: acm.aliyun.com
namespace:
accessKey:
secretKey:
#url: 配置成你的nacos地址,集群环境请使用(,)分隔。

在 soul-admin 项目中的 application.yml 文件中配置 nacos 同步的相关配置如下:

soul:
sync:
nacos:
url: localhost:8848
namespace: 7aba82c9-426b-4d72-89c9-060d9af63e14
# 此处为前文中提到的命名空间ID或命名空间名称 soul

Nacos 的关键特性
服务发现和服务健康监测
动态配置服务
动态 DNS 服务
服务及其元数据管理
Nacos 中的几个概念
命名空间(Namespace): 不同环境的配置隔离
配置分组(Group): 不同的服务可以归类到同一分组。一般将一个项目的配置分到一组
配置集(Data ID) : 一个配置文件通常就是一个配置集

  1. nacos同步策略-源码追踪
    从 soul-bootstrap 开始追踪
    NacosSyncDataConfiguration 这个类加载了 soul.sync.nacos 这段配置。该类创建了 nacosSyncDataService 。
@Bean
public SyncDataService nacosSyncDataService(final ObjectProvider<ConfigService> configService, final ObjectProvider<PluginDataSubscriber> pluginSubscriber,
final ObjectProvider<List<MetaDataSubscriber>> metaSubscribers, final ObjectProvider<List<AuthDataSubscriber>> authSubscribers) {
log.info("you use nacos sync soul data.......");
return new NacosSyncDataService(configService.getIfAvailable(), pluginSubscriber.getIfAvailable(),
metaSubscribers.getIfAvailable(Collections::emptyList), authSubscribers.getIfAvailable(Collections::emptyList));
}
public NacosSyncDataService(final ConfigService configService, final PluginDataSubscriber pluginDataSubscriber,
final List<MetaDataSubscriber> metaDataSubscribers, final List<AuthDataSubscriber> authDataSubscribers) {
super(configService, pluginDataSubscriber, metaDataSubscribers, authDataSubscribers);
start();
}
public void start() {
watcherData(PLUGIN_DATA_ID, this::updatePluginMap);
watcherData(SELECTOR_DATA_ID, this::updateSelectorMap);
watcherData(RULE_DATA_ID, this::updateRuleMap);
watcherData(META_DATA_ID, this::updateMetaDataMap);
watcherData(AUTH_DATA_ID, this::updateAuthMap);
}

进入 watcherData 方法,此时我们会进入一个重要的类 NacosCacheHandler 。

// 即便对 lambda 不熟悉,我们也可以大致推测,这里的功能应该是通过调用updateXxxMap方法,对元数据、插件、选择器等进行监听
protected void watcherData(final String dataId, final OnChange oc) {
Listener listener = new Listener() {
@Override
public void receiveConfigInfo(final String configInfo) {
oc.change(configInfo);
} @Override
public Executor getExecutor() {
return null;
}
};
oc.change(getConfigAndSignListener(dataId, listener));
LISTENERS.computeIfAbsent(dataId, key -> new ArrayList<>()).add(listener);
} protected interface OnChange {
void change(String changeData);
}

updatePluginMap 方法:

protected void updatePluginMap(final String configInfo) {
try {
// Fix bug #656(https://github.com/dromara/soul/issues/656) 此处解决了一个 nacos data sync error 的 bug
List<PluginData> pluginDataList = new ArrayList<>(GsonUtils.getInstance().toObjectMap(configInfo, PluginData.class).values());
pluginDataList.forEach(pluginData -> Optional.ofNullable(pluginDataSubscriber).ifPresent(subscriber -> {
subscriber.unSubscribe(pluginData);
subscriber.onSubscribe(pluginData);
}));
} catch (JsonParseException e) {
log.error("sync plugin data have error:", e);
}
}

追踪一下 unSubscribe(pluginData) 和 onSubscribe(pluginData) ,进入 CommonPluginDataSubscriber 中:

@Override
public void onSubscribe(final PluginData pluginData) {
subscribeDataHandler(pluginData, DataEventTypeEnum.UPDATE);
} @Override
public void unSubscribe(final PluginData pluginData) {
subscribeDataHandler(pluginData, DataEventTypeEnum.DELETE);
} // 根据传入的数据类型ID,和数据处理类型,调用相应的处理函数,看具体是执行update更新还是delete删除
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));
}
}
});
}

admin追踪:
是 DataSyncConfiguration 这个类加载了 soul.sync.nacos 这段配置。该类创建了 NacosDataChangedListener 。

/**
* The type Nacos listener.
*/
@Configuration
@ConditionalOnProperty(prefix = "soul.sync.nacos", name = "url")
@Import(NacosConfiguration.class)
static class NacosListener { /**
* Data changed listener data changed listener.
*
* @param configService the config service
* @return the data changed listener
*/
@Bean
@ConditionalOnMissingBean(NacosDataChangedListener.class)
public DataChangedListener nacosDataChangedListener(final ConfigService configService) {
return new NacosDataChangedListener(configService);
} /**
* Nacos data init zookeeper data init.
*
* @param configService the config service
* @param syncDataService the sync data service
* @return the nacos data init
*/
@Bean
@ConditionalOnMissingBean(NacosDataInit.class)
public NacosDataInit nacosDataInit(final ConfigService configService, final SyncDataService syncDataService) {
return new NacosDataInit(configService, syncDataService);
}
}
@Override
public void run(final String... args) {
String pluginDataId = NacosPathConstants.PLUGIN_DATA_ID;
String authDataId = NacosPathConstants.AUTH_DATA_ID;
String metaDataId = NacosPathConstants.META_DATA_ID;
if (dataIdNotExist(pluginDataId) && dataIdNotExist(authDataId) && dataIdNotExist(metaDataId)) {
syncDataService.syncAll(DataEventTypeEnum.REFRESH);
}
}
// type 是 REFRESH
@Override
public boolean syncAll(final DataEventTypeEnum type) {
appAuthService.syncData();
List<PluginData> pluginDataList = pluginService.listAll();
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.PLUGIN, type, pluginDataList));
List<SelectorData> selectorDataList = selectorService.listAll();
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, type, selectorDataList));
List<RuleData> ruleDataList = ruleService.listAll();
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.RULE, type, ruleDataList));
metaDataService.syncData();
return true;
}
public static void send(final String message, final DataEventTypeEnum type) {
if (StringUtils.isNotBlank(message)) {
if (DataEventTypeEnum.MYSELF == type) {
Session session = (Session) ThreadLocalUtil.get(SESSION_KEY);
if (session != null) {
sendMessageBySession(session, message);
}
} else {
SESSION_SET.forEach(session -> sendMessageBySession(session, message));
}
}
}

Soul 网关 Nacos 数据同步源码解析的更多相关文章

  1. Eureka应用注册与集群数据同步源码解析

    在之前的EurekaClient自动装配及启动流程解析一文中我们提到过,在构造DiscoveryClient类时,会把自身注册到服务端,本文就来分析一下这个注册流程 客户端发起注册 boolean r ...

  2. nacos服务注册源码解析

    1.客户端使用 compile 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:2.2.3.RELEASE' compi ...

  3. Nacos服务发现源码解析

    1.Spring服务发现的统一规范 Spring将这套规范定义在Spring Cloud Common中 discovery包下面定义了服务发现的规范 核心接口:DiscoveryClient 用于服 ...

  4. Eureka源码解析系列文章汇总

    先看一张图 0 这个图是Eureka官方提供的架构图,整张图基本上把整个Eureka的核心功能给列出来了,当你要阅读Eureka的源码时可以参考着这个图和下方这些文章 EurekaServer Eur ...

  5. 死磕 java同步系列之StampedLock源码解析

    问题 (1)StampedLock是什么? (2)StampedLock具有什么特性? (3)StampedLock是否支持可重入? (4)StampedLock与ReentrantReadWrite ...

  6. 死磕 java同步系列之Semaphore源码解析

    问题 (1)Semaphore是什么? (2)Semaphore具有哪些特性? (3)Semaphore通常使用在什么场景中? (4)Semaphore的许可次数是否可以动态增减? (5)Semaph ...

  7. [源码解析] PyTorch 分布式(1) --- 数据加载之DistributedSampler

    [源码解析] PyTorch 分布式(1) --- 数据加载之DistributedSampler 目录 [源码解析] PyTorch 分布式(1) --- 数据加载之DistributedSampl ...

  8. [源码解析] PyTorch分布式优化器(2)----数据并行优化器

    [源码解析] PyTorch分布式优化器(2)----数据并行优化器 目录 [源码解析] PyTorch分布式优化器(2)----数据并行优化器 0x00 摘要 0x01 前文回顾 0x02 DP 之 ...

  9. HDFS源码解析:教你用HDFS客户端写数据

    摘要:终于开始了这个很感兴趣但是一直觉得困难重重的源码解析工作,也算是一个好的开端. 本文分享自华为云社区<hdfs源码解析之客户端写数据>,作者: dayu_dls. 在我们客户端写数据 ...

随机推荐

  1. TypeError: filter() got an unexpected keyword argument 'XXX'

    Flask使用SQLAlchemy查询报如下错误: TypeError: filter() got an unexpected keyword argument 'XXX' 出错原因: 查询错误,应该 ...

  2. springboot项目报错Exception getting JDBC Driver: com.mysql.cj.jdbc.Driver

    将驱动换成 <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysq ...

  3. C++ string的内部究竟是什么样的?

    在C语言中,有两种方式表示字符串: 一种是用字符数组来容纳字符串,例如char str[10] = "abc",这样的字符串是可读写的: 一种是使用字符串常量,例如char *st ...

  4. 「译」用 Blazor WebAssembly 实现微前端

    原文作者: Wael Kdouh 原文链接:https://medium.com/@waelkdouh/microfrontends-with-blazor-webassembly-b25e4ba3f ...

  5. vue项目中的字符串每隔4位一个空格

    项目中遇到现实银行卡号的需求所以需要这个方法 我们这里运用 JavaScript replace()方法 replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子 ...

  6. —用python写图片格式批量处理工具

    python爬取微博评论(无重复数据) 前言 一.整体思路 二.获取微博地址 1.获取ajax地址 2.解析页面中的微博地址 3.获取指定用户微博地址 三.获取主评论 四.获取子评论 1.解析子评论 ...

  7. 哎,这让人抠脑壳的 LFU。

    这是why哥的第 83 篇原创文章 让人抠脑壳的 LFU 前几天在某APP看到了这样的一个讨论: 看到一个有点意思的评论: LFU 是真的难,脑壳都给我抠疼了. 如果说 LRU 是 Easy 模式的话 ...

  8. MyBatis中id回填的两种方式

    在一种场景下需要刚刚插入数据的ID,如果数据少可以先看数据库,记下ID,但数据很多,假设一万个用户并发,每个用户都插入自己的ID,就很难记下来. 下面给定一个场景: 1 User user = new ...

  9. linux安装ftp步骤

    1,查看是否安装了FTP:rpm -qa |grep vsftpd 2,如果没有安装,可以使用如下命令直接安装 yum -y install vsftpd 默认安装目录:/etc/vsftpd 3,添 ...

  10. Go GRPC 入门(二)

    前言 最近较忙,其实准备一篇搞定的 中途有事,只能隔了一天再写 正文 pb.go 需要注意的是,在本个 demo 中,客户端与服务端都是 Golang,所以在客户端与服务端都公用一个 pb.go 模板 ...