nacos-config模块学习《配置中心》

178 阅读7分钟

准备

源码链接:gitee.com/mirrors/Nac…
使用分支:v2.x-develop

核心类

NacosConfigService:配置中心核心类;
ConfigPublishRequestHandler:发布配置处理核心类;
ClientWorker:配置中心长轮询的核心类;
ConfigChangeBatchListenRequestHandler:服务端批量获取变更配置的处理类 ConfigQueryRequestHandler:服务端获取配置信息处理类

源码解读

  • 发布配置【客户端】
  1. client/src/main/java/com/alibaba/nacos/client/config/NacosConfigService.java
@Override
public boolean publishConfig(String dataId, String group, String content) throws NacosException {//发布配置入口
    return publishConfig(dataId, group, content, ConfigType.getDefaultType().getType());//※发布默认类型的配置(文本)
}

@Override
public boolean publishConfig(String dataId, String group, String content, String type) throws NacosException {
    return publishConfigInner(namespace, dataId, group, null, null, null, content, type, null);//※发布配置核心
}

private boolean publishConfigInner(String tenant, String dataId, String group, String tag, String appName,
        String betaIps, String content, String type, String casMd5) throws NacosException {
    group = blank2defaultGroup(group);
    ParamUtils.checkParam(dataId, group, content);
    //初始化配置请求
    ConfigRequest cr = new ConfigRequest();
    cr.setDataId(dataId);
    cr.setTenant(tenant);
    cr.setGroup(group);
    cr.setContent(content);
    cr.setType(type);
    configFilterChainManager.doFilter(cr, null);
    content = cr.getContent();
    String encryptedDataKey = cr.getEncryptedDataKey();

    return worker
            .publishConfig(dataId, group, tenant, appName, tag, betaIps, content, encryptedDataKey, casMd5, type);//※使用grpc发布配置
}

2.client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java

/**
 * publish config.
 *
 * @param dataId  dataId.
 * @param group   group.
 * @param tenant  tenant.
 * @param appName appName.
 * @param tag     tag.
 * @param betaIps betaIps.
 * @param content content.
 * @param casMd5  casMd5.
 * @param type    type.
 * @return success or not.
 * @throws NacosException exception throw.
 */
public boolean publishConfig(String dataId, String group, String tenant, String appName, String tag, String betaIps,
        String content, String encryptedDataKey, String casMd5, String type) throws NacosException {
    return agent.publishConfig(dataId, group, tenant, appName, tag, betaIps, content, encryptedDataKey, casMd5,
            type);
}

@Override
public boolean publishConfig(String dataId, String group, String tenant, String appName, String tag,
        String betaIps, String content, String encryptedDataKey, String casMd5, String type)
        throws NacosException {
    try {
        ConfigPublishRequest request = new ConfigPublishRequest(dataId, group, tenant, content);
        request.setCasMd5(casMd5);
        request.putAdditionalParam(TAG_PARAM, tag);
        request.putAdditionalParam(APP_NAME_PARAM, appName);
        request.putAdditionalParam(BETAIPS_PARAM, betaIps);
        request.putAdditionalParam(TYPE_PARAM, type);
        request.putAdditionalParam(ENCRYPTED_DATA_KEY_PARAM, encryptedDataKey == null ? "" : encryptedDataKey);
        ConfigPublishResponse response = (ConfigPublishResponse) requestProxy(getOneRunningClient(), request);//※使用代理请求发送配置
        if (!response.isSuccess()) {
            LOGGER.warn("[{}] [publish-single] fail, dataId={}, group={}, tenant={}, code={}, msg={}",
                    this.getName(), dataId, group, tenant, response.getErrorCode(), response.getMessage());
            return false;
        } else {
            LOGGER.info("[{}] [publish-single] ok, dataId={}, group={}, tenant={}", getName(),
                    dataId, group, tenant);
            return true;
        }
    } catch (Exception e) {
        LOGGER.warn("[{}] [publish-single] error, dataId={}, group={}, tenant={}, code={}, msg={}",
                this.getName(), dataId, group, tenant, "unknown", e.getMessage());
        return false;
    }
}

