微服务架构 | *2.4 Nacos 获取配置与事件订阅机制的源码分析

丈夫欲遂平生志,一载寒窗一举汤。这篇文章主要讲述微服务架构 | *2.4 Nacos 获取配置与事件订阅机制的源码分析相关的知识,希望能为你提供帮助。
【微服务架构 | *2.4 Nacos 获取配置与事件订阅机制的源码分析】@[TOC](微服务架构 | *2.4 Nacos 获取配置与事件订阅机制的源码分析)
前言参考资料:
《Spring Microservices in Action》
《Spring Cloud Alibaba 微服务原理与实战》
《B站 尚硅谷 SpringCloud 框架开发教程 周阳》
为方便理解与表达,这里把 Nacos 控制台和 Nacos 注册中心称为 Nacos 服务器(就是 web 界面那个),我们编写的业务服务称为 Nacso 客户端;
由于篇幅有限,这里将源码分析分为上下两篇,其中上篇讲获取配置与事件订阅机制,下篇讲长轮询定时机制;
上篇《微服务架构 | *2.3 Spring Cloud 启动及加载配置文件源码分析(以 Nacos 为例)》中提到,读取 Nacos 服务器里的配置依靠的是 NacosPropertySourceLocator.locate() 方法,我们这次的源码之旅将从这个方法开始;
1. 客户端获取 Nacos 服务器里的配置 1.1 定位 Nacos 配置源 NacosPropertySourceLocator.locate()

  • 该方法的主要作用是:
    • 初始化 ConfigService 对象,这是 Nacos 客户端提供的用于访问实现配置中心基本操作的类;
    • 按照顺序分别加载共享配置、扩展配置、应用名称对应的配置;
  • 方法源码如下:
@Override public PropertySource< ?> locate(Environment env) //【断点步入 长轮询定时机制】获取配置服务器实例,这是 Nacos 客户端提供的用于访问实现配置中心基本操作的类 ConfigService configService = nacosConfigProperties.configServiceInstance(); if (null == configService) log.warn("no instance of config service found, cant load config from nacos"); return null; long timeout = nacosConfigProperties.getTimeout(); //Nacos 属性源生成器 nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout); String name = nacosConfigProperties.getName(); //DataId 前缀(这里是 nacos-config-client) String dataIdPrefix = nacosConfigProperties.getPrefix(); if (StringUtils.isEmpty(dataIdPrefix)) dataIdPrefix = name; //没有配置 DataId 前缀则用 spring.application.name 属性的值 if (StringUtils.isEmpty(dataIdPrefix)) dataIdPrefix = env.getProperty("spring.application.name"); //创建复合属性源 CompositePropertySource composite = new CompositePropertySource(NACOS_PROPERTY_SOURCE_NAME); //加载共享配置 loadSharedConfiguration(composite); //加载外部配置 loadExtConfiguration(composite); //【断点步入】加载 Nacos 服务器上应用程序名对应的的配置 loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env); return composite;

  • 进入 NacosPropertySourceLocator.loadApplicationConfiguration() 方法,根据 Data ID 加载配置;
private void loadApplicationConfiguration(CompositePropertySource compositePropertySource, String dataIdPrefix, NacosConfigProperties properties, Environment environment) //获取配置格式(这里是 yaml) String fileExtension = properties.getFileExtension(); //获取nacosGroup(这里是 DEFAULT_GROUP) String nacosGroup = properties.getGroup(); //如果我们配置了前缀,则按前缀获取配置文件(由于我们没配置前缀,这里是 nacos-config-client.yaml 获取不到) loadNacosDataIfPresent(compositePropertySource, dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true); for (String profile : environment.getActiveProfiles()) //这里是 nacos-config-client-dev.yaml 可以获取 String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension; //【断点步入】加载配置 loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup, fileExtension, true);

  • 进入 NacosPropertySourceLocator.loadNacosDataIfPresent() 方法,判断是否更新情况;
private void loadNacosDataIfPresent(final CompositePropertySource composite, final String dataId, final String group, String fileExtension, boolean isRefreshable) //获取更新的配置,需要注意,NacosContextRefresher 类与事件订阅机制相关,本篇第2点将重点讨论 if (NacosContextRefresher.getRefreshCount() != 0) NacosPropertySource ps; if (!isRefreshable) ps = NacosPropertySourceRepository.getNacosPropertySource(dataId); else ps = nacosPropertySourceBuilder.build(dataId, group, fileExtension, true); composite.addFirstPropertySource(ps); else //【断点步入】如果我们没有更新配置,则走下面代码 NacosPropertySource ps = nacosPropertySourceBuilder.build(dataId, group,fileExtension, isRefreshable); composite.addFirstPropertySource(ps);

  • 进入 NacosPropertySourceBuilder.build() 方法,加载并封装配置;
NacosPropertySource build(String dataId, String group, String fileExtension, boolean isRefreshable) //【断点步入】加载 Nacos Properties p = loadNacosData(dataId, group, fileExtension); //将获取到的配置封装到 NacosPropertySource NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId, propertiesToMap(p), new Date(), isRefreshable); NacosPropertySourceRepository.collectNacosPropertySources(nacosPropertySource); return nacosPropertySource;

  • 进入 NacosPropertySourceBuilder.loadNacosData() 方法,
