客户端
入口
在引入配置中心 maven 依赖的 jar 文件中找到 spring-cloud-starter-alibaba-nacos-config-2.2.5.RELEASE.jar!/META-INF/spring.factories
,在该配置文件找到 NacosConfigBootstrapConfiguration 配置类,该类是 nacos 配置中心的入口类,类中注册了三个 bean。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-3e76e28983c266165754b5dec87e7218.png)
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-e942a9c057fa36c49e7714b609f09879.png)
NacosConfigProperties:属性配置类,对应配置文件中 spring.cloud.nacos.config 前缀的属性。
NacosConfigManager:管理 NacosConfigProperties 和 ConfigService。
NacosPropertySourceLocator:加载配置中心配置信息。
NacosConfigManager
在 NacosConfigManager 构造方法中,调用了 createConfigService 方法,该方法通过工厂类调用 ConfigService 实现类的构造方法创建 ConfigService 实例。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-e935f5897c842e215893a28139371416.png)
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-ec592287bd3eee99c806e104fd188768.png)
在 ConfigService 的实现类 NacosConfigService 的构造方法中会初始化 this.agent = new MetricsHttpAgent(new ServerHttpAgent(properties));,该 agent 是用来像服务端发送请求的代理。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-33016ee1ae69e707489fc8fcedc78ed5.png)
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-708461a970c990cde52322af37515797.png)
ServerHttpAgent 类中 NacosRestTemplate 属性是发送远程调用的工具类,会调用 HttpMethod.GET 方法调用服务端 rest 请求。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-ba175f46b6824a9b81a41b0c7e18473c.png)
在回到 NacosConfigService#NacosConfigService 的方法中 this.worker = new ClientWorker(this.agent, this.configFilterChainManager, properties); 该属性是客户端工作线程类,在类的内部有两个线程池:
1. 只有一个线程的线程池 this.executor = Executors.newScheduledThreadPool(1, new ThreadFactory()
用来执行定时任务,每隔 10ms 执行一次 checkConfigInfo(); 方法,按照每 3000 个配置项为一批次捞取待轮询的 cacheData 实例,将其包装成为一个 LongPollingTask 提交进入第二个线程池 executorService 处理。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-92771cfd92a3f6ed2ace2680e2f3657f.png)
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-b209cdd76a043073303e7232c54e0f28.png)
2.线程数等于处理器个数的线程池,用来执行 ClientWorker.LongPollingRunnable#LongPollingRunnable#run,cacheMap 中缓存着需要刷新的配置,将 cacheMap 中数量以 3000 分一个组,分别创建一个 LongPollingRunnable 用来监听配置更新,在 LongPollingRunnable#run 方法中调用checkLocalConfig(cacheData); 检查本地的配置,容错的处理;调用 checkUpdateDataIds(cacheDatas, inInitializingCacheList); 方法是向 nacos 服务端 发送一个长连接超时事件30s,返回有更新的dataids;调用 getServerConfig(dataId, group, tenant, 3000L); 方法是根据返回有变化的dataids调用服务端配置中心接口获取配置属性,并更新本地快照;调用checkListenerMd5();方式,对有变化的配置添加监听处理;最后继续调用executorService.execute(this); 方法轮询处理。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-ea3bfee364bfa002b64c2a00eb140e27.png)
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-3db25cdf0c0c6f2576c0b797fb0bc8e0.png)
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-81491157c2a9b5a035fd2ad11cdf1cd7.png)
CacheData#checkListenerMd5
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-0d3aae052f9694d6e20313ff96e9639a.png)
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-1168187c70b63090bfbe6dc2fa096748.png)
在listener.receiveConfigInfo(contentTmp); 方法中会调用到 AbstractSharedListener#receiveConfigInfo 方法,会发布 RefreshEvent 事件。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-702f5b93e817f4562446c146f1db3ce8.png)
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-e043781b2deaceeda3b0294ed73977b6.png)
对应的事件监听器为:RefreshEventListener, Spring Cloud 实现的,在该监听器里更新配置和刷新容器中标记了 @RefreshScope 的配置,在 onApplicationEvent 方法中监听2个事件,ApplicationReadyEvent(spring boot 事件,表示 application 应该初始化完成)、RefreshEvent。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-bf21bda28653d2dff4f51a4fd2b21a38.png)
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-ce3d6db63b29372e7c8d467730c25ff8.png)
RefreshEvent:this.handle((RefreshEvent)event);处理该事件,用来刷新容器中标记了 @RefreshScope注解的配置,org.springframework.cloud.context.refresh.ContextRefresher#refresh
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-080eb23fcd58e03f369249cd17514d36.png)
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-b20c46acc3957c94954a7131b1e8690a.png)
refreshEnvironment();中 extract(this.context.getEnvironment().getPropertySources()) 抽取除系统变量外的其他变量;addConfigFilesToEnvironment();把原有的 environment里面的参数放到一个新建的 spring context 容器下重新加载,完事之后关闭新容器,这里就是获取参数的新值;
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-a5948c661403b4ac5ea687317d9ae74d.png)
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-0d94319f88d22ba53752005a20201b3a.png)
changes(before,extract(this.context.getEnvironment().getPropertySources())) 获取新的参数值,并和之前得进行比较找出改变得参数值。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-33f05089539e94d5abfdcd6aafb6e2b3.png)
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys)); 发布环境变更事件,并带上改变得参数值。
回到 ContextRefresher#refresh 方法,看下 this.scope.refreshAll(); 刷新标记@RefreshScope注解的 bean。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-5c0b401457342809770c552af9dcb0d4.png)
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-27b6a49e4c4842aa013bf8f0cc32165b.png)
super.destroy(); 方法,清楚 scope 里面的缓存,下次就会重新从 BeanFactory 获取一个新的实例会使用新的配置。
this.context.publishEvent(new RefreshScopeRefreshedEvent()); 方法发布事件。
服务端
DumpService
DumpService 类是一个抽象类负责从存储中查询配置保存到磁盘上,它有两个子类,EmbeddedDumpService嵌入式存储(DERBY)、ExternalDumpService扩展数存储。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-605188c595568b6ad4dbe8a2fdd494c8.png)
ExternalDumpService 实现类的 init 方法上 @PostConstruct 注解,在 spring 构建 bean 的过程中会执行带有 @PostConstruct 的初始化方法。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-c1478f2a95c7b91faffaa0fe9947f87b.png)
调用到抽象父类 DumpService#dumpOperate 的方法,调用到 dumpConfigInfo 方法,dumpConfigInfo 方法会判断是全量更新,还是追加更新。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-9a80d63e20dc65bcdba7e5f47f3ef8d7.png)
如果 isAllDump 为true 会走全量更新,会进行判断是否有快速更新配置、是否存在心跳检查文件、最后检查时间是否小于 6小时,上述判断都满足就不走全量更新,否则走全量更新。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-a5db217390d61e49e37159885a085b0e.png)
dumpAllProcessor.process(new DumpAllTask());将数据库中的所有 configInfo 配置信息查询出来,写入服务器端磁盘缓存。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-adb18a9276603769ee0d60bfb6ab6076.png)
persistService.findConfigMaxId(); 查询数据库中最大的主键,用于分页处理。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-6ef0e77d6b600701fb8ec6a15f7de0a3.png)
persistService.findAllConfigInfoFragment(lastMaxId, PAGE_SIZE); 从数据库中分页查询数据,每次查询1000条。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-e399a0a61fb0ec81363c8fbbe2b33a74.png)
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-15a3faffe7a6e33145b801ee792e5945.png)
ConfigCacheService.dump(cf.getDataId(), cf.getGroup(), cf.getTenant(), cf.getContent(), cf.getLastModified(),cf.getType()); 写入磁盘
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-059ebc22c9aaa1a1583d07fc14818047.png)
保存到文件中
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-2281c483bcc69b7c1cfb18687e80df8d.png)
updateMd5(groupKey, md5, lastModifiedTs); 缓存配置信息的 MD5 到内存中,并发布 LocalDataChangeEvent 事件。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-ae427e0a5d339f00027a8bd22d4aedd7.png)
事件监听器会在 NotifyCenter.registerSubscriber 调用。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-5877e8ecc869c172902f080f6019c129.png)
获取配置
HttpMethod.GET /nacos/v1/cs/configs 获取服务端配置接口,ConfigController#getConfig。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-cb561964f4ab95e96c276fedec1092e3.png)
在getConfig 中调用了 inner.doGetConfig(request, response, dataId, group, tenant, tag, clientIp);
在 doGetConfig 方法中会调用 DiskUtil.targetBetaFile(dataId, group, tenant);方法,从本地磁盘上获取,不是从 mysql 中拉取,如果直接修改 mysql数据不会生效的,需要发布 ConfigDataChangeEvent 事件,触发更新。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-489091aa4bf0a7a8d266a3cb0c7d90fc.png)
监听配置
HttpMethod.POST 请求调用 /nacos/v1/cs/configs/listener 轮询接口调用长连接。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-f28ce87d05f1e3f8182872463d3ac309.png)
longPollingService.addLongPollingClient(request, response, clientMd5Map, probeRequestSize); 长连接轮询处理。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-68b62dc8345f9c8f7d948f58de8a79b2.png)
SwitchService.getSwitchInteger(SwitchService.FIXED_DELAY_TIME, 500); 最多处理29.5s 需要保留0.5s来响应客户端,避免超时。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-abee553eb2edb7e430faa0f0dafbe2b4.png)
MD5Util.compareMd5(req, rsp, clientMd5Map); 比较客户端的 md5 与当前服务端的是否一致,不一致返回到 changedGroups。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-f865bc5ccb514c332503690f3f6bf28e.png)
有不一致数据直接响应 generateResponse(req, rsp, changedGroups);
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-01765036670430de9280877631ecf64b.png)
线程池执行长连接任务 ConfigExecutor.executeLongPolling。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-fb636dc840ee3342dfd247af58c008fd.png)
LongPollingService.ClientLongPolling#run 长轮询。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-9b72e8c67accf46db1d95ae661ca35a6.png)
ConfigExecutor.scheduleLongPolling 延迟 29.5s 执行
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-e1c91102fcc64cdf9731135c250c245f.png)
延迟执行先删除队列中自己的任务 allSubs.remove(ClientLongPolling.this);
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-102a5e895aca264559b835d33c9722d4.png)
allSubs.add(this); 添加到队列
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-5f9624f1794f689eb156fbe832dc344b.png)
inner.doPollingConfig(request, response, clientMd5Map, probeModify.length());
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-3d86a654fac79e2be756c1cebc382a70.png)
MD5Util.compareMd5(request, response, clientMd5Map); 和当前配置比较,返回有变更的配置
nacos 管理端变更配置
HttpMethod.POST /nacos/v1/cs/configs
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-a9e0346d946b5c5ab3828ea04f6bb427.png)
persistService.insertOrUpdate(srcIp, srcUser, configInfo, time, configAdvanceInfo, true); 持节化信息到数据库。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-bf44cfe30e900d7c67f1555b9e2392ce.png)
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-d8903f4d108fb9c5e48a29c50db13e15.png)
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-73741a510a356847b3ea5ca75a54475c.png)
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-303edef410442b1732ca8002cf6a495d.png)
回到 ConfigController#publishConfig 看下 ConfigChangePublisher.notifyConfigChange 方法,触发 ConfigDataChangeEvent 事件。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-3a26408090fa6dd8040e68649821d364.png)
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-916404674523d6a2ff5fccb9f400e545.png)
ConfigDataChangeEvent 事件监听。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-5d09d03c400b8ed07820755cc9140e34.png)
ConfigExecutor.executeAsyncNotify(new AsyncTask(nacosAsyncRestTemplate, queue)); 同步其他节点。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-ed81b0ad8e42824bc9f028861c75ff2e.png)
还有 LongPollingService 初始化的时候订阅了 LocalDataChangeEvent 事件,也会监听到。
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-9640e045a0e3b25f9b16680f612a13e4.png)
ConfigExecutor.executeLongPolling(new DataChangeTask(evt.groupKey, evt.isBeta, evt.betaIps));
看下 LongPollingService.DataChangeTask#run,push 模式, 遍历 allSubs 把变化的 key 响应客户端。clientSub.sendResponse(Arrays.asList(groupKey));
![](http://www.e-1.cn/wp-content/uploads/2023/12/frc-27a1c684276ebb0cf3ef2d2215d4d6a7.png)
作者:京东物流 张士欣
来源:京东云开发者社区 自猿其说Tech 转载请注明来源