意识形态 加强网站建设,个人简历生成器,个人网站下载,响应式网站的优缺点Nacos配置拉取及配置刷新原理
一、初始化时获取配置文件
背景 SpringCloud项目中SpringBoot在启动阶段除了会创建SpringBoot容器#xff0c;还会通过bootstrap.yml构建一个SpringCloud容器#xff0c;之后会在准备上下文阶段通过SPI加载实现类后#xff0c;会进行配置合并…Nacos配置拉取及配置刷新原理
一、初始化时获取配置文件
背景 SpringCloud项目中SpringBoot在启动阶段除了会创建SpringBoot容器还会通过bootstrap.yml构建一个SpringCloud容器之后会在准备上下文阶段通过SPI加载实现类后会进行配置合并。 NacosPropertySourceLocator类
1、该类为拉取nacos配置文件的核心类在结果SPI加载时NacosPropertySourceLocator作为PropertySourceLocator的实现类也将被加载到PropertySourceBootstrapConfiguration中参加遍历。 2、最后来到NacosPropertySourceLocator类的locate方法在该方法中会通过nacosConfigManager来获取ConfigService接口实现类 3、该方法结果调用最后会通过反射获取NacosConfigService对象的有参构造器创建并返回 4、回到上面的locate方法最后开始加载配置文件此处会加载三种类型配置文件分别为共享配置文件、拓展配置文件、应用自身的配置文件。此时由于顺序的原因导致覆盖我们可以得出优先级为从下至上而其中每一种配置文件的加载都会有三种加载方式分别是从本地获取再从nacos获取为从nacos获取时网络异常等原因导致获取失败最后一种获取方式为从快照获取快照的来源为每一种成功从nacos获取后保存。 5、最后都将使用上文的NacosConfigService对象getConfigInner方法实现三种方式的获取
private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {group blank2defaultGroup(group);ParamUtils.checkKeyParam(dataId, group);ConfigResponse cr new ConfigResponse();cr.setDataId(dataId);cr.setTenant(tenant);cr.setGroup(group);// 优先使用本地配置String content LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);if (content ! null) {LOGGER.warn([{}] [get-config] get failover ok, dataId{}, group{}, tenant{}, config{}, agent.getName(),dataId, group, tenant, ContentUtils.truncateContent(content));cr.setContent(content);String encryptedDataKey LocalEncryptedDataKeyProcessor.getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);cr.setEncryptedDataKey(encryptedDataKey);configFilterChainManager.doFilter(null, cr);content cr.getContent();return content;}try {ConfigResponse response worker.getServerConfig(dataId, group, tenant, timeoutMs);cr.setContent(response.getContent());cr.setEncryptedDataKey(response.getEncryptedDataKey());configFilterChainManager.doFilter(null, cr);content cr.getContent();return content;} catch (NacosException ioe) {if (NacosException.NO_RIGHT ioe.getErrCode()) {throw ioe;}LOGGER.warn([{}] [get-config] get from server error, dataId{}, group{}, tenant{}, msg{},agent.getName(), dataId, group, tenant, ioe.toString());}LOGGER.warn([{}] [get-config] get snapshot ok, dataId{}, group{}, tenant{}, config{}, agent.getName(),dataId, group, tenant, ContentUtils.truncateContent(content));content LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);cr.setContent(content);String encryptedDataKey LocalEncryptedDataKeyProcessor.getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);cr.setEncryptedDataKey(encryptedDataKey);configFilterChainManager.doFilter(null, cr);content cr.getContent();return content;
}6、最后将配置文件中的信息都保存到composite中返回。
以上为项目启动时获取的配置文件流程
配置刷新原理
背景 当远程 Nacos Config Server 中的配置信息发生了变更我们springboot应用作为Nacos Config Client 是如何感知到的呢 此处我们需要知道一点前置知识那就是两个数据消费模型push模型和pull模型。 Push 模型当 Server 端的数据发生了变更其会主动将更新推送给 Client。Push 模型 适合于 Client 数量不多且 Server 端数据变化比较频繁的场景。其实时性较好但其需要维护长连接占用系统资源。Pull 模型需要 Client 定时查看 Server 端数据是否更新。其实时性不好且可能会产生数据更新的丢失。 那nacos采用的是哪种模型呢
其实都不是Nacos采用了长轮询模型实现的变更通知。 那什么是长轮询呢 长轮询模型整合了 Push 与 Pull 模型的优势。Client 仍定时发起 Pull 请求查看 Server 端数据是否更新。若发生了更新则 Server 立即将更新数据以响应的形式发送给 Client 端 若没有发生更新Server 端不会发送任何信息但其会临时性的保持住这个连接一段时间。 若在此时间段内Server 端数据发生了变更这个变更就会触发 Server 向 Client 发送变更结 果。这次发送的执行就是因为长连接的存在。若此期间仍未发生变更则放弃这个连接。 等待着下一次 Client 的 Pull 请求。 长轮询模型是 Push 与 Pull 模型的整合既减少了 Push 模型中长连接的被长时间维护 的时间又降低了 Pull 模型实时性较差的问题。Nacos断开连接默认为3秒 2.1、在上文第2、3步我们提到NacosPropertySourceLocator会通过反射获取到NacosConfigService类的全程构造方法并且构造出NacosConfigService对象。此时我们来看看NacosConfigService类的构造方法。注意该方法中我画绿圈的worker属性构造。该属性即为配置刷新的核心。 2.2、来到ClientWorker构造方法我们可以看到其中有两个周期线程池其中executor线程池就是为了控制发出配置检测的间隔为10秒一次断开连接默认为3秒 2.3、接下来我们看到发出配置检测请求的checkConfigInfo()方法 ParamUtil.getPerTaskConfigSize()的值为3000currentLongingTaskCount默认为0所以正常情况下都只是循环一次 2.4、接下来我们点到LongPollingRunnable看着像长轮询的类该类实现了Runnable接口查看其run方法。
以下是使用本地配置时检测是否更新 2.5、然后开始检测并获得远程的变化了的key 2.6、然后获取到对应的value设置新值且计算md5md5即用于比较是否发现变化注意其中的setContent方法
public void setContent(String content) {this.content content;this.md5 getMd5String(this.content);
}2.7、随后开始检测并开启下一次任务 2.8、接下来我们打开检测是否变化的方法checkListenerMd5() 2.9、发现发生变化后将会调用safeNotifyListener方法safeNotifyListener方法则会触发刷新的方法。
补充
3.1、在介绍该方法之前我们补充以下之前遗漏的初始化监听器的步骤在springboot准备完环境后将会发出ApplicationReadyEvent事件。而在NacosConfigAutoConfiguration配置类中注入了一个NacosContextRefresher对象 3.2、我们打开NacosContextRefresher对象看到该类实现了ApplicationListener接口并且监听事件刚好为上步骤中提到的ApplicationReadyEvent事件。 3.3、接下来我们看看他的监听方法中做了什么看到使用了CAS来控制registerNacosListenersForApplications方法的调用来注册Nacos监听器 3.4、接下来接着查看registerNacosListenersForApplications方法如何绑定的监听器我们看到是调用registerNacosListener()方法给每一个dataId绑定监听器。 3.5、接下来我们点到registerNacosListener方法中看到给使用dataId与groupId生成的key绑定一个AbstractSharedListener抽象实现类的监听器需要注意的时候该实现类中的实现的innerReceive()方法会发出一个RefreshEvent事件该事件会触发带有RefreshScope注解的类刷新。 3.6、至此我们看到了绑定监听器的过程接下来我们回到之前的地方。 2.10、此时回到CacheData类的checkListenerMd5()方法中该方法调用了safeNotifyListener()方法 2.11、打开safeNotifyListener()方法看到Listener被强转为AbstractSharedListener修改了其中的dataId与group 2.12、随后调用了AbstractSharedListener类的receiveConfigInfo()方法该方法中我们在上文提到过该方法中会发布出一个RefreshEvent事件该事件会触发带有RefreshScope注解的类刷新。 RefreshScope概述 RefreshScope注解标注了Scope注解井默认了ScopedProxyMode.TARGET_CLASS 属性此属性的功能就是创建一个代理在每次调用的时候都用它来调用GenericScope#get方法来获取bean对象在GenericScope里面包装了一个内部类BeanLifecycleWrapperCache来对加了 RefreshScope的bean进行缓存使其在不刷新时获取的都是同一个对象如属性发生变更会调用ContextRefresher#frefresh()一RefreshScope#refreshAll()进行缓存 清理方法调用并发送刷新事件通知一调用GenericScope#destroy()实现清理缓存当下一次使用此bean对象时代理对象会调用GenericScoper#get(String name, ObjectFactory?objectFactory)方法创建一个新的bean对象井存入缓存中此时新对象因为Spring的装配机制就是新的属性了