private Properties loadNacosData(String dataId, String group, String fileExtension) //省略其他代码try //【断点步入】根据 dataId、group 等信息获取配置 data = https://www.songbingjia.com/android/configService.getConfig(dataId, group, timeout); catch (NacosException e) log.error("get data from Nacos error,dataId:, ", dataId, e); return EMPTY_PROPERTIES;

  • 一直追下去发现在 NacosConfigService.getConfigInner() 方法里成功获取到配置;
微服务架构 | *2.4 Nacos 获取配置与事件订阅机制的源码分析

文章图片

2. Nacos 配置的事件订阅机制 2.1 监听 ApplicationReadyEvent事件,注册监听器 NacosContextRefresher.onApplicationEvent()
  • 上下文准备完毕后,程序运行,EventPublishingRunListener 发布 ApplicationReadyEvent 事件,详情请见[《微服务架构 | *2.3 Spring Cloud 启动及加载配置文件源码分析(以 Nacos 为例)》]()中的《4. 程序运行事件》;
  • 上面 1.1 提到,事件订阅机制与 NacosContextRefresher(Nacos上下文更新器) 相关,这是因为里面有个NacosContextRefresher.onApplicationEvent() 方法实现了对事件 ApplicationReadyEvent(上下文准备完毕事件) 的监听,源码如下:
//监听 ApplicationReadyEvent 事件 @Override public void onApplicationEvent(ApplicationReadyEvent event) if (this.ready.compareAndSet(false, true)) //【断点步入】为应用程序注册 Nacos 监听器 this.registerNacosListenersForApplications();

2.2 注册 Nacos 监听器,监听配置变更 NacosContextRefresher.registerNacosListener()
  • 当监听到 ApplicationReadyEvent事件后,最终会调用 NacosContextRefresher.registerNacosListener() 方法来实现 Nacos 监听器的注册,源码如下:
private void registerNacosListener(final String group, final String dataId) Listener listener = listenerMap.computeIfAbsent(dataId, i -> new Listener() //接受配置变更的回调 @Override public void receiveConfigInfo(String configInfo) refreshCountIncrement(); String md5 = ""; if (!StringUtils.isEmpty(configInfo)) try MessageDigest md = MessageDigest.getInstance("MD5"); md5 = new BigInteger(1, md.digest(configInfo.getBytes("UTF-8"))).toString(16); catch (NoSuchAlgorithmException | UnsupportedEncodingException e) log.warn("[Nacos] unable to get md5 for dataId: " + dataId, e); refreshHistory.add(dataId, md5); //发布 RefreshEvent 配置变更事件 applicationContext.publishEvent(new RefreshEvent(this, null, "Refresh Nacos config")); if (log.isDebugEnabled()) log.debug("Refresh Nacos config group " + group + ",dataId" + dataId); @Override public Executor getExecutor() return null; ); try //监听配置 configService.addListener(dataId, group, listener); catch (NacosException e) e.printStackTrace();

  • 当收到配置变更的回调时,会通过 applicationContext.publishEvent() 发布一个 RefreshEvent 事件;
  • 该事件又会被RefreshEventListener(事件更新监听器) 监听,源码如下:
public void onApplicationEvent(ApplicationEvent event) if (event instanceof ApplicationReadyEvent) //监听 ApplicationReadyEvent 事件 this.handle((ApplicationReadyEvent)event); else if (event instanceof RefreshEvent) //【断点步入 2.3】监听 RefreshEvent 事件 this.handle((RefreshEvent)event);

2.3 监听配置变更,实施变更 RefreshEventListener.handle()
  • RefreshEventListener(事件更新监听器) 类使用 RefreshEventListener.handle() 方法变更配置,源码如下:
public void handle(RefreshEvent event) if (this.ready.get()) log.debug("Event received " + event.getEventDesc()); Set< String> keys = this.refresh.refresh(); log.info("Refresh keys changed: " + keys);

3. 源码结构图小结 3.1 客户端获取 Nacos 服务器上的配置源码结构图
  • NacosPropertySourceLocator.locate():初始化 ConfigService 对象,定位配置;
    • NacosPropertySourceLocator.loadApplicationConfiguration():根据 Data ID 加载配置;
    • NacosPropertySourceLocator.loadNacosDataIfPresent():判断是否更新配置;
      • NacosPropertySourceBuilder.build():加载并封装配置;
      • NacosPropertySourceBuilder.loadNacosData():加载配置;
        • NacosConfigService.getConfig():使用配置服务获取配置;
        • NacosConfigService.getConfigInner():最终在这里获取到配置;
3.2 Nacos 配置的事件订阅机制
  • 上下文准备完毕,程序运行,EventPublishingRunListener 发布 ApplicationReadyEvent 事件;
  • NacosContextRefresher.onApplicationEvent():监听 ApplicationReadyEvent 事件;
    • NacosContextRefresher.registerNacosListener():注册 Nacos 监听器,监听配置变更;
  • 变更发生时,NacosContextRefresher 发布一个 RefreshEvent 事件;
  • RefreshEventListener.onApplicationEvent():同时监听 ApplicationReadyEvent 和 RefreshEvent 事件;
    • RefreshEventListener.handle():实施变更方法;
最后::: hljs-center
新人制作,如有错误,欢迎指出,感激不尽!
:::
::: hljs-center
欢迎关注公众号,会分享一些更日常的东西!
:::
::: hljs-center
如需转载,请标注出处!
:::
::: hljs-center
微服务架构 | *2.4 Nacos 获取配置与事件订阅机制的源码分析

文章图片

:::

    推荐阅读