Nacos Config源碼

先來看看一個配置中心需要滿足哪些功能?

  • 客戶端從服務(wù)端拉取配置
  • 客戶端緩存拉取到的配置
  • 客戶端監(jiān)聽配置變更(異步)
  • @Value的動態(tài)注入

引題:Spring如何加載外部化配置?

首先 關(guān)于Spring Cloud 如何實現(xiàn)的外部化配置加載?

org.springframework.cloud.bootstrap.config.PropertySourceLocator,在Nacos的spring-cloud-starter-alibaba-nacos-config-2.2.6.RELEASE.jar!\META-INF\spring.factories中 配置了NacosConfigBootstrapConfiguration,看一下這個配置類:

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigBootstrapConfiguration {

  @Bean
  @ConditionalOnMissingBean
  public NacosConfigProperties nacosConfigProperties() {
      return new NacosConfigProperties();
  }

  @Bean
  @ConditionalOnMissingBean
  public NacosConfigManager nacosConfigManager(
          NacosConfigProperties nacosConfigProperties) {
      return new NacosConfigManager(nacosConfigProperties);
  }

  @Bean
  public NacosPropertySourceLocator nacosPropertySourceLocator(
          NacosConfigManager nacosConfigManager) {
      return new NacosPropertySourceLocator(nacosConfigManager);
  }

}

這里面的NacosPropertySourceLocator繼承了PropertySourceLocator,那問題來了,Spring Cloud是如何處理這些PropertySourceLocator的呢?

PropertySourceLocator加載原理

在spring boot項目啟動時,有一個prepareContext的方法,它會回調(diào)所有實現(xiàn)了ApplicationContextInitializer的實例,來做一些初始化工作。

ApplicationContextInitializer是Spring框架原有的東西, 它的主要作用就是在ConfigurableApplicationContext類型(或者子類型)的ApplicationContext做 refresh之前,允許我們對ConfiurableApplicationContext的實例做進一步的設(shè)置和處理。

它可以用在需要對應(yīng)用程序上下文進行編程初始化的web應(yīng)用程序中,比如根據(jù)上下文環(huán)境注冊propertySource,或者配置文件。而Config 的這個配置中心的需求恰好需要這樣一個機制來完成。

PropertySourceBootstrapConfiguration

// 在Spring IoC容器刷新之前 回調(diào) ApplicationContextInitializer.initialize 方法
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(PropertySourceBootstrapProperties.class)
public class PropertySourceBootstrapConfiguration implements
        ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

    /**
     * Bootstrap property source name.
     */
    public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = "bootstrapProperties";

    // 這里會將 PropertySourceLocator 的所有實現(xiàn)給注入進來 實現(xiàn)在BootstrapImportSelector里
    @Autowired(required = false)
    private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();

    @Override
    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) {
            // 回調(diào)所有實現(xiàn)PropertySourceLocator接口實例的locate方法,并收集到source這個集合中。
            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;
        }
        // 只有propertysource不為空的情況,才會設(shè)置到environment中
        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);
        }
    }
}

Nacos客戶端加載配置

知道了Spring Cloud提供的接口,把目光放到Nacos里面。從上面的代碼可知,Spring Cloud會分別調(diào)用每個PropertySourceLocatorlocateCollection(org.springframework.core.env.Environment)方法,那就看一下這個方法。其中:locateCollection是接口的默認方法,最終的核心還是locate

NacosPropertySourceLocator#locate

public PropertySource<?> locate(Environment env) {
    nacosConfigProperties.setEnvironment(env);
    ConfigService configService = nacosConfigManager.getConfigService();

    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;
    }
    // 這里的dataId是項目名
    if (StringUtils.isEmpty(dataIdPrefix)) {
        dataIdPrefix = env.getProperty("spring.application.name");
    }

    CompositePropertySource composite = new CompositePropertySource(
        NACOS_PROPERTY_SOURCE_NAME);

    // 加載共享配置
    loadSharedConfiguration(composite);
    // 加載擴展配置
    loadExtConfiguration(composite);
    // 加載應(yīng)用配置
    loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
    return composite;
}

NacosPropertySourceLocator#loadApplicationConfiguration

private void loadApplicationConfiguration( CompositePropertySource compositePropertySource,
                                          String dataIdPrefix,
                                          NacosConfigProperties properties, 
                                          Environment environment) {
    String fileExtension = properties.getFileExtension(); //文件后綴 json、yml、properties等
    String nacosGroup = properties.getGroup(); // DEFAULT_GROUP
    // load directly once by default 沒有后綴的
    loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
                           fileExtension, true);
    // load with suffix, which have a higher priority than the default 加上后綴
    loadNacosDataIfPresent(compositePropertySource,
                           dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension,
                           true);
    // Loaded with profile, which have a higher priority than the suffix 加上激活的profile
    for (String profile : environment.getActiveProfiles()) {
        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) {
    if (null == dataId || dataId.trim().length() < 1) {
        return;
    }
    if (null == group || group.trim().length() < 1) {
        return;
    }
    NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
         fileExtension, isRefreshable);
    // 把屬性源保存到compositePropertySource中
    this.addFirstPropertySource(composite, propertySource, false);
}

NacosPropertySourceLocator#loadNacosPropertySource

private NacosPropertySource loadNacosPropertySource(final String dataId, final String group, 
                                                    String fileExtension,
                                                    boolean isRefreshable) {
    // 是否支持自動刷新 第一次需要從遠端拿
    if (NacosContextRefresher.getRefreshCount() != 0) {
        if (!isRefreshable) {
            return NacosPropertySourceRepository.getNacosPropertySource(dataId, group);
        }
    }
    return nacosPropertySourceBuilder.build(dataId, group, fileExtension, isRefreshable);
}

非自動刷新

com.alibaba.cloud.nacos.NacosPropertySourceRepository#getNacosPropertySource

就是本地的一個Map, key的值是 dataId + "," + group