private Response requestProxy(RpcClient rpcClientInner, Request request) throws NacosException {
    return requestProxy(rpcClientInner, request, requestTimeout);
}

private Response requestProxy(RpcClient rpcClientInner, Request request, long timeoutMills)
        throws NacosException {
    try {
        request.putAllHeader(super.getSecurityHeaders(resourceBuild(request)));
        request.putAllHeader(super.getCommonHeader());
    } catch (Exception e) {
        throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
    }
    JsonObject asJsonObjectTemp = new Gson().toJsonTree(request).getAsJsonObject();
    asJsonObjectTemp.remove("headers");
    asJsonObjectTemp.remove("requestId");
    boolean limit = Limiter.isLimit(request.getClass() + asJsonObjectTemp.toString());
    if (limit) {
        throw new NacosException(NacosException.CLIENT_OVER_THRESHOLD,
                "More than client-side current limit threshold");
    }
    Response response;
    if (timeoutMills < 0) {
        response = rpcClientInner.request(request);//※将配置发送到服务端
    } else {
        response = rpcClientInner.request(request, timeoutMills);//※将配置发送到服务端
    }
    // If the 403 login operation is triggered, refresh the accessToken of the client
    if (response.getErrorCode() == ConfigQueryResponse.NO_RIGHT) {
        reLogin();
    }
    return response;
}
  • 发布配置【服务端】 1.config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigPublishRequestHandler.java

@Override
@TpsControl(pointName = "ConfigPublish")
@Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG)
@ExtractorManager.Extractor(rpcExtractor = ConfigRequestParamExtractor.class)
public ConfigPublishResponse handle(ConfigPublishRequest request, RequestMeta meta) throws NacosException {
    
    try {
        String dataId = request.getDataId();
        String group = request.getGroup();
        String content = request.getContent();
        final String tenant = NamespaceUtil.processNamespaceParameter(request.getTenant());
        
        final String srcIp = meta.getClientIp();
        final String tag = request.getAdditionParam("tag");
        final String appName = request.getAdditionParam("appName");
        final String type = request.getAdditionParam("type");
        final String srcUser = request.getAdditionParam("src_user");
        final String encryptedDataKey = request.getAdditionParam("encryptedDataKey");
        
        // check tenant
        ParamUtils.checkParam(dataId, group, "datumId", content);
        ParamUtils.checkParam(tag);
        
        ConfigForm configForm = new ConfigForm();
        configForm.setDataId(dataId);
        configForm.setGroup(group);
        configForm.setNamespaceId(tenant);
        configForm.setContent(content);
        configForm.setTag(tag);
        configForm.setAppName(appName);
        configForm.setSrcUser(srcUser);
        configForm.setConfigTags(request.getAdditionParam("config_tags"));
        configForm.setDesc(request.getAdditionParam("desc"));
        configForm.setUse(request.getAdditionParam("use"));
        configForm.setEffect(request.getAdditionParam("effect"));
        configForm.setType(type);
        configForm.setSchema(request.getAdditionParam("schema"));
        
        if (!ConfigType.isValidType(type)) {
            configForm.setType(ConfigType.getDefaultType().getType());
        }
        
        ConfigRequestInfo configRequestInfo = new ConfigRequestInfo();
        configRequestInfo.setSrcIp(srcIp);
        configRequestInfo.setSrcType(RPC);
        configRequestInfo.setRequestIpApp(meta.getLabels().get(Constants.APPNAME));
        configRequestInfo.setBetaIps(request.getAdditionParam("betaIps"));
        configRequestInfo.setCasMd5(request.getCasMd5());
        
        String encryptedDataKeyFinal = null;
        if (StringUtils.isNotBlank(encryptedDataKey)) {
            encryptedDataKeyFinal = encryptedDataKey;
        } else {
            Pair<String, String> pair = EncryptionHandler.encryptHandler(dataId, content);
            content = pair.getSecond();
            encryptedDataKeyFinal = pair.getFirst();
            configForm.setContent(content);
        }
        try {
            configOperationService.publishConfig(configForm, configRequestInfo, encryptedDataKeyFinal);//※将处理好的配置信息发布到服务端
            return ConfigPublishResponse.buildSuccessResponse();
        } catch (NacosApiException | ConfigAlreadyExistsException ex) {
            return ConfigPublishResponse.buildFailResponse(ResponseCode.FAIL.getCode(),
                    ex.getErrMsg());
        }
        
    } catch (Exception e) {
        Loggers.REMOTE_DIGEST.error("[ConfigPublishRequestHandler] publish config error ,request ={}", request, e);
        return ConfigPublishResponse.buildFailResponse(
                (e instanceof NacosException) ? ((NacosException) e).getErrCode() : ResponseCode.FAIL.getCode(),
                e.getMessage());
    }
}
  1. config/src/main/java/com/alibaba/nacos/config/server/service/ConfigOperationService.java
