这里还是更新了一下服务端和客户端的版本保持一致…
之前的文章版本不一致 所以看源码和在debug客户端 jar 的时候有点费劲~
当前版本:
Nacos 2.0.3
spring-cloud-alibaba 2.2.7.RELEASE
(这也是官方建议的版本)

分为客户端和服务端两个部分分析

客户端

自动配置 NacosConfigAutoConfiguration

这个是Nacos配置的自动注入类,看看在启动容器的时候做了什么

  1. @Configuration(
  2. proxyBeanMethods = false
  3. )
  4. @ConditionalOnProperty(
  5. name = {"spring.cloud.nacos.config.enabled"},
  6. matchIfMissing = true
  7. )
  8. public class NacosConfigAutoConfiguration {
  9. @Bean
  10. public NacosConfigProperties nacosConfigProperties(ApplicationContext context) {
  11. return context.getParent() != null && BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context.getParent(), NacosConfigProperties.class).length > 0 ? (NacosConfigProperties)BeanFactoryUtils.beanOfTypeIncludingAncestors(context.getParent(), NacosConfigProperties.class) : new NacosConfigProperties();
  12. }
  13. @Bean
  14. public NacosConfigManager nacosConfigManager(NacosConfigProperties nacosConfigProperties) {
  15. return new NacosConfigManager(nacosConfigProperties);
  16. }
  17. // ignore...
  18. }
  • 首先使用了 proxyBeanMethods = false

    proxyBeanMethods:是用来指定@Bean注解标注的方法是否使用代理,默认是true使用代理,直接从IOC容器之中取得对象;如果设置为false,也就是不使用注解,每次调用@Bean标注的方法获取到的对象和IOC容器中的都不一样,是一个新的对象,所以我们可以将此属性设置为false来提高性能(在不存在方法调用的时候);

  • 这里向容器内注册了一个 NacosConfigManager ,配置管理者,那么我可以这么理解:在容器初始化的时候根据 Nacos 的配置信息生成了 Nacos 的配置管理者


配置管理者 NacosConfigManager

  1. public class NacosConfigManager {
  2. private static ConfigService service = null;
  3. public NacosConfigManager(NacosConfigProperties nacosConfigProperties) {
  4. this.nacosConfigProperties = nacosConfigProperties;
  5. createConfigService(nacosConfigProperties);
  6. }
  7. static ConfigService createConfigService(NacosConfigProperties nacosConfigProperties) {
  8. if (Objects.isNull(service)) {
  9. Class var1 = NacosConfigManager.class;
  10. synchronized(NacosConfigManager.class) {
  11. try {
  12. if (Objects.isNull(service)) {
  13. service = NacosFactory.createConfigService(nacosConfigProperties.assembleConfigServiceProperties());
  14. }
  15. } catch (NacosException var4) {
  16. log.error(var4.getMessage());
  17. throw new NacosConnectionFailureException(nacosConfigProperties.getServerAddr(), var4.getMessage(), var4);
  18. }
  19. }
  20. }
  21. return service;
  22. }
  23. // ignore..
  24. }
  • 可以看到该配置管理者的静态方法,返回了一个 ConfigService 是一个接口,从名字上我们可以猜测,这是配置服务的抽象接口,抽象了客户端进行配置相关的行为
  • Nacos配置管理者(NacosConfigManager)保存了对于 ConfigService (NacosConfigService)的引用

配置拉取的落地 NacosConfigService