private final static ConcurrentHashMap<String, NacosPropertySource> NACOS_PROPERTY_SOURCE_REPOSITORY = new ConcurrentHashMap<>();
public static NacosPropertySource getNacosPropertySource(String dataId, String group) {
    return NACOS_PROPERTY_SOURCE_REPOSITORY.get(getMapKey(dataId, group));
}

自動刷新

com.alibaba.cloud.nacos.client.NacosPropertySourceBuilder#build

NacosPropertySource build(String dataId, String group, String fileExtension,
                          boolean isRefreshable) {
    // 請求http接口的方式 從nacos上拿數(shù)據(jù) 然后封裝為PropertySource
    // 請求地址是:/v1/cs/configs
    List<PropertySource<?>> propertySources = loadNacosData(dataId, group, fileExtension);
    NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,
                                                                      group, dataId, 
                                                                      new Date(), 
                                                                      isRefreshable);
    // 插入到本地的Map中去
    NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
    return nacosPropertySource;
}

NacosPropertySourceBuilder#loadNacosData

loadNacosData這個方法的核心邏輯是configService.getConfig(dataId, group, timeout)

com.alibaba.nacos.client.config.NacosConfigService#getConfigInner

從這里開始 就是Nacos的代碼 與Spring Cloud無關(guān)了。

public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
    return getConfigInner(namespace, dataId, group, timeoutMs);
}
// timeoutMs默認是3000
private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
    // group如果為空則是DEFAULT_GROUP 否則是傳入的.trim()
    group = blank2defaultGroup(group);
    ParamUtils.checkKeyParam(dataId, group);
    ConfigResponse cr = new ConfigResponse();

    cr.setDataId(dataId);
    cr.setTenant(tenant);
    cr.setGroup(group);

    // 優(yōu)先使用本地配置
    String content=LocalConfigInfoProcessor.getFailover(agent.getName(),dataId, group,tenant);
    if (content != null) {
        
        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 {
        // 這里就是與nacos server交互了
        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;
        }
    }
    // 這里是從本地快照里面取
    content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);
    cr.setContent(content);
    String encryptedDataKey = LocalEncryptedDataKeyProcessor
        .getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);
    cr.setEncryptedDataKey(encryptedDataKey);
    // 沒有具體實現(xiàn) 可以忽略
    configFilterChainManager.doFilter(null, cr);
    content = cr.getContent();
    return content;
}

com.alibaba.nacos.client.config.impl.ClientWorker#getServerConfig

public ConfigResponse getServerConfig(String dataId, String group, String tenant, 
                                      long readTimeout)throws NacosException {
    ConfigResponse configResponse = new ConfigResponse();
    if (StringUtils.isBlank(group)) {
        group = Constants.DEFAULT_GROUP;
    }

    HttpRestResult<String> result = null;
    try {
        Map<String, String> params = new HashMap<String, String>(3);
        if (StringUtils.isBlank(tenant)) {
            params.put("dataId", dataId);
            params.put("group", group);
        } else {
            params.put("dataId", dataId);
            params.put("group", group);
            params.put("tenant", tenant);
        }
        // CONFIG_CONTROLLER_PATH = /v1/cs/configs
        // agent = ServerHttpAgent 里面默認用的是CloseableHttpClient
        result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, 
                               agent.getEncode(), readTimeout);
    } catch (Exception ex) {
        
        throw new NacosException(NacosException.SERVER_ERROR, ex);
    }

    switch (result.getCode()) {
        case HttpURLConnection.HTTP_OK:
            // 保存到本地快照 然后返回
            LocalConfigInfoProcessor.saveSnapshot(agent.getName(),dataId,group,tenant,
                                                  result.getData());
            configResponse.setContent(result.getData());
            String configType;
            if (result.getHeader().getValue(CONFIG_TYPE) != null) {
                configType = result.getHeader().getValue(CONFIG_TYPE);
            } else {
                configType = ConfigType.TEXT.getType();
            }
            configResponse.setConfigType(configType);
            String encryptedDataKey = result.getHeader().getValue(ENCRYPTED_DATA_KEY);
            LocalEncryptedDataKeyProcessor.saveEncryptDataKeySnapshot(agent.getName(),dataId, 
                                                                      group,tenant,
                                                                      encryptedDataKey);
            configResponse.setEncryptedDataKey(encryptedDataKey);
            return configResponse;
        case HttpURLConnection.HTTP_NOT_FOUND:
            LocalConfigInfoProcessor.saveSnapshot(agent.getName(),dataId,group,tenant, null);
            LocalEncryptedDataKeyProcessor.saveEncryptDataKeySnapshot(agent.getName(), dataId,
                                                                      group, tenant, null);
            return configResponse;
        case HttpURLConnection.HTTP_CONFLICT: {
            throw new NacosException(NacosException.CONFLICT,
                                     "data being modified, dataId=" + dataId + ",group=" + group + ",tenant=" + tenant);
        }
        case HttpURLConnection.HTTP_FORBIDDEN: {
            throw new NacosException(result.getCode(), result.getMessage());
        }
        default: {
            throw new NacosException(result.getCode(),
                                     "http error, code=" + result.getCode() + ",dataId=" + dataId + ",group=" + group + ",tenant="
                                     + tenant);
        }
    }
}

Nacos服務(wù)端處理拉取配置請求

到服務(wù)端這里 需要下載源碼去看,沒有maven的GAV可以引入,因此 下面的代碼都是粘貼自源碼。

com.alibaba.nacos.config.server.controller.ConfigController#getConfig

@GetMapping
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public void getConfig(HttpServletRequest request, HttpServletResponse response,
                      @RequestParam String dataId, @RequestParam String group,
                      @RequestParam(required = false, defaultValue = "") String tenant,
                      @RequestParam(required = false) String tag)
    throws IOException, ServletException, NacosException {
    // check tenant
    ParamUtils.checkTenant(tenant);
    tenant = NamespaceUtil.processNamespaceParameter(tenant);
    // check params
    ParamUtils.checkParam(dataId, group, "datumId", "content");
    ParamUtils.checkParam(tag);

    final String clientIp = RequestUtil.getRemoteIp(request);
    inner.doGetConfig(request, response, dataId, group, tenant, tag, clientIp);
}