/**
 * Adds or updates non-aggregated data.
 *
 * @throws NacosException NacosException.
 */
public Boolean publishConfig(ConfigForm configForm, ConfigRequestInfo configRequestInfo, String encryptedDataKey) throws NacosException {
    Map<String, Object> configAdvanceInfo = getConfigAdvanceInfo(configForm);
    ParamUtils.checkParam(configAdvanceInfo);
    
    configForm.setEncryptedDataKey(encryptedDataKey);
    ConfigInfo configInfo = new ConfigInfo(configForm.getDataId(), configForm.getGroup(),
            configForm.getNamespaceId(), configForm.getAppName(), configForm.getContent());
    //set old md5
    if (StringUtils.isNotBlank(configRequestInfo.getCasMd5())) {
        configInfo.setMd5(configRequestInfo.getCasMd5());
    }
    configInfo.setType(configForm.getType());
    configInfo.setEncryptedDataKey(encryptedDataKey);
    
    ConfigOperateResult configOperateResult;
    //beta publish
    if (StringUtils.isNotBlank(configRequestInfo.getBetaIps())) {//※发布到beta表:config_info_beta
        configForm.setGrayName(BetaGrayRule.TYPE_BETA);
        configForm.setGrayRuleExp(configRequestInfo.getBetaIps());
        configForm.setGrayVersion(BetaGrayRule.VERSION);
        configGrayModelMigrateService.persistBeta(configForm, configInfo, configRequestInfo);//※使用jdbc持久化到默认数据库或第三方数据库【mysql】
        configForm.setGrayPriority(Integer.MAX_VALUE);
        publishConfigGray(BetaGrayRule.TYPE_BETA, configForm, configRequestInfo);
        return Boolean.TRUE;
    }
    // tag publish
    if (StringUtils.isNotBlank(configForm.getTag())) {//※发布到tag表:config_info_tag
        configForm.setGrayName(TagGrayRule.TYPE_TAG + "_" + configForm.getTag());
        configForm.setGrayRuleExp(configForm.getTag());
        configForm.setGrayVersion(TagGrayRule.VERSION);
        configForm.setGrayPriority(Integer.MAX_VALUE - 1);
        configGrayModelMigrateService.persistTagv1(configForm, configInfo, configRequestInfo);//※使用jdbc持久化到默认数据库或第三方数据库【mysql】
        publishConfigGray(TagGrayRule.TYPE_TAG, configForm, configRequestInfo);
        return Boolean.TRUE;
    }
    
    //formal publish
    if (StringUtils.isNotBlank(configRequestInfo.getCasMd5())) {//※发布到formal表:config_info【两种方式,一种使用使用版本号做乐观锁,一种无乐观锁】
        configOperateResult = configInfoPersistService.insertOrUpdateCas(configRequestInfo.getSrcIp(),
                configForm.getSrcUser(), configInfo, configAdvanceInfo);//※使用jdbc持久化到默认数据库或第三方数据库【mysql】
        if (!configOperateResult.isSuccess()) {
            LOGGER.warn(
                    "[cas-publish-config-fail] srcIp = {}, dataId= {}, casMd5 = {}, msg = server md5 may have changed.",
                    configRequestInfo.getSrcIp(), configForm.getDataId(), configRequestInfo.getCasMd5());
            throw new NacosApiException(HttpStatus.INTERNAL_SERVER_ERROR.value(), ErrorCode.RESOURCE_CONFLICT,
                    "Cas publish fail, server md5 may have changed.");
        }
    } else {
        if (configRequestInfo.getUpdateForExist()) {
            configOperateResult = configInfoPersistService.insertOrUpdate(configRequestInfo.getSrcIp(),
                    configForm.getSrcUser(), configInfo, configAdvanceInfo);//※使用jdbc持久化到默认数据库或第三方数据库【mysql】
        } else {
            try {
                configOperateResult = configInfoPersistService.addConfigInfo(configRequestInfo.getSrcIp(),
                        configForm.getSrcUser(), configInfo, configAdvanceInfo);//※使用jdbc持久化到默认数据库或第三方数据库【mysql】
            } catch (DataIntegrityViolationException ive) {
                LOGGER.warn("[publish-config-failed] config already exists. dataId: {}, group: {}, namespaceId: {}",
                        configForm.getDataId(), configForm.getGroup(), configForm.getNamespaceId());
                throw new ConfigAlreadyExistsException(
                        String.format("config already exist, dataId: %s, group: %s, namespaceId: %s",
                                configForm.getDataId(), configForm.getGroup(), configForm.getNamespaceId()));
            }
        }
    }
    ConfigChangePublisher.notifyConfigChange(
            new ConfigDataChangeEvent(configForm.getDataId(), configForm.getGroup(), configForm.getNamespaceId(),
                    configOperateResult.getLastModified()));
    ConfigTraceService.logPersistenceEvent(configForm.getDataId(), configForm.getGroup(),
            configForm.getNamespaceId(), configRequestInfo.getRequestIpApp(), configOperateResult.getLastModified(),
            InetUtils.getSelfIP(), ConfigTraceService.PERSISTENCE_EVENT, ConfigTraceService.PERSISTENCE_TYPE_PUB,
            configForm.getContent());
    return true;
}
  • 监听配置【客户端】
  1. client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java