对于ConfigService而言,其只有一个实现,就是 NacosConfigService ,这里就直接贴上该类的代码

  1. @SuppressWarnings("PMD.ServiceOrDaoClassShouldEndWithImplRule")
  2. public class NacosConfigService implements ConfigService {
  3. /**
  4. * will be deleted in 2.0 later versions
  5. */
  6. @Deprecated
  7. ServerHttpAgent agent = null;
  8. /**
  9. * long polling.
  10. */
  11. private final ClientWorker worker;
  12. private final ConfigFilterChainManager configFilterChainManager;
  13. public NacosConfigService(Properties properties) throws NacosException {
  14. ValidatorUtils.checkInitParam(properties);
  15. initNamespace(properties);
  16. this.configFilterChainManager = new ConfigFilterChainManager(properties);
  17. ServerListManager serverListManager = new ServerListManager(properties);
  18. serverListManager.start();
  19. this.worker = new ClientWorker(this.configFilterChainManager, serverListManager, properties);
  20. // will be deleted in 2.0 later versions
  21. agent = new ServerHttpAgent(serverListManager);
  22. }
  23. @Override
  24. public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
  25. return getConfigInner(namespace, dataId, group, timeoutMs);
  26. }
  27. private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
  28. group = blank2defaultGroup(group);
  29. ParamUtils.checkKeyParam(dataId, group);
  30. ConfigResponse cr = new ConfigResponse();
  31. cr.setDataId(dataId);
  32. cr.setTenant(tenant);
  33. cr.setGroup(group);
  34. // use local config first 首先使用本地配置
  35. String content = LocalConfigInfoProcessor.getFailover(worker.getAgentName(), dataId, group, tenant);
  36. if (content != null) {
  37. LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}, config={}",
  38. worker.getAgentName(), dataId, group, tenant, ContentUtils.truncateContent(content));
  39. cr.setContent(content);
  40. String encryptedDataKey = LocalEncryptedDataKeyProcessor
  41. .getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);
  42. cr.setEncryptedDataKey(encryptedDataKey);
  43. configFilterChainManager.doFilter(null, cr);
  44. content = cr.getContent();
  45. return content;
  46. }
  47. try {
  48. ConfigResponse response = worker.getServerConfig(dataId, group, tenant, timeoutMs, false);
  49. cr.setContent(response.getContent());
  50. cr.setEncryptedDataKey(response.getEncryptedDataKey());
  51. configFilterChainManager.doFilter(null, cr);
  52. content = cr.getContent();
  53. return content;
  54. } catch (NacosException ioe) {
  55. if (NacosException.NO_RIGHT == ioe.getErrCode()) {
  56. throw ioe;
  57. }
  58. LOGGER.warn("[{}] [get-config] get from server error, dataId={}, group={}, tenant={}, msg={}",
  59. worker.getAgentName(), dataId, group, tenant, ioe.toString());
  60. }
  61. LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}",
  62. worker.getAgentName(), dataId, group, tenant, ContentUtils.truncateContent(content));
  63. content = LocalConfigInfoProcessor.getSnapshot(worker.getAgentName(), dataId, group, tenant);
  64. cr.setContent(content);
  65. String encryptedDataKey = LocalEncryptedDataKeyProcessor
  66. .getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);
  67. cr.setEncryptedDataKey(encryptedDataKey);
  68. configFilterChainManager.doFilter(null, cr);
  69. content = cr.getContent();
  70. return content;
  71. }
  72. }
  • **NacosFactory.createConfigService(nacosConfigProperties.assembleConfigServiceProperties())** 在NacosConfigManager代码里面(第14行)通过 NacosFactory 创建的 NacosConfigService,由其构造器可以看出,首先校验了配置文件的参数,接着new了三个对象并保存起来,分别是:ConfigFilterChainManager,ServerListManager,ClientWorker。**ConfigFilterChainManager**和过滤相关,而**ServerListManager**和服务相关,接着调用的start方法,可以看出本质上启动了一个线,**ClientWorker**启动了一个名为**com.alibaba.nacos.client.Worker**的守护线程,用来长轮询 这三个具体有什么用,暂时先不往里面深挖
  • **getConfig/getConfigInner** 这个方法从名称上可以知道,这个应该就是拉取配置的实现细节
  • **ServerHttpAgent**这个看别的文档,说的是利用HTTP请求查询配置、发布配置 ,不过在2.0+的版本里,通讯已经使用grpc的方式来进行调用,所以说该方法被废除了
  • **ClientWorker**长轮询的实现

    客户端拉取配置的时间点

    这里先说两个结论,在启动项目的时候Nacos会去服务端拉取配置,其次会在服务端变更配置文件之后也会去拉取配置

  • 这里我本地只启动了server1服务,配置中心写了两个配置,一个是给server1的配置,另一个是给未启动的server2

image.png
启动Nacos服务后,在 **NacosConfigManager#getConfigInner**也就是拉取配置的方法上打了断点,以此来看客户端都会在什么时候拉取配置

  • 第一次是在项目启动的时候
  • 第二次是在更新server1-dev.yml配置文件的时候
  • 同时,我更新server2-dev.yml,是并不会更新配置的,所以说明了一点,配置更改只会通知相应的服务,不会通知所有的
  • 其次,一次拉取配置的行为,走了多次断点,我这里放一下日志:

image.png
可以看到,会依次走 dataId 等于 server1 和 server1.yml 的文件去拉取配置,显而易见的此时拉取到的content是null的
image.png接着又去拉取 dataId 等于 server1-dev.yml 的文件,此时就会拉取到我在Nacos上配置的server1-dev.yml