com.alibaba.nacos.config.server.controller.ConfigServletInner#doGetConfig

public String doGetConfig(HttpServletRequest request, HttpServletResponse response, 
                          String dataId, String group, String tenant, String tag, 
                          String clientIp) throws IOException, ServletException {
    final String groupKey = GroupKey2.getKey(dataId, group, tenant);
    String autoTag = request.getHeader("Vipserver-Tag");
    String requestIpApp = RequestUtil.getAppName(request);
    // 加讀寫鎖 lockResult>0加鎖成功 =0表示緩存不存在 <0加鎖失敗
    int lockResult = tryConfigReadLock(groupKey);

    final String requestIp = RequestUtil.getRemoteIp(request);
    boolean isBeta = false;
    if (lockResult > 0) {
        // LockResult > 0 means cacheItem is not null 
        // and other thread can`t delete this cacheItem 這里的代碼拿下面分析 太TM長了
    } else if (lockResult == 0) {

        // 緩存項目前不存在 返回404
        ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1, 
                                        ConfigTraceService.PULL_EVENT_NOTFOUND, -1, requestIp);

        response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        response.getWriter().println("config data not exist");
        return HttpServletResponse.SC_NOT_FOUND + "";

    } else {
        // 表示有其他線程正在寫 返回409 呆會重試
        response.setStatus(HttpServletResponse.SC_CONFLICT);
        response.getWriter().println("requested file is being modified, please try later.");
        return HttpServletResponse.SC_CONFLICT + "";

    }

    return HttpServletResponse.SC_OK + "";
}

加鎖成功 準備讀

這里面刪除了一些邏輯 就按照正常的配置看源碼 太TM長了

FileInputStream fis = null;
try {
    String md5 = Constants.NULL;
    long lastModified = 0L;
    // ConcurrentHashMap<String, CacheItem> CACHE 從這里面取的值 groupKey -> cacheItem.
    CacheItem cacheItem = ConfigCacheService.getContentCache(groupKey);
    // 是否是beta發(fā)布 在控制臺頁面配置
    if (cacheItem.isBeta() && cacheItem.getIps4Beta().contains(clientIp)) {
        isBeta = true;
    }
    // 配置格式 轉(zhuǎn)換為對應(yīng)的枚舉 然后設(shè)置到響應(yīng)頭中 解釋一下,為啥要用一下枚舉轉(zhuǎn)換一下 是為了保證像YML、Yml以及
    // yml或者前后帶了空格 這樣的 都可以轉(zhuǎn)換為yml 其實就是忽略大小寫清空前后空格 來一次轉(zhuǎn)換 那么 這么做不復雜么?
    final String configType =
        (null != cacheItem.getType()) ? cacheItem.getType() : FileTypeEnum.TEXT.getFileType();
    response.setHeader("Config-Type", configType);
    FileTypeEnum fileTypeEnum = FileTypeEnum.getFileTypeEnumByFileExtensionOrFileType(configType);
    String contentTypeHeader = fileTypeEnum.getContentType();
    response.setHeader(HttpHeaderConsts.CONTENT_TYPE, contentTypeHeader);

    File file = null;
    ConfigInfoBase configInfoBase = null;
    PrintWriter out = null;
    if (isBeta) { // 判斷是否是beta 請求頭中加上isBeta=true
        // 這里的代碼刪了
    } else {
        if (StringUtils.isBlank(tag)) { // 標簽為空
            if (isUseTag(cacheItem, autoTag)) {
                // 這里的代碼刪了
            } else {
                // 獲取緩存項目的MD5 MD5的值再每一次配置更新會發(fā)生變化
                md5 = cacheItem.getMd5();
                lastModified = cacheItem.getLastModifiedTs();
                if (PropertyUtil.isDirectRead()) { //如果單機模式 從數(shù)據(jù)庫中取
                    configInfoBase = persistService.findConfigInfo(dataId, group, tenant);
                } else {
                    // 集群模式下 從文件中取 速度比數(shù)據(jù)庫快
                    file = DiskUtil.targetFile(dataId, group, tenant);
                }
                if (configInfoBase == null && fileNotExist(file)) { // 判斷是否取出來了
                    
                    ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1,
                                                    ConfigTraceService.PULL_EVENT_NOTFOUND, -1,
                                                    requestIp);
                    response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                    response.getWriter().println("config data not exist");
                    return HttpServletResponse.SC_NOT_FOUND + "";
                }
            }
        } else {
            // 這里的代碼刪了 和上面大同小異
        }
    }

    response.setHeader(Constants.CONTENT_MD5, md5);

    // Disable cache.
    response.setHeader("Pragma", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setHeader("Cache-Control", "no-cache,no-store");
    if (PropertyUtil.isDirectRead()) {
        response.setDateHeader("Last-Modified", lastModified);
    } else {
        fis = new FileInputStream(file);
        response.setDateHeader("Last-Modified", file.lastModified());
    }

    if (PropertyUtil.isDirectRead()) {
        out = response.getWriter();
        out.print(configInfoBase.getContent());
        out.flush();
        out.close();
    } else {
        fis.getChannel().transferTo(0L, fis.getChannel().size(), 
                                    Channels.newChannel(response.getOutputStream()));
    }

    final long delayed = System.currentTimeMillis() - lastModified;

    // TODO distinguish pull-get && push-get
    /*
     Otherwise, delayed cannot be used as the basis of push delay directly,
     because the delayed value of active get requests is very large.
    */
    ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, lastModified,
                                    ConfigTraceService.PULL_EVENT_OK, delayed, requestIp);

} finally {
    releaseConfigReadLock(groupKey);
    IoUtils.closeQuietly(fis);
}

Nacos客戶端更新配置緩存