public ClientWorker(final ConfigFilterChainManager configFilterChainManager, ConfigServerListManager serverListManager,
        final NacosClientProperties properties) throws NacosException {
    this.configFilterChainManager = configFilterChainManager;
    
    init(properties);
    
    agent = new ConfigRpcTransportClient(properties, serverListManager);
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(initWorkerThreadCount(properties),
            new NameThreadFactory("com.alibaba.nacos.client.Worker"));
    agent.setExecutor(executorService);
    agent.start();//※ 客户端代理开启
    
}
  1. client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigTransportClient.java
/**
 * base start client.
 */
public void start() throws NacosException {
    securityProxy.login(this.properties);
    this.executor.scheduleWithFixedDelay(() -> securityProxy.login(properties), 0,
            this.securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS);
    startInternal();//※ 开始轮询
}
  1. client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java

@Override
public void startInternal() {
    executor.schedule(() -> {
        while (!executor.isShutdown() && !executor.isTerminated()) {
            try {
                listenExecutebell.poll(5L, TimeUnit.SECONDS);
                if (executor.isShutdown() || executor.isTerminated()) {
                    continue;
                }
                executeConfigListen();//※ 执行配置监听
            } catch (Throwable e) {
                LOGGER.error("[rpc listen execute] [rpc listen] exception", e);
                try {
                    Thread.sleep(50L);
                } catch (InterruptedException interruptedException) {
                    //ignore
                }
                notifyListenConfig();
            }
        }
    }, 0L, TimeUnit.MILLISECONDS);
    
}
@Override
public void executeConfigListen() throws NacosException {

    Map<String, List<CacheData>> listenCachesMap = new HashMap<>(16);
    Map<String, List<CacheData>> removeListenCachesMap = new HashMap<>(16);
    long now = System.currentTimeMillis();
    boolean needAllSync = now - lastAllSyncTime >= ALL_SYNC_INTERNAL;
    for (CacheData cache : cacheMap.get().values()) {

        synchronized (cache) {

            checkLocalConfig(cache);

            // check local listeners consistent.
            if (cache.isConsistentWithServer()) {
                cache.checkListenerMd5();
                if (!needAllSync) {
                    continue;
                }
            }

            // If local configuration information is used, then skip the processing directly.
            if (cache.isUseLocalConfigInfo()) {
                continue;
            }

            if (!cache.isDiscard()) {
                List<CacheData> cacheDatas = listenCachesMap.computeIfAbsent(String.valueOf(cache.getTaskId()),
                        k -> new LinkedList<>());
                cacheDatas.add(cache);
            } else {
                List<CacheData> cacheDatas = removeListenCachesMap.computeIfAbsent(
                        String.valueOf(cache.getTaskId()), k -> new LinkedList<>());
                cacheDatas.add(cache);
            }
        }

    }

    //execute check listen ,return true if has change keys.
    boolean hasChangedKeys = checkListenCache(listenCachesMap);//※检查是否发生变化

    //execute check remove listen.
    checkRemoveListenCache(removeListenCachesMap);

    if (needAllSync) {
        lastAllSyncTime = now;
    }
    //If has changed keys,notify re sync md5.
    if (hasChangedKeys) {
        notifyListenConfig();
    }

}