拉取本地配置

  1. private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
  2. //ignore ..
  3. String content = LocalConfigInfoProcessor.getFailover(this.worker.getAgentName(), dataId, group, tenant);
  4. if (content != null) {
  5. LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}, config={}", new Object[]{this.worker.getAgentName(), dataId, group, tenant, ContentUtils.truncateContent(content)});
  6. cr.setContent(content);
  7. encryptedDataKey = LocalEncryptedDataKeyProcessor.getEncryptDataKeyFailover(this.agent.getName(), dataId, group, tenant);
  8. cr.setEncryptedDataKey(encryptedDataKey);
  9. this.configFilterChainManager.doFilter((IConfigRequest)null, cr);
  10. content = cr.getContent();
  11. return content;
  12. }
  13. // ignore ..
  14. }
  15. public static String getFailover(String serverName, String dataId, String group, String tenant) {
  16. File localPath = getFailoverFile(serverName, dataId, group, tenant);
  17. if (localPath.exists() && localPath.isFile()) {
  18. try {
  19. return readFile(localPath);
  20. } catch (IOException var6) {
  21. LOGGER.error("[" + serverName + "] get failover error, " + localPath, var6);
  22. return null;
  23. }
  24. } else {
  25. return null;
  26. }
  27. }

本人localPath是在:**C:\Users\xxxx\nacos\config\config_rpc_client_nacos\data\config-data\DEFAULT_GROUP\server1**

  • 由上面的代码可以看出,优先会去拉取本地的配置 **LocalConfigInfoProcessor.getFailover**
  • 如果获取到的内容 **content** 的话,就会通过本地加密数据密钥处理器拿到密钥,并且进行解密的操作,并且返回 可见 4-12 行

拉取远端配置

  1. private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
  2. // ignore ...
  3. ConfigResponse response = this.worker.getServerConfig(dataId, group, tenant, timeoutMs, false);
  4. cr.setContent(response.getContent());
  5. cr.setEncryptedDataKey(response.getEncryptedDataKey());
  6. this.configFilterChainManager.doFilter((IConfigRequest)null, cr);
  7. content = cr.getContent();
  8. return content;
  9. }
  • 我们可以看到,在这里调用了 **worker.getServerConfig(xxx)** 拉取的远端的配置
  • 之后从响应体中获取到内容 以及数据密钥,并且进行解密操作之后,将配置返回

那么,我们看看这个worker是怎么做的,其内部具体实现了什么操作?
**getServerConfig**从这个方法点进去后,经过一系列套娃,这里放上代码

  1. public ConfigResponse queryConfig(String dataId, String group, String tenant, long readTimeouts, boolean notify) throws NacosException {
  2. // ignore ..
  3. ConfigQueryResponse response = (ConfigQueryResponse)this.requestProxy(rpcClient, request, readTimeouts);
  4. ConfigResponse configResponse = new ConfigResponse();
  5. if (response.isSuccess()) {
  6. LocalConfigInfoProcessor.saveSnapshot(this.getName(), dataId, group, tenant, response.getContent());
  7. configResponse.setContent(response.getContent());
  8. String configType;
  9. if (StringUtils.isNotBlank(response.getContentType())) {
  10. configType = response.getContentType();
  11. } else {
  12. configType = ConfigType.TEXT.getType();
  13. }
  14. configResponse.setConfigType(configType);
  15. String encryptedDataKey = response.getEncryptedDataKey();
  16. LocalEncryptedDataKeyProcessor.saveEncryptDataKeySnapshot(ClientWorker.this.agent.getName(), dataId, group, tenant, encryptedDataKey);
  17. configResponse.setEncryptedDataKey(encryptedDataKey);
  18. return configResponse;
  19. }
  20. }
  • 可以看到这里通过 **requestProxy** 方法请求的服务端
  • 接着获取到配置后,如果成功,则 **LocalConfigInfoProcessor.saveSnapshot** 保存快照
  • 其次 从相应体中保存了密钥信息到本地的快照里_**saveEncryptDataKeySnapshot**_

再次向里面看,看看 requestProxy 是如何请求数据的
……. 未完待续,这里底层用的 grpc 这技术没用过,我先看看官方文档,之后再写 TAT
这是官方文档的简介,先摆上来:https://grpc.io/docs/what-is-grpc/introduction/

  • 使用grpc 的好处是: 就算服务端是Java,但是客户端可以使用其他语言,来维持通讯,调用服务端的方法!!!

    you can easily create a gRPC server in Java with clients in Go, Python, or Ruby.

这里贴一下从别处扒的流程图
Nacos配置拉取的过程 - 图4