在客戶端啟動成功之后,在內(nèi)存中做了緩存,但是后續(xù)配置的變化,客戶端會取更新緩存,那么更新流程如下:

  • 客戶端發(fā)起長輪訓請求
  • 服務(wù)端收到請求以后,先比較服務(wù)端緩存中的數(shù)據(jù)是否相同
    • 如果不同,則直接返回
    • 如果相同,則通過schedule延遲29.5s之后再執(zhí)行比較
  • 為了保證當服務(wù)端在29.5s之內(nèi)發(fā)生數(shù)據(jù)變化能夠及時通知給客戶端,服務(wù)端采用事件訂閱的方式來監(jiān)聽服務(wù)端本地數(shù)據(jù)變化的事件,一旦收到事件,則觸發(fā)DataChangeTask的通知,并且遍歷allStubs隊列中的ClientLongPolling,把結(jié)果寫回到客戶端,就完成了一次數(shù)據(jù)的推送
  • 如果 DataChangeTask 任務(wù)完成了數(shù)據(jù)的 “推送” 之后,ClientLongPolling 中的調(diào)度任務(wù)又開始執(zhí)行了怎么辦呢? 很簡單,只要在進行 “推送” 操作之前,先將原來等待執(zhí)行的調(diào)度任務(wù)取消掉就可以了,這樣就防止了推送操作寫完響應(yīng)數(shù)據(jù)之后,調(diào)度任務(wù)又去寫響應(yīng)數(shù)據(jù),這時肯定會報錯的。所以,在ClientLongPolling方法中,最開始的一個步驟就是刪除訂閱事件

緩存的更新一定是異步來做的,那就找一下哪里有異步的操作。在ClientWorker中,ClientWorker的初始化看這里

com.alibaba.nacos.client.config.impl.ClientWorker#ClientWorker

public ClientWorker(final HttpAgent agent, 
                    final ConfigFilterChainManager configFilterChainManager,
                    final Properties properties) {
    this.agent = agent;
    this.configFilterChainManager = configFilterChainManager;
    init(properties);

    this.executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setName("com.alibaba.nacos.client.Worker." + agent.getName());
            t.setDaemon(true);
            return t;
        }
    });

    this.executorService = Executors
        .newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("com.alibaba.nacos.client.Worker.longPolling." + agent.getName());
                t.setDaemon(true);
                return t;
            }
        });
    // 固定延遲時間的線程
    this.executor.scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
            try {
                checkConfigInfo(); // 檢查配置
            } catch (Throwable e) {
                LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
            }
        }
    }, 1L, 10L, TimeUnit.MILLISECONDS);
}

com.alibaba.nacos.client.config.impl.ClientWorker#checkConfigInfo

public void checkConfigInfo() {
    // cacheMap = new ConcurrentHashMap<String, CacheData>();  groupKey -> cacheData
    int listenerSize = cacheMap.size();
    // Round up the longingTaskCount. ParamUtil.getPerTaskConfigSize() = 3000 
    // 分片
    int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());
    if (longingTaskCount > currentLongingTaskCount) {
        for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {
            // The task list is no order.So it maybe has issues when changing.
            executorService.execute(new LongPollingRunnable(i));
        }
        currentLongingTaskCount = longingTaskCount;
    }
}

com.alibaba.nacos.client.config.impl.ClientWorker.LongPollingRunnable

LongPollingRunnable實現(xiàn)了Runnable,因此 只看run方法即可。這里面的代碼 只能自己打斷點去看。

public void run() {

    List<CacheData> cacheDatas = new ArrayList<CacheData>();
    List<String> inInitializingCacheList = new ArrayList<String>();
    try {
        // check failover config
        for (CacheData cacheData : cacheMap.values()) {
            if (cacheData.getTaskId() == taskId) {
                cacheDatas.add(cacheData);
                try {
                    // 內(nèi)存數(shù)據(jù)與本地數(shù)據(jù)對比
                    checkLocalConfig(cacheData);
                    if (cacheData.isUseLocalConfigInfo()) {
                        cacheData.checkListenerMd5();
                    }
                } catch (Exception e) {
                    LOGGER.error("get local config info error", e);
                }
            }
        }

        // check server config 可能發(fā)生變化的數(shù)據(jù)與Nacos Server對比 
        // 這里面發(fā)送了長輪詢請求。返回值是服務(wù)端明確告知 確實存在變更的key
        List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas,inInitializingCacheList);
        
        for (String groupKey : changedGroupKeys) {
            String[] key = GroupKey.parseKey(groupKey);
            String dataId = key[0];
            String group = key[1];
            String tenant = null;
            if (key.length == 3) {
                tenant = key[2];
            }
            try {
                // 這里是客戶端拉取配置的請求 在Nacos客戶端加載配置那里
                ConfigResponse response = getServerConfig(dataId, group, tenant, 3000L);
                CacheData cache = cacheMap.get(GroupKey.getKeyTenant(dataId, group, tenant));
                cache.setContent(response.getContent()); // 更新本地緩存
                cache.setEncryptedDataKey(response.getEncryptedDataKey());
                if (null != response.getConfigType()) {
                    cache.setType(response.getConfigType());
                }
            } catch (NacosException ioe) {
                // 刪掉了日志打印
            }
        }
        for (CacheData cacheData : cacheDatas) {
            if (!cacheData.isInitializing() ||
                inInitializingCacheList.contains(GroupKey.getKeyTenant(cacheData.dataId,
                                                                       cacheData.group,
                                                                       cacheData.tenant))) {
                
                cacheData.checkListenerMd5();
                cacheData.setInitializing(false);
            }
        }
        inInitializingCacheList.clear();
        // 再次執(zhí)行
        executorService.execute(this);

    } catch (Throwable e) {

        // 出現(xiàn)錯誤 就延時執(zhí)行
        executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS);
    }
}

com.alibaba.nacos.client.config.impl.ClientWorker#checkUpdateConfigStr

長輪詢的實現(xiàn) 其實就是調(diào)用http的請求,在發(fā)送請求時,請求頭設(shè)置了Long-Pulling-TimeoutLong-Pulling-Timeout-No-Hangup