private boolean checkListenCache(Map<String, List<CacheData>> listenCachesMap) throws NacosException {

    final AtomicBoolean hasChangedKeys = new AtomicBoolean(false);
    if (!listenCachesMap.isEmpty()) {
        List<Future> listenFutures = new ArrayList<>();
        for (Map.Entry<String, List<CacheData>> entry : listenCachesMap.entrySet()) {
            String taskId = entry.getKey();
            //获取客户端
            RpcClient rpcClient = ensureRpcClient(taskId);

            ExecutorService executorService = ensureSyncExecutor(taskId);
            Future future = executorService.submit(() -> {
                List<CacheData> listenCaches = entry.getValue();
                //reset notify change flag.
                for (CacheData cacheData : listenCaches) {
                    cacheData.getReceiveNotifyChanged().set(false);
                }
                //rpc请求:配置变化监听
                ConfigBatchListenRequest configChangeListenRequest = buildConfigRequest(listenCaches);
                configChangeListenRequest.setListen(true);
                try {
                    ConfigChangeBatchListenResponse listenResponse = (ConfigChangeBatchListenResponse) requestProxy(
                            rpcClient, configChangeListenRequest);
                    if (listenResponse != null && listenResponse.isSuccess()) {

                        Set<String> changeKeys = new HashSet<String>();
                        //变更的配置:没有变更后的信息
                        List<ConfigChangeBatchListenResponse.ConfigContext> changedConfigs = listenResponse.getChangedConfigs();
                        //handle changed keys,notify listener
                        if (!CollectionUtils.isEmpty(changedConfigs)) {
                            hasChangedKeys.set(true);
                            for (ConfigChangeBatchListenResponse.ConfigContext changeConfig : changedConfigs) {
                                String changeKey = GroupKey.getKeyTenant(changeConfig.getDataId(),
                                        changeConfig.getGroup(), changeConfig.getTenant());
                                changeKeys.add(changeKey);
                                boolean isInitializing = cacheMap.get().get(changeKey).isInitializing();
                                //※配置发生变化,刷新配置内容
                                refreshContentAndCheck(rpcClient, changeKey, !isInitializing);
                            }

                        }

                        for (CacheData cacheData : listenCaches) {
                            if (cacheData.getReceiveNotifyChanged().get()) {
                                String changeKey = GroupKey.getKeyTenant(cacheData.dataId, cacheData.group,
                                        cacheData.getTenant());
                                if (!changeKeys.contains(changeKey)) {
                                    boolean isInitializing = cacheMap.get().get(changeKey).isInitializing();
                                    refreshContentAndCheck(rpcClient, changeKey, !isInitializing);
                                }
                            }
                        }

                        //handler content configs
                        for (CacheData cacheData : listenCaches) {
                            cacheData.setInitializing(false);
                            String groupKey = GroupKey.getKeyTenant(cacheData.dataId, cacheData.group,
                                    cacheData.getTenant());
                            if (!changeKeys.contains(groupKey)) {
                                synchronized (cacheData) {
                                    if (!cacheData.getReceiveNotifyChanged().get()) {
                                        cacheData.setConsistentWithServer(true);
                                    }
                                }
                            }
                        }

                    }
                } catch (Throwable e) {
                    LOGGER.error("Execute listen config change error ", e);
                    try {
                        Thread.sleep(50L);
                    } catch (InterruptedException interruptedException) {
                        //ignore
                    }
                    notifyListenConfig();
                }
            });
            listenFutures.add(future);

        }
        for (Future future : listenFutures) {
            try {
                future.get();
            } catch (Throwable throwable) {
                LOGGER.error("Async listen config change error ", throwable);
            }
        }

    }
    return hasChangedKeys.get();
}

