美文网首页
nacos配置中心源码分析(1)

nacos配置中心源码分析(1)

作者: ok200 | 来源:发表于2021-03-31 14:03 被阅读0次
    nacos版本
     <dependency>
         <groupId>com.alibaba.cloud</groupId>
         <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
         <version>2.0.0.RELEASE</version>
     </dependency>
    

    bootstrap.yaml

    spring:
      application:
        name: service1
      profiles:
        active: dev
      cloud:
        nacos:
          config:
            namespace: 14d29622-bf23-4d4a-b86d-cdedc18ff83b
            file-extension: yaml
            server-addr: 127.0.0.1:8848
            ext-config[0]:
              data-id: common-${spring.profiles.active}.yaml
              refresh: true
    

    nacos是Alibaba 开源的微服务发现、管理、配置的组件,它提供了server端和客户端的能力,这里我们将来分析下nacos与spring cloud服务集成,作为微服务的配置中心的nacos的底层源码

    nacos读取配置分析
    // springboot服务的统一入口
    public static void main(String[] args) {
            SpringApplication.run(MyApplication.class, args);
    }
    

    启动微服务,从SpringApplication.run()方法进入,到达SpringApplication#run(java.lang.String...) 进行spring容器的启动的各项工作:
    1、准备上下文


    2、应用初始化设定(配置刷新)



    3、加载各种初始化配置,其中包括属性配置初始化



    4、PropertySourceBootstrapConfiguration#initialize方法中加载到nacos配置源
    public void initialize(ConfigurableApplicationContext applicationContext) {
       List<PropertySource<?>> composite = new ArrayList<>();
       AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
       boolean empty = true;
       ConfigurableEnvironment environment = applicationContext.getEnvironment();
       for (PropertySourceLocator locator : this.propertySourceLocators) {
           // 配置源
          Collection<PropertySource<?>> source = locator.locateCollection(environment);
          if (source == null || source.size() == 0) {
             continue;
          }
          List<PropertySource<?>> sourceList = new ArrayList<>();
          for (PropertySource<?> p : source) {
             if (p instanceof EnumerablePropertySource) {
                EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) p;
                sourceList.add(new BootstrapPropertySource<>(enumerable));
             }
             else {
                sourceList.add(new SimpleBootstrapPropertySource(p));
             }
          }
          logger.info("Located property source: " + sourceList);
          composite.addAll(sourceList);
          empty = false;
       }
       if (!empty) {
          MutablePropertySources propertySources = environment.getPropertySources();
          String logConfig = environment.resolvePlaceholders("${logging.config:}");
          LogFile logFile = LogFile.get(environment);
          for (PropertySource<?> p : environment.getPropertySources()) {
             if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
                propertySources.remove(p.getName());
             }
          }
          insertPropertySources(propertySources, composite);
          reinitializeLoggingSystem(environment, logConfig, logFile);
          setLogLevels(applicationContext, environment);
          handleIncludedProfiles(environment);
       }
    }
    

    5、然后到达此方法NacosPropertySourceLocator#locate 进行bootstrap.yaml配置的nacos相关配置获取与加载

    public PropertySource<?> locate(Environment env) {
    
       ConfigService configService = nacosConfigProperties.configServiceInstance();
    
       if (null == configService) {
          log.warn("no instance of config service found, can't load config from nacos");
          return null;
       }
       long timeout = nacosConfigProperties.getTimeout();
       nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
             timeout);
       String name = nacosConfigProperties.getName();
    
       String dataIdPrefix = nacosConfigProperties.getPrefix();
       if (StringUtils.isEmpty(dataIdPrefix)) {
          dataIdPrefix = name;
       }
    
       if (StringUtils.isEmpty(dataIdPrefix)) {
          dataIdPrefix = env.getProperty("spring.application.name");
       }
    
       CompositePropertySource composite = new CompositePropertySource(
             NACOS_PROPERTY_SOURCE_NAME);
        // 加载共享配置 对应 shared-dataids 配置项
       loadSharedConfiguration(composite);
        // 加载额外配置 对应 ext-config[n] 配置项
       loadExtConfiguration(composite);
        // 加载微服务对应配置
       loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
    
       return composite;
    }
    

    6、主要看NacosPropertySourceLocator#loadApplicationConfiguration 加载微服务配置,另外两个加载方法雷同。加载顺序,按照

    ${prefix}.${file-extension}  => ${prefix}-${spring.profiles.active}.${file-extension} 
    

    7、第一次启动从远程获取数据进行加载

    8、然后在NacosConfigService#getConfigInner方法进行加载配置,首先加载本地配置:nacos从远程读取配置文件成功,会将配置文件快照备份在磁盘上,当无法连接服务端时,使用本地快照的配置。如果存在本地备份,则不会从远程获取配置。那如果配置发生更新怎么办?这就涉及到nacos动态配置功能,将在下一篇进行分析。
    private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
        group = null2defaultGroup(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);
            configFilterChainManager.doFilter(null, cr);
            content = cr.getContent();
            return content;
        }
    
        try {  // 没有磁盘快照,从配置中心加载服务配置
            content = worker.getServerConfig(dataId, group, tenant, timeoutMs);
    
            cr.setContent(content);
    
            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);
        configFilterChainManager.doFilter(null, cr);
        content = cr.getContent();
        return content;
    }
    

    9、ClientWorker#getServerConfig方法

    public String getServerConfig(String dataId, String group, String tenant, long readTimeout)
        throws NacosException {
        if (StringUtils.isBlank(group)) {
            group = Constants.DEFAULT_GROUP;
        }
    
        HttpResult result = null;
        try {
            List<String> params = null;
            if (StringUtils.isBlank(tenant)) {
                params = Arrays.asList("dataId", dataId, "group", group);
            } else {
                params = Arrays.asList("dataId", dataId, "group", group, "tenant", tenant);
            }
            // get方法获取配置文件内容
            result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);
        } catch (IOException e) {
            String message = String.format(
                "[%s] [sub-server] get server config exception, dataId=%s, group=%s, tenant=%s", agent.getName(),
                dataId, group, tenant);
            LOGGER.error(message, e);
            throw new NacosException(NacosException.SERVER_ERROR, e);
        }
    
        switch (result.code) {
            case HttpURLConnection.HTTP_OK:
                // 备份快照至磁盘
                LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, result.content);
                return result.content;
            case HttpURLConnection.HTTP_NOT_FOUND:
                LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, null);
                return null;
            case HttpURLConnection.HTTP_CONFLICT: {
                LOGGER.error(
                    "[{}] [sub-server-error] get server config being modified concurrently, dataId={}, group={}, "
                        + "tenant={}", agent.getName(), dataId, group, tenant);
                throw new NacosException(NacosException.CONFLICT,
                    "data being modified, dataId=" + dataId + ",group=" + group + ",tenant=" + tenant);
            }
            case HttpURLConnection.HTTP_FORBIDDEN: {
                LOGGER.error("[{}] [sub-server-error] no right, dataId={}, group={}, tenant={}", agent.getName(), dataId,
                    group, tenant);
                throw new NacosException(result.code, result.content);
            }
            default: {
                LOGGER.error("[{}] [sub-server-error]  dataId={}, group={}, tenant={}, code={}", agent.getName(), dataId,
                    group, tenant, result.code);
                throw new NacosException(result.code,
                    "http error, code=" + result.code + ",dataId=" + dataId + ",group=" + group + ",tenant=" + tenant);
            }
        }
    }
    

    10、具体http请求方法,请求失败时,重试3次,从服务端配置地址获取下一个服务地址进行新的请求。如果请求成功nacos服务端返回的字符串内容。

    public HttpResult httpGet(String path, List<String> headers, List<String> paramValues, String encoding,
                              long readTimeoutMs) throws IOException {
        final long endTime = System.currentTimeMillis() + readTimeoutMs;
        final boolean isSSL = false;
    
        String currentServerAddr = serverListMgr.getCurrentServerAddr();
        // 默认 3
        int maxRetry = this.maxRetry;
    
        do {
            try {
                List<String> newHeaders = getSpasHeaders(paramValues);
                if (headers != null) {
                    newHeaders.addAll(headers);
                }
                HttpResult result = HttpSimpleClient.httpGet(
                    getUrl(currentServerAddr, path), newHeaders, paramValues, encoding,
                    readTimeoutMs, isSSL);
                if (result.code == HttpURLConnection.HTTP_INTERNAL_ERROR
                    || result.code == HttpURLConnection.HTTP_BAD_GATEWAY
                    || result.code == HttpURLConnection.HTTP_UNAVAILABLE) {
                    LOGGER.error("[NACOS ConnectException] currentServerAddr: {}, httpCode: {}",
                        serverListMgr.getCurrentServerAddr(), result.code);
                } else {
                    // Update the currently available server addr
                    serverListMgr.updateCurrentServerAddr(currentServerAddr);
                    return result;
                }
            } catch (ConnectException ce) {
                LOGGER.error("[NACOS ConnectException httpGet] currentServerAddr:{}, err : {}", serverListMgr.getCurrentServerAddr(), ce.getMessage());
            } catch (SocketTimeoutException stoe) {
                LOGGER.error("[NACOS SocketTimeoutException httpGet] currentServerAddr:{}, err : {}", serverListMgr.getCurrentServerAddr(), stoe.getMessage());
            } catch (IOException ioe) {
                LOGGER.error("[NACOS IOException httpGet] currentServerAddr: " + serverListMgr.getCurrentServerAddr(), ioe);
                throw ioe;
            }
    
            if (serverListMgr.getIterator().hasNext()) {
                // 下一个nacos服务地址
                currentServerAddr = serverListMgr.getIterator().next();
            } else {
                maxRetry --;
                if (maxRetry < 0) {
                    throw new ConnectException("[NACOS HTTP-GET] The maximum number of tolerable server reconnection errors has been reached");
                }
                serverListMgr.refreshCurrentServerAddr();
            }
    
        } while (System.currentTimeMillis() <= endTime);
    
        LOGGER.error("no available server");
        throw new ConnectException("no available server");
    }
    

    至此,nacos从配置中心加载配置的源码就分析完了。

    相关文章

      网友评论

          本文标题:nacos配置中心源码分析(1)

          本文链接:https://www.haomeiwen.com/subject/zuozhltx.html