学习目标:

学习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. 如何在Ubuntu Server 18.04 LTS中配置静态IP地址

    安装Ubuntu Server 18.04后需要分配一个的静态IP地址.先前的LTS版本Ubuntu 16.04使用/etc/network/interfaces文件配置静态IP地址,但是Ubuntu ...

  2. C#中RDLC控制某列的显示隐藏

    使用 1.添加参数IsEnable(用于控制显示或隐藏)2.在RDLC页面,需要控制的列上,右键,列的可见性...,基于表达式隐藏或显示3.输入 IIF(Parameters!IsEnable.Val ...

  3. Android 自定义构建类型 BuildType

    简单介绍了 BuildType 定义在 app 项目与 module 项目的区别,以及在 Gradle 2.x 与 3.x 版本的区别. 最近接触到自定义构建类型 BuildType,发现这一块有些地 ...

  4. jsp中将一个jsp引入另一个jsp指定位置

    <jsp:include page="badSurveyUpdate.jsp"/>

  5. Centos7 搭建openldap完整详细教程(真实可用)

    最近,由于公司需求,需要搭建openldap来统一用户名和密码,目前市面上几乎所有的工具都支持ldap协议,具体ldap的介绍这里就不详细说明了,这里主要记录一下如果部署openldap来实现Ldap ...

  6. 仅4步,就可通过SQL进行分布式死锁的检测与消除

    摘要:本文主要介绍在 GaussDB(DWS) 中,如何通过 SQL 语句,对分布式死锁进行检测和恢复. 分布式数仓应用场景中,我们经常遇到数据库系统 hang 住的问题,所谓 hang 是指虽然数据 ...

  7. jit编译原理

    jit用以把程序全部或部分翻译成本地机器码,当需要装载某个类[通常是创建第一个对象时],编译器会先找到其.class文件,然后将该类的字节码装入内存. hotspot采用惰性评估法: 如果一段代码频繁 ...

  8. web.xml中配置启动时加载的servlet,load-on-starup

    web.xml中配置启动时加载的servlet,load-on-starup 使用servlet来初始化配置文件数据: 在servlet的配置当中,<load-on-startup>1&l ...

  9. docker获取Let's Encrypt永久免费SSL证书

    一 起因 官方的cerbot太烦了,不建议使用 还不如野蛮生长的acme.sh,而这里介绍docker运行cerbot获取Let's Encrypt永久免费SSL证书 二 选型 cerbot的证书不会 ...

  10. 被自己以为的GZIP秀到了

    问题的开始 我司某产品线有这么一个神奇接口 (https://host/path/customQuery) 该接口在预发或线上缓存正常的情况下TTFB为150ms左右(可以认为服务处理时间差不多就是T ...