private void refreshContentAndCheck(RpcClient rpcClient, String groupKey, boolean notify) {
    if (cacheMap.get() != null && cacheMap.get().containsKey(groupKey)) {
        CacheData cache = cacheMap.get().get(groupKey);
        refreshContentAndCheck(rpcClient, cache, notify);//※更新本地缓存中的配置内容
    }
}

private void refreshContentAndCheck(RpcClient rpcClient, CacheData cacheData, boolean notify) {
    try {

        ConfigResponse response = this.queryConfigInner(rpcClient, cacheData.dataId, cacheData.group,
                cacheData.tenant, requestTimeout, notify);//※rpc获取最新配置内容
        cacheData.setEncryptedDataKey(response.getEncryptedDataKey());
        cacheData.setContent(response.getContent());
        if (null != response.getConfigType()) {
            cacheData.setType(response.getConfigType());
        }
        if (notify) {
            LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, type={}", agent.getName(),
                    cacheData.dataId, cacheData.group, cacheData.tenant, cacheData.getMd5(),
                    response.getConfigType());
        }
        cacheData.checkListenerMd5();
    } catch (Exception e) {
        LOGGER.error("refresh content and check md5 fail ,dataId={},group={},tenant={} ", cacheData.dataId,
                cacheData.group, cacheData.tenant, e);
    }
}

ConfigResponse queryConfigInner(RpcClient rpcClient, String dataId, String group, String tenant,
        long readTimeouts, boolean notify) throws NacosException {
    ConfigQueryRequest request = ConfigQueryRequest.build(dataId, group, tenant);
    request.putHeader(NOTIFY_HEADER, String.valueOf(notify));

    ConfigQueryResponse response = (ConfigQueryResponse) requestProxy(rpcClient, request, readTimeouts);

    ConfigResponse configResponse = new ConfigResponse();
    if (response.isSuccess()) {
        LocalConfigInfoProcessor.saveSnapshot(this.getName(), dataId, group, tenant, response.getContent());
        configResponse.setContent(response.getContent());
        String configType;
        if (StringUtils.isNotBlank(response.getContentType())) {
            configType = response.getContentType();
        } else {
            configType = ConfigType.TEXT.getType();
        }
        configResponse.setConfigType(configType);
        String encryptedDataKey = response.getEncryptedDataKey();
        LocalEncryptedDataKeyProcessor.saveEncryptDataKeySnapshot(agent.getName(), dataId, group, tenant,
                encryptedDataKey);
        configResponse.setEncryptedDataKey(encryptedDataKey);
        return configResponse;
    } else if (response.getErrorCode() == ConfigQueryResponse.CONFIG_NOT_FOUND) {
        LocalConfigInfoProcessor.saveSnapshot(this.getName(), dataId, group, tenant, null);
        LocalEncryptedDataKeyProcessor.saveEncryptDataKeySnapshot(agent.getName(), dataId, group, tenant, null);
        return configResponse;
    } else if (response.getErrorCode() == ConfigQueryResponse.CONFIG_QUERY_CONFLICT) {
        LOGGER.error(
                "[{}] [sub-server-error] get server config being modified concurrently, dataId={}, group={}, "
                        + "tenant={}", this.getName(), dataId, group, tenant);
        throw new NacosException(NacosException.CONFLICT,
                "data being modified, dataId=" + dataId + ",group=" + group + ",tenant=" + tenant);
    } else {
        LOGGER.error("[{}] [sub-server-error]  dataId={}, group={}, tenant={}, code={}", this.getName(), dataId,
                group, tenant, response);
        throw new NacosException(response.getErrorCode(),
                "http error, code=" + response.getErrorCode() + ",msg=" + response.getMessage() + ",dataId="
                        + dataId + ",group=" + group + ",tenant=" + tenant);

    }
}
  • 监听配置【服务端】
  1. config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigChangeBatchListenRequestHandler.java
