关于后端:这些不知道别说你熟悉-Nacos深度源码解析

4次阅读

共计 5606 个字符,预计需要花费 15 分钟才能阅读完成。

大家好,这篇文章跟大家聊下 SpringCloudAlibaba 中的微服务组件 Nacos。Nacos 既能做注册核心,又能做配置核心,这篇文章次要来聊下做配置核心时 client 端的一些设计,次要从源码层面进行剖析,置信看完这篇文章你对 Nacos client 端的工作原理应该有比拟粗浅的理解。
SpringCloud 利用启动拉去配置
咱们之前写过一篇文章,介绍了一些 Spring 提供的扩大机制。其中说到了 ApplicationContextInitializer,该扩大是在上下文筹备阶段(prepareContext),容器刷新之前做一些初始化工作,比方咱们罕用的配置核心 client 根本都是继承该初始化器,在容器刷新前将配置从近程拉到本地,而后封装成 PropertySource 放到 Environment 中供应用。
在 SpringCloud 场景下,SpringCloud 标准中提供了 PropertySourceBootstrapConfiguration 继承 ApplicationContextInitializer,另外还提供了个 PropertySourceLocator,二者配合实现配置核心的接入。

从上述截图能够看出,在 PropertySourceBootstrapConfiguration 这个单例对象初始化的时候会将 Spring 容器中所有的 PropertySourceLocator 实现注入进来。而后在 initialize 办法中循环所有的 PropertySourceLocator 进行配置的获取,从这儿能够看出 SpringCloud 利用是反对咱们引入多个配置核心实现的,获取到配置后调用 insertPropertySources 办法将所有的 PropertySource(封装的一个个配置文件)增加到 Spring 的环境变量 environment 中。

上图展现了在 spring-cloud-starter-alibaba-nacos-config 包提供的主动拆卸类中进行了 NacosPropertySourceLocator 的定义,该类继承自上述说的 PropertySourceLocator,重写了 locate 办法进行配置的读取。
咱们来剖析下 NacosPropertySourceLocator,locate 办法只提取了次要流程代码,能够看到 Nacos 启动会加载以下三种配置文件,也就是咱们在 bootstrap.yml 文件里配置的扩大配置 extension-configs、共享配置 shared-configs 以及利用本人的配置,加载到配置文件后会封装成 NacosPropertySource 返回。

public PropertySource<?> locate(Environment env) {
    // 生成 NacosConfigService 实例,后续配置操作都是围绕该类进行
    ConfigService configService = nacosConfigManager.getConfigService();
    if (null == configService) {log.warn("no instance of config service found, can't load config from nacos");
        return null;
    }
    long timeout = nacosConfigProperties.getTimeout();
    // 配置获取(应用 configService)、配置封装、配置缓存等操作
    nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
            timeout);
    CompositePropertySource composite = new CompositePropertySource(NACOS_PROPERTY_SOURCE_NAME);
    loadSharedConfiguration(composite);
    loadExtConfiguration(composite);
    loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
    return composite;
}

复制代码
loadApplicationConfiguration 加载利用配置时,同时会加载以下三种配置,别离是

不带扩展名后缀,application

带扩展名后缀,application.yml

带环境,带扩展名后缀,application-prod.yml

并且从上到下,优先级顺次增高

加载的外围办法是 loadNacosDataIfPresent -> loadNacosPropertySource

build 办法调用 loadNacosData 获取配置,而后封装成 NacosPropertySource,并且将该对象缓存到 NacosPropertySourceRepository 中,后续会用到。

loadNacosData 办法中会将理论配置加载申请委托给 configService 去做,而后解析返回的字符串,解析器实现了 PropertySourceLoader 接口,反对 yml、properties、xml、json 这几种。

getConfig 办法会调用到 getConfigInner 办法,通过 namespace, dataId, group 惟一定位一个配置文件

首先获取本地缓存文件的配置内容,如果有间接返回

如果步骤 1 从本地没找到相应配置文件,开始从远处拉去,Nacos 2.0 以上版本应用 Grpc 协定进行近程通信,1.0 及以下应用 Http 协定进行近程通信,咱们这边以 1.x 为例来解读

getServerConfig 办法会结构最终的 http 申请参数进行调用,如果返回 ok,则将返回内容写入到本地缓存文件中,并进行返回。

至此,在我的项目启动的时候(上下文筹备阶段)咱们就拉到了近程 Nacos 中的配置,并且封装成 NacosPropertySource 放到了 Spring 的环境变量里。
监听器注册
下面章节咱们说了服务启动的时候从近程 Nacos 服务端拉到配置,这个章节咱们来说下配置变动怎么实时告诉到客户端,首先须要注册监听器。
次要看 NacosContextRefresher 类,该类会监听服务启动公布的 ApplicationReadyEvent 事件,而后进行配置监听器的注册。

registerNacosListenersForApplications 办法里会进行判断,如果主动刷新机制是开启的,则进行监听器注册。上个章节咱们说到了会将拉到的配置缓存到 NacosPropertySourceRepository 中,这儿就从缓存中获取所有的配置,而后循环进行监听器注册(如果配置文件中配置 refresh 字段为 false,则不注册监听器)。

咱们能够看到,监听器是以 dataId + groupId + namespace 为维度进行注册的,监听器的次要操作就三步。

REFRESH_COUNT ++,在上述说的 loadNacosPropertySource 办法有用到

往 NacosRefreshHistory#records 中增加一条刷新记录

公布一个 RefreshEvent 事件,该事件是 SpringCloud 提供的,次要就是用来做环境变更刷新用的