List<String> checkUpdateConfigStr(String probeUpdateString, boolean isInitializingCacheList) throws Exception {

    Map<String, String> params = new HashMap<String, String>(2);
    params.put(Constants.PROBE_MODIFY_REQUEST, probeUpdateString);
    Map<String, String> headers = new HashMap<String, String>(2);
    headers.put("Long-Pulling-Timeout", "" + timeout);

    // told server do not hang me up if new initializing cacheData added in
    if (isInitializingCacheList) {
        headers.put("Long-Pulling-Timeout-No-Hangup", "true");
    }

    if (StringUtils.isBlank(probeUpdateString)) {
        return Collections.emptyList();
    }

    try {
        // In order to prevent the server from handling the delay of the client's long task,
        // increase the client's read timeout to avoid this problem.

        long readTimeoutMs = timeout + (long) Math.round(timeout >> 1);
        HttpRestResult<String> result = agent.httpPost("/v1/cs/configs/listener", headers,
                                                       params,agent.getEncode(),readTimeoutMs);

        if (result.ok()) {
            setHealthServer(true);
            return parseUpdateDataIdResponse(result.getData());
        } else {
            setHealthServer(false);
        }
    } catch (Exception e) {
        setHealthServer(false);
        throw e;
    }
    return Collections.emptyList();
}

Nacos 服務(wù)端處理長輪詢

com.alibaba.nacos.config.server.controller.ConfigController#listener

@PostMapping("/listener")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public void listener(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
    String probeModify = request.getParameter("Listening-Configs");
    if (StringUtils.isBlank(probeModify)) {
        throw new IllegalArgumentException("invalid probeModify");
    }

    probeModify = URLDecoder.decode(probeModify, Constants.ENCODE);

    Map<String, String> clientMd5Map;
    try {
        clientMd5Map = MD5Util.getClientMd5Map(probeModify);
    } catch (Throwable e) {
        throw new IllegalArgumentException("invalid probeModify");
    }

    // do long-polling
    inner.doPollingConfig(request, response, clientMd5Map, probeModify.length());
}

com.alibaba.nacos.config.server.controller.ConfigServletInner#doPollingConfig

public String doPollingConfig(HttpServletRequest request, HttpServletResponse response,
                              Map<String, String> clientMd5Map, int probeRequestSize) 
    throws IOException {

    // Long polling. 判斷當前請求是否支持長輪詢
    if (LongPollingService.isSupportLongPolling(request)) {
        longPollingService.addLongPollingClient(request, response, clientMd5Map,
                                                probeRequestSize);
        return HttpServletResponse.SC_OK + "";
    }

    // Compatible with short polling logic. MD5 逐項對比 返回的是不相同的返回
    List<String> changedGroups = MD5Util.compareMd5(request, response, clientMd5Map);

    // Compatible with short polling result.
    String oldResult = MD5Util.compareMd5OldResult(changedGroups);
    String newResult = MD5Util.compareMd5ResultString(changedGroups);

    String version = request.getHeader(Constants.CLIENT_VERSION_HEADER);
    if (version == null) {
        version = "2.0.0";
    }
    int versionNum = Protocol.getVersionNumber(version);

    // Before 2.0.4 version, return value is put into header.
    if (versionNum < START_LONG_POLLING_VERSION_NUM) {
        response.addHeader(Constants.PROBE_MODIFY_RESPONSE, oldResult);
        response.addHeader(Constants.PROBE_MODIFY_RESPONSE_NEW, newResult);
    } else {
        request.setAttribute("content", newResult);
    }

    Loggers.AUTH.info("new content:" + newResult);

    // Disable cache.
    response.setHeader("Pragma", "no-cache");
    response.setDateHeader("Expires", 0);
    response.setHeader("Cache-Control", "no-cache,no-store");
    response.setStatus(HttpServletResponse.SC_OK);
    return HttpServletResponse.SC_OK + "";
}

com.alibaba.nacos.config.server.service.LongPollingService#addLongPollingClient

public void addLongPollingClient(HttpServletRequest req, HttpServletResponse rsp, 
                                 Map<String, String> clientMd5Map, int probeRequestSize) {

    String str = req.getHeader(LongPollingService.LONG_POLLING_HEADER);
    String noHangUpFlag = req.getHeader(LongPollingService.LONG_POLLING_NO_HANG_UP_HEADER);
    String appName = req.getHeader(RequestUtil.CLIENT_APPNAME_HEADER);
    String tag = req.getHeader("Vipserver-Tag");
    int delayTime = SwitchService.getSwitchInteger(SwitchService.FIXED_DELAY_TIME, 500);

    // and one response is returned 500 ms in advance to avoid client timeout.
    long timeout = Math.max(10000, Long.parseLong(str) - delayTime);
    if (isFixedPolling()) { // 默認false
        timeout = Math.max(10000, getFixedPollingInterval());
    } else {
        long start = System.currentTimeMillis();
        // 先比較 是否發(fā)生了變更
        List<String> changedGroups = MD5Util.compareMd5(req, rsp, clientMd5Map);
        if (changedGroups.size() > 0) {
            generateResponse(req, rsp, changedGroups);
            return;
        } else if (noHangUpFlag != null && noHangUpFlag.equalsIgnoreCase(TRUE_STR)) {
            return;
        }
    }
    String ip = RequestUtil.getRemoteIp(req);

    // Must be called by http thread, or send response. 將請求變成異步的
    final AsyncContext asyncContext = req.startAsync();

    // AsyncContext.setTimeout() is incorrect, Control by oneself
    asyncContext.setTimeout(0L);
    // 獲取當前的會話 這個會話的掌控權(quán) 完全在服務(wù)端 通過線程池提交一個任務(wù)
    ConfigExecutor.executeLongPolling(new ClientLongPolling(asyncContext, clientMd5Map, ip,
                                                            probeRequestSize, timeout, appName,
                                                            tag));
}

com.alibaba.nacos.config.server.service.LongPollingService.ClientLongPolling#run