@Override
@TpsControl(pointName = "ConfigListen")
@Secured(action = ActionTypes.READ, signType = SignType.CONFIG)
@ExtractorManager.Extractor(rpcExtractor = ConfigBatchListenRequestParamExtractor.class)
public ConfigChangeBatchListenResponse handle(ConfigBatchListenRequest configChangeListenRequest, RequestMeta meta)
        throws NacosException {
    String connectionId = StringPool.get(meta.getConnectionId());
    String tag = configChangeListenRequest.getHeader(Constants.VIPSERVER_TAG);
    ParamUtils.checkParam(tag);
    ConfigChangeBatchListenResponse configChangeBatchListenResponse = new ConfigChangeBatchListenResponse();
    for (ConfigBatchListenRequest.ConfigListenContext listenContext : configChangeListenRequest.getConfigListenContexts()) {
        String groupKey = GroupKey2.getKey(listenContext.getDataId(), listenContext.getGroup(),
                listenContext.getTenant());
        groupKey = StringPool.get(groupKey);
        
        String md5 = StringPool.get(listenContext.getMd5());
        
        if (configChangeListenRequest.isListen()) {
            configChangeListenContext.addListen(groupKey, md5, connectionId);
            boolean isUptoDate = ConfigCacheService.isUptodate(groupKey, md5, meta.getClientIp(), tag,
                    meta.getAppLabels());
            if (!isUptoDate) {
                //※判断服务端的md5与本地的是否一样,不一样则发生变化
                configChangeBatchListenResponse.addChangeConfig(listenContext.getDataId(), listenContext.getGroup(),
                        listenContext.getTenant());
            }
        } else {
            configChangeListenContext.removeListen(groupKey, connectionId);
        }
    }
    
    return configChangeBatchListenResponse;
    
}
  1. config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigQueryRequestHandler.java