注册操作通过 ConfigService,在 ClientWorker 中解决,这块会创立一个 CacheData 对象,该对象次要就是用来治理监听器的,也是十分重要的一个类。

CacheData 中字段如下图,ManagerListenerWrap 对 Listener 做层包装,外部会保留 listener、上次变更的 content 以及 md5(用来判断配置有没有变更用)。

并且在 addCacheDataIfAbsent 办法中会将方才创立的 CacheData 缓存到 ClientWorker 中的一个 Map 中,后续会用到。

至此,在服务启动后向每一个须要反对热更新的配置都注册了一个监听器,用来监听近程配置的变动,以及做相应的解决
配置热更新
下面章节咱们讲了服务启动的时候从近程 Nacos 服务端拉到配置,以及服务启动后对须要反对热更新的配置都注册了一个监听器,这个章节咱们来说下配置变动后具体是怎么解决的。
回到上述说过的 NacosPropertySourceLocator 的 locate 办法看看,该办法首先会获取一个 ConfigService。

NacosConfigManager 中会进行一个 ConfigService 单例对象的创立,创立流程最终会委托给 ConfigFactory,应用反射形式创立一个 NacosConfigService 的实例对象,NacosConfigService 是一个很外围的类,配置的获取,监听器的注册都须要经此。

咱们看下 NacosConfigService 的构造函数,会去创立一个 ClientWorker 类的对象,这个类是实现配置热更新的外围类。

ClientWorker 的构造函数里会去创立两个线程池,executor 会每隔 10ms 进行一次配置变更的查看,executorService 次要是用来解决长轮询申请的。

checkConfigInfo 办法中会创立一个长轮询工作丢到 executorService 线程池中去解决。

LongPollingRunnable 的 run 办法代码有点多,次要流程如下:

获取上个章节中说到的缓存 cacheMap,而后遍历,判断如果该配置应用的是本地缓存模式,则调用 checkListenerMd5 去查看读到的本地缓存文件中内容的 Md5 跟上次更新的 Md5 是不是一样,不一样则调用 safeNotifyListener 去告诉监听器解决,并且更新 listenerWrap 中的 content、Md5

checkUpdateDataIds 该办法中,会将所有的 dataId 按定义格局拼接出一个字符串,结构一个长轮询申请,发给服务端,Long-Pulling-Timeout 超时工夫默认 30s,如果服务端没有配置变更,则会放弃该申请直到超时,有配置变更则间接返回有变更的 dataId 列表。

拿到第二步有变更的 dataId 后会调用 getServerConfig 获取最新的配置内容,而后遍历调用 checkListenerMd5 去查看最新拉取的配置内容的 Md5 跟上次更新的 Md5 是不是一样,不一样则调用 safeNotifyListener 去告诉监听器解决,并且更新 listenerWrap 中的 content、Md5

checkListenerMd5 办法如下,次要就是判断两个 md5 是不是雷同,不同则调用 safeNotifyListener 解决。

safeNotifyListener 办法次要就是调用监听器的 receiveConfigInfo 办法,而后更新监听器包装器中的 lastContent、lastCallMd5 字段。

监听器要执行的办法咱们下面也曾经讲过了,这边再贴下截图,次要就是公布 RefreshEvent 事件。

至此,Nacos 的解决流程曾经完结了,RefreshEvent 事件次要由 SpringCloud 相干类来解决。
RefreshEvent 事件处理
RefreshEvent 事件会由 RefreshEventListener 来解决,该 listener 含有一个 ContextRefresher 的对象。

如下图所示,refreshEnvironment 会去刷新 Spring 环境变量,实际上是交给 updateEnvironment 办法去做的刷新,具体刷新思维就是从新创立一个 Spring 容器,而后将这个新容器中的环境信息设置到原有的 Spring 环境中。拿到所有变动的配置项后,公布一个环境变动的 EnvironmentChangeEvent 事件。

ConfigurationPropertiesRebinder 会监听 EnvironmentChangeEvent 事件,监听到事件后会对所有的标注有 ConfigurationProperties 注解的配置类进行销毁后从新初始化的操作,完之后咱们的配置类中的属性就是最新的了。

这里咱们说到了会对标有 ConfigurationProperties 注解的配置类进行 rebind,那对于一般组件类里标有 @Value 注解的属性要怎么失效呢?这个其实须要配合 @RefreshScope 注解来失效的。
咱们持续回到上述的 refresh() 办法,接着会有一步 refreshAll 的操作,会调用父类的 destroy 办法。

父类就是 GenericScope,咱们晓得 Spring 中的 Bean 是有 Scope 的概念的,Spring 默认 Scope 有单例和原型两种,同时提供了 Scope 扩大接口,通过实现该接口咱们能够定义本人的 Scope。

通过 doGetBean 办法能够看出,这些自定义 Scope 类型对象的治理会交给相应的 Scope 实现去治理。

SpringCloud 实现的 RefreshScope 就是用来在运行时动静刷新 Bean 用的,RefreshScope 继承 GenericScope,提供 get 和 destroy 办法。

GenericScope 外部有一个 cache,用来保留所有该 Scope 类型的对象。

回到主线,所以在 refreshAll 中调用 super.destroy 办法时会将该 scope 的这些 Bean 都销毁掉,在下次 get 的时候在从新创立 Bean,新创建的 Bean 就有了咱们最新的配置。

至此,咱们就实现了配置热更新的成果了。
总结
文章从服务启动时的配置拉取,服务启动后的配置监听器注册,以及配置变动后的热更新实现三个方面从源码层面解析了整个的原理,心愿对大家有所帮忙。

正文完
 0