public void run() {
    // 這里依舊是提交任務(wù) 在29.5s 之后執(zhí)行 = newSingleScheduledExecutorService
    asyncTimeoutFuture = ConfigExecutor.scheduleLongPolling(new Runnable() {
        @Override
        public void run() {
            try {
                getRetainIps().put(ClientLongPolling.this.ip, System.currentTimeMillis());

                // Delete subsciber's relations.
                allSubs.remove(ClientLongPolling.this);

                if (isFixedPolling()) {
                    
                    List<String> changedGroups = MD5Util
                        .compareMd5((HttpServletRequest) asyncContext.getRequest(),
                                    (HttpServletResponse) asyncContext.getResponse(),
                                    clientMd5Map);
                    if (changedGroups.size() > 0) {
                        sendResponse(changedGroups);
                    } else {
                        sendResponse(null);
                    }
                } else {
                    
                    sendResponse(null);
                }
            } catch (Throwable t) {
            }

        }

    }, timeoutTime, TimeUnit.MILLISECONDS);
    // 記錄當前任務(wù)
    allSubs.add(this);
}

但是這里有一個問題 就是 在這29.5s內(nèi) 發(fā)生了數(shù)據(jù)的變更 難道這個長輪詢還要一直等到29.5s結(jié)束才返回數(shù)據(jù)么?

當然不是,客戶端會話保存在了allSubs中,只需要注冊一個監(jiān)聽 去響應(yīng)發(fā)生變化的事件即可。

Nacos客戶端監(jiān)聽配置的變化

這里想寫一些東西 但是不知道寫什么 就是代碼尋找的過程 也是沒有的

com.alibaba.nacos.config.server.service.LongPollingService#LongPollingService

public LongPollingService() {
    allSubs = new ConcurrentLinkedQueue<ClientLongPolling>();

    ConfigExecutor.scheduleLongPolling(new StatTask(), 0L, 10L, TimeUnit.SECONDS);

    // Register LocalDataChangeEvent to NotifyCenter.
    NotifyCenter.registerToPublisher(LocalDataChangeEvent.class, NotifyCenter.ringBufferSize);

    // 這里就看到了注冊監(jiān)聽者 監(jiān)聽 LocalDataChangeEvent
    NotifyCenter.registerSubscriber(new Subscriber() {

        @Override
        public void onEvent(Event event) {
            if (isFixedPolling()) {
                // Ignore.
            } else {
                if (event instanceof LocalDataChangeEvent) {
                    LocalDataChangeEvent evt = (LocalDataChangeEvent) event;
                    // 這里又提交了一個任務(wù)
                    ConfigExecutor.executeLongPolling(
                        new DataChangeTask(evt.groupKey, evt.isBeta, evt.betaIps));
                }
            }
        }

        @Override
        public Class<? extends Event> subscribeType() {
            return LocalDataChangeEvent.class;
        }
    });

}

com.alibaba.nacos.config.server.service.LongPollingService.DataChangeTask#run

public void run() {
    try {
        ConfigCacheService.getContentBetaMd5(groupKey);
        for (Iterator<ClientLongPolling> iter = allSubs.iterator(); iter.hasNext(); ) {
            ClientLongPolling clientSub = iter.next();
            if (clientSub.clientMd5Map.containsKey(groupKey)) {
                // If published tag is not in the beta list, then it skipped.
                if (isBeta && !CollectionUtils.contains(betaIps, clientSub.ip)) {
                    continue;
                }

                // If published tag is not in the tag list, then it skipped.
                if (StringUtils.isNotBlank(tag) && !tag.equals(clientSub.tag)) {
                    continue;
                }

                getRetainIps().put(clientSub.ip, System.currentTimeMillis());
                iter.remove(); // Delete subscribers' relationships.
                // 這里就是給客戶端響應(yīng)了
                clientSub.sendResponse(Arrays.asList(groupKey));
            }
        }
    } catch (Throwable t) {}
}

com.alibaba.nacos.config.server.service.LongPollingService.ClientLongPolling#sendResponse

asyncContext.complete(); 返回異步請求用的

void sendResponse(List<String> changedGroups) {     
    // 將29.5s的定時任務(wù)取消
    if (null != asyncTimeoutFuture) {
        asyncTimeoutFuture.cancel(false);
    }
    generateResponse(changedGroups);
}
void generateResponse(List<String> changedGroups) {
    if (null == changedGroups) { // 無變化的情況下

        // Tell web container to send http response.
        asyncContext.complete();
        return;
    }

    HttpServletResponse response = (HttpServletResponse) asyncContext.getResponse();

    try {
        final String respString = MD5Util.compareMd5ResultString(changedGroups);

        // Disable cache.
        response.setHeader("Pragma", "no-cache");
        response.setDateHeader("Expires", 0);
        response.setHeader("Cache-Control", "no-cache,no-store");
        response.setStatus(HttpServletResponse.SC_OK);
        response.getWriter().println(respString);
        asyncContext.complete();
    } catch (Exception ex) {
        PULL_LOG.error(ex.toString(), ex);
        asyncContext.complete();
    }
}

SpringCloud如何同步更新緩存

Nacos客戶端加載配置這里,有一個很重要的判斷,就是NacosContextRefresher.getRefreshCount() != 0,這個refreshCount在哪里做的更新呢?看一下這個類的定義吧:

class NacosContextRefresher implements ApplicationListener<ApplicationReadyEvent>,這里其實監(jiān)聽了事件ApplicationReadyEvent,那就看一下他的onApplicationEvent

NacosContextRefresher#onApplicationEvent

public void onApplicationEvent(ApplicationReadyEvent event) {
    // many Spring context
    if (this.ready.compareAndSet(false, true)) {
        this.registerNacosListenersForApplications();
    }
}
private void registerNacosListenersForApplications() {
    // 判斷 配置spring.cloud.nacos.config.refresh-enabled 默認是true
    if (isRefreshEnabled()) {
        for (NacosPropertySource propertySource : NacosPropertySourceRepository.getAll()) {
            // propertySource.isRefreshable()默認是true
            if (!propertySource.isRefreshable()) {
                continue;
            }
            String dataId = propertySource.getDataId();
            registerNacosListener(propertySource.getGroup(), dataId);
        }
    }
}
private void registerNacosListener(final String groupKey, final String dataKey) {
    String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
    Listener listener = listenerMap.computeIfAbsent(key, lst -> new AbstractSharedListener() {
        @Override
        public void innerReceive(String dataId, String group,
                                 String configInfo) {
            // refreshCount自增1
            refreshCountIncrement();
            // 記錄刷新歷史 供spring acurator使用
            nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
            // 發(fā)布刷新 Nacos配置 的事件 這個事件是Spring Cloud提供刷新事件 分別刷新了
            // 1. Environment上下文 2.@RefreshScope標注的properties以及對應(yīng)的Bean
            applicationContext.publishEvent(new RefreshEvent(this, null, "Refresh config"));
        }
    });
    try {
        configService.addListener(dataKey, groupKey, listener);
    } catch (NacosException e) {
        
    }
}