@Override
@TpsControl(pointName = "ConfigQuery")
@Secured(action = ActionTypes.READ, signType = SignType.CONFIG)
@ExtractorManager.Extractor(rpcExtractor = ConfigRequestParamExtractor.class)
public ConfigQueryResponse handle(ConfigQueryRequest request, RequestMeta meta) throws NacosException {
    try {
        request.setTenant(NamespaceUtil.processNamespaceParameter(request.getTenant()));
        String dataId = request.getDataId();
        String group = request.getGroup();
        String tenant = request.getTenant();
        String groupKey = GroupKey2.getKey(dataId, group, tenant);
        boolean notify = request.isNotify();
        
        String requestIpApp = meta.getLabels().get(CLIENT_APPNAME_HEADER);
        String clientIp = meta.getClientIp();
        
        ConfigQueryChainRequest chainRequest = ConfigChainRequestExtractorService.getExtractor().extract(request, meta);
        ConfigQueryChainResponse chainResponse = configQueryChainService.handle(chainRequest);
        
        if (ResponseCode.FAIL.getCode() == chainResponse.getResultCode()) {
            return ConfigQueryResponse.buildFailResponse(ResponseCode.FAIL.getCode(), chainResponse.getMessage());//※ 找不到配置
        }
        
        if (chainResponse.getStatus() == ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_NOT_FOUND) {
            return handlerConfigNotFound(request.getDataId(), request.getGroup(), request.getTenant(), requestIpApp, clientIp, notify);//※配置存在冲突
        }
        
        if (chainResponse.getStatus() == ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_QUERY_CONFLICT) {
            return handlerConfigConflict(clientIp, groupKey);
        }
        
        ConfigQueryResponse response = new ConfigQueryResponse();//※ 封装配置信息
        
        // Check if there is a matched gray rule
        if (chainResponse.getStatus() == ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_FOUND_GRAY) {
            if (BetaGrayRule.TYPE_BETA.equals(chainResponse.getMatchedGray().getGrayRule().getType())) {
                response.setBeta(true);
            } else if (TagGrayRule.TYPE_TAG.equals(chainResponse.getMatchedGray().getGrayRule().getType())) {
                response.setTag(URLEncoder.encode(chainResponse.getMatchedGray().getRawGrayRule(), ENCODE_UTF8));
            }
        }
        
        // Check if there is a special tag
        if (chainResponse.getStatus() == ConfigQueryChainResponse.ConfigQueryStatus.SPECIAL_TAG_CONFIG_NOT_FOUND) {
            response.setTag(request.getTag());
        }
        
        response.setMd5(chainResponse.getMd5());
        response.setEncryptedDataKey(chainResponse.getEncryptedDataKey());
        response.setContent(chainResponse.getContent());
        response.setContentType(chainResponse.getConfigType());
        response.setLastModified(chainResponse.getLastModified());
        
        String pullType = ConfigTraceService.PULL_TYPE_OK;
        if (chainResponse.getContent() == null) {
            pullType = ConfigTraceService.PULL_TYPE_NOTFOUND;
            response.setErrorInfo(ConfigQueryResponse.CONFIG_NOT_FOUND, "config data not exist");
        } else {
            response.setResultCode(ResponseCode.SUCCESS.getCode());
        }
        
        String pullEvent = resolvePullEventType(chainResponse, request.getTag());
        LogUtil.PULL_CHECK_LOG.warn("{}|{}|{}|{}", groupKey, clientIp, response.getMd5(), TimeUtils.getCurrentTimeStr());
        final long delayed = System.currentTimeMillis() - response.getLastModified();
        ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, response.getLastModified(), pullEvent, pullType,
                delayed, clientIp, notify, "grpc");
        
        return response;
        
    } catch (Exception e) {
        LOGGER.error("Failed to handle grpc configuration query", e);
        return ConfigQueryResponse.buildFailResponse(ResponseCode.FAIL.getCode(), e.getMessage());
    }
    
}
  • 获取配置【客户端】
  1. api/src/main/java/com/alibaba/nacos/api/config/ConfigService.java
/**
 * Get config.
 *
 * @param dataId    dataId
 * @param group     group
 * @param timeoutMs read timeout
 * @return config value
 * @throws NacosException NacosException
 */
String getConfig(String dataId, String group, long timeoutMs) throws NacosException;
  1. client/src/main/java/com/alibaba/nacos/client/config/NacosConfigService.java

@Override
public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
    return getConfigInner(namespace, dataId, group, timeoutMs);
}

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);

    // We first try to use local failover content if exists.
    // A config content for failover is not created by client program automatically,
    // but is maintained by user.
    // This is designed for certain scenario like client emergency reboot,
    // changing config needed in the same time, while nacos server is down.
    String content = LocalConfigInfoProcessor.getFailover(worker.getAgentName(), dataId, group, tenant);
    if (content != null) {
        LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}",
                worker.getAgentName(), 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;
    }

    try {
        ConfigResponse response = worker.getServerConfig(dataId, group, tenant, timeoutMs, false);//※通过grpc获取配置
        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={}",
                worker.getAgentName(), dataId, group, tenant, ioe.toString());
    }

    content = LocalConfigInfoProcessor.getSnapshot(worker.getAgentName(), dataId, group, tenant);
    if (content != null) {
        LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}",
                worker.getAgentName(), dataId, group, tenant);
    }
    cr.setContent(content);
    String encryptedDataKey = LocalEncryptedDataKeyProcessor
            .getEncryptDataKeySnapshot(agent.getName(), dataId, group, tenant);
    cr.setEncryptedDataKey(encryptedDataKey);
    configFilterChainManager.doFilter(null, cr);
    content = cr.getContent();
    return content;
}
  • 获取配置【服务端】
    与监听配置【服务端】一样

一句话概括

通过grpc将配置信息发布到服务端。客户端轮询配置是否发生变化【每次查询都是批量的】,获取到变化配置后,单个获取具体的配置内容更新到客户端缓存。
一天一捏捏,接着扒拉~~~