org.springframework.cloud.endpoint.event.RefreshEventListener

這里少貼一點代碼 還是看onApplicationEvent方法,這個方法調(diào)用了handle(RefreshEvent event),而方法里面調(diào)用了this.refresh.refresh(),即org.springframework.cloud.context.refresh.ContextRefresher#refresh

org.springframework.cloud.context.refresh.ContextRefresher#refresh

public synchronized Set<String> refresh() {
    Set<String> keys = refreshEnvironment();
    this.scope.refreshAll();
    return keys;
}
// 更新 environment上下文
public synchronized Set<String> refreshEnvironment() {
    Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
    addConfigFilesToEnvironment();
    Map<String, Object> after = extract(this.context.getEnvironment().getPropertySources());
    Set<String> keys = changes(before, after.keySet());
    // org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder 負責監(jiān)聽
    this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
    return keys;
}

org.springframework.cloud.context.scope.refresh.RefreshScope#refreshAll

public void refreshAll() {
    // 銷毀Bean
    super.destroy();
    // 這里沒有找到事件的監(jiān)聽者
    this.context.publishEvent(new RefreshScopeRefreshedEvent());
}

Nacos集群間數(shù)據(jù)同步

Nacos發(fā)布配置的接口處理在com.alibaba.nacos.config.server.controller.ConfigController#publishConfig,具體看這里面的邏輯,代碼不粘了,直接引到數(shù)據(jù)同步的位置。

ConfigChangePublisher.notifyConfigChange(new ConfigDataChangeEvent(false, dataId, group, tenant, time.getTime()));

com.alibaba.nacos.config.server.service.notify.AsyncNotifyService#AsyncNotifyService

public AsyncNotifyService(ServerMemberManager memberManager) {
    this.memberManager = memberManager;

    // Register ConfigDataChangeEvent to NotifyCenter.
    NotifyCenter.registerToPublisher(ConfigDataChangeEvent.class, NotifyCenter.ringBufferSize);

    // Register A Subscriber to subscribe ConfigDataChangeEvent.
    NotifyCenter.registerSubscriber(new Subscriber() {

        @Override
        public void onEvent(Event event) {
            // Generate ConfigDataChangeEvent concurrently
            if (event instanceof ConfigDataChangeEvent) {
                ConfigDataChangeEvent evt = (ConfigDataChangeEvent) event;
                long dumpTs = evt.lastModifiedTs;
                String dataId = evt.dataId;
                String group = evt.group;
                String tenant = evt.tenant;
                String tag = evt.tag;
                // 集群間節(jié)點數(shù)據(jù)
                Collection<Member> ipList = memberManager.allMembers();

                // In fact, any type of queue here can be
                Queue<NotifySingleTask> queue = new LinkedList<NotifySingleTask>();
                for (Member member : ipList) {
                    queue.add(new NotifySingleTask(dataId, group, tenant, tag, dumpTs, 
                                                   member.getAddress(), evt.isBeta));
                }
                // 再次提交異步任務(wù)
                ConfigExecutor.executeAsyncNotify(new AsyncTask(nacosAsyncRestTemplate,queue));
            }
        }

        @Override
        public Class<? extends Event> subscribeType() {
            return ConfigDataChangeEvent.class;
        }
    });
}

com.alibaba.nacos.config.server.service.notify.AsyncNotifyService.AsyncTask#run

public void run() {
    executeAsyncInvoke();
}
private void executeAsyncInvoke() {
    while (!queue.isEmpty()) {
        NotifySingleTask task = queue.poll();
        String targetIp = task.getTargetIP();
        if (memberManager.hasMember(targetIp)) {
            // 判斷節(jié)點是否健康 不健康 則延時執(zhí)行
            boolean unHealthNeedDelay = memberManager.isUnHealth(targetIp);
            if (unHealthNeedDelay) {
                // target ip is unhealthy, then put it in the notification list
                ConfigTraceService.logNotifyEvent(task.getDataId(),task.getGroup(),
                                                  task.getTenant(),null,task.getLastModified(),
                                                  InetUtils.getSelfIP(), 
                                                  ConfigTraceService.NOTIFY_EVENT_UNHEALTH,
                                                  0, task.target);
                // get delay time and set fail count to the task
                asyncTaskExecute(task);
            } else {
                Header header = Header.newInstance();
                header.addParam(NotifyService.NOTIFY_HEADER_LAST_MODIFIED,
                                String.valueOf(task.getLastModified()));
                header.addParam(NotifyService.NOTIFY_HEADER_OP_HANDLE_IP,
                                InetUtils.getSelfIP());
                if (task.isBeta) {
                    header.addParam("isBeta", "true");
                }
                AuthHeaderUtil.addIdentityToHeader(header);
                // 其實還是發(fā)送了http的請求 url=/v1/cs/communication/dataChange
                restTemplate.get(task.url, header, Query.EMPTY, String.class, 
                                 new AsyncNotifyCallBack(task));
            }
        }
    }
}

com.alibaba.nacos.config.server.controller.CommunicationController#notifyConfigInfo

@GetMapping("/dataChange")
public Boolean notifyConfigInfo(HttpServletRequest request, @RequestParam String dataId,
                                @RequestParam String group,
                                @RequestParam(required=false,defaultValue ="") String tenant,
                                @RequestParam(required = false) String tag) {
    dataId = dataId.trim();
    group = group.trim();
    String lastModified = request.getHeader(NotifyService.NOTIFY_HEADER_LAST_MODIFIED);
    long lastModifiedTs =StringUtils.isEmpty(lastModified) ? -1 : Long.parseLong(lastModified);
    String handleIp = request.getHeader(NotifyService.NOTIFY_HEADER_OP_HANDLE_IP);
    String isBetaStr = request.getHeader("isBeta");
    if (StringUtils.isNotBlank(isBetaStr) && trueStr.equals(isBetaStr)) {
        dumpService.dump(dataId, group, tenant, lastModifiedTs, handleIp, true);
    } else {
        dumpService.dump(dataId, group, tenant, tag, lastModifiedTs, handleIp);
    }
    return true;
}

com.alibaba.nacos.config.server.service.dump.DumpService#dump

public void dump(String dataId, String group, String tenant, String tag, long lastModified, 
                 String handleIp, boolean isBeta) {
    String groupKey = GroupKey2.getKey(dataId, group, tenant);
    String taskKey = String.join("+", dataId, group, tenant, String.valueOf(isBeta), tag);
    dumpTaskMgr.addTask(taskKey, new DumpTask(groupKey, tag, lastModified, handleIp, isBeta));
}

com.alibaba.nacos.config.server.manager.TaskManager#addTask

public void addTask(Object key, AbstractDelayTask newTask) {
    // 這一部分代碼是 super.addTask(key, newTask);
    lock.lock();
    try {
        AbstractDelayTask existTask = tasks.get(key);
        if (null != existTask) {
            newTask.merge(existTask);
        }
        // 將任務(wù)丟到一個Map 讓定時任務(wù)輪詢處理
        tasks.put(key, newTask);
    } finally {
        lock.unlock();
    }
    // super.addTask(key, newTask);代碼結(jié)束
    // 指標監(jiān)控
    MetricsMonitor.getDumpTaskMonitor().set(tasks.size());
}

com.alibaba.nacos.common.task.engine.NacosDelayTaskExecuteEngine#processTasks

protected void processTasks() {
    Collection<Object> keys = getAllTaskKeys();
    for (Object taskKey : keys) {
        AbstractDelayTask task = removeTask(taskKey);
        if (null == task) {
            continue;
        }
        NacosTaskProcessor processor = getProcessor(taskKey);
        if (null == processor) {
            getEngineLog().error("processor not found for task, so discarded. " + task);
            continue;
        }
        try {
            // ReAdd task if process failed 這里是具體的處理邏輯
            if (!processor.process(task)) {
                retryFailedTask(taskKey, task);
            }
        } catch (Throwable e) {
            getEngineLog().error("Nacos task execute error : " + e.toString(), e);
            retryFailedTask(taskKey, task);
        }
    }
}

com.alibaba.nacos.config.server.service.dump.processor.DumpProcessor#process

這里面的代碼就不貼了 好多 大概知道這么個流程吧

總結(jié)

磁盤緩存與內(nèi)存緩存有啥關(guān)系

他們屬于兩套生態(tài) 磁盤緩存屬于Nacos本身的,但是內(nèi)存緩存屬于Spring Cloud,Nacos可以獨立于Spring Cloud使用。

內(nèi)存緩存的位置:

com.alibaba.cloud.nacos.NacosPropertySourceRepository#NACOS_PROPERTY_SOURCE_REPOSITORY

磁盤緩存的位置:

com.alibaba.nacos.client.config.impl.LocalConfigInfoProcessor#getFailover

C:\Users\lilg\nacos\config\fixed-42.193.97.198_8848_nacos\data\config-data\DEFAULT_GROUP\nacos-config

ClientWorker初始化路徑

總結(jié)一下,路徑的鏈路從com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration#nacosConfigManager

com.alibaba.cloud.nacos.NacosConfigManager#createConfigService再到com.alibaba.nacos.api.config.ConfigFactory#createConfigService,這里面通過反射調(diào)用了com.alibaba.nacos.client.config.NacosConfigService#NacosConfigService的構(gòu)造方法,然后實例化了ClientWorker

// NacosConfigManager的構(gòu)造方法在NacosConfigBootstrapConfiguration中有調(diào)用
// com.alibaba.cloud.nacos.NacosConfigManager#NacosConfigManager
public NacosConfigManager(NacosConfigProperties nacosConfigProperties) {
    this.nacosConfigProperties = nacosConfigProperties;
    // Compatible with older code in NacosConfigProperties,It will be deleted in the future.
    createConfigService(nacosConfigProperties);
}
static ConfigService createConfigService(NacosConfigProperties nacosConfigProperties) {
    if (Objects.isNull(service)) {
        synchronized (NacosConfigManager.class) {
            try {
                if (Objects.isNull(service)) {
                    service = NacosFactory.createConfigService(
                        nacosConfigProperties.assembleConfigServiceProperties());
                }
            }
            catch (NacosException e) {
                log.error(e.getMessage());
                throw new NacosConnectionFailureException(
                    nacosConfigProperties.getServerAddr(), e.getMessage(), e);
            }
        }
    }
    return service;
}
// com.alibaba.nacos.api.config.ConfigFactory#createConfigService(java.util.Properties)
public static ConfigService createConfigService(Properties properties) throws NacosException {
    try {
        Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.config.NacosConfigService");
        Constructor constructor = driverImplClass.getConstructor(Properties.class);
        ConfigService vendorImpl = (ConfigService) constructor.newInstance(properties);
        return vendorImpl;
    } catch (Throwable e) {
        throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
    }
}
// 根據(jù)上面的反射
public NacosConfigService(Properties properties) throws NacosException {
    ValidatorUtils.checkInitParam(properties);
    String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE);
    if (StringUtils.isBlank(encodeTmp)) {
        this.encode = Constants.ENCODE;
    } else {
        this.encode = encodeTmp.trim();
    }
    initNamespace(properties);
    this.configFilterChainManager = new ConfigFilterChainManager(properties);

    this.agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
    this.agent.start();
    this.worker = new ClientWorker(this.agent, this.configFilterChainManager, properties);
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內(nèi)容