Spring Cloud Nacos(二)--Nacos客户端的数据的加载流程

Nacos客户端的数据的加载流程

Nacos的外部化配置的实现? NacosPropertySourceLocator。

  • Spring cloud Nacos配置的加载

  • Spring Cloud Nacos配置变更

  • @RefreshScope - Spring Cloud中提供的能力,在Spring中只提供了@Scope

  • Nacos Confi core

    • 客户端配置的加载
    • 客户端配置的动态刷新
    • 客户端配置的本地快照
    • 服务端配置存储(存储到内存/数据库)
    • 数据动态变更比较(客户端对请求的数据进行分片,通过本地比较+探测+最终服务端数据的获取)
    • 数据同步,基于广摇的方式发送数据变更事件

配置数据的最终加载,是基于configService.getConfig,Nacos提供的SDK来实现的。

String getConfig(String dataId, String group, long timeoutMs) throws NacosException;

关于Nacos SDK的使用教程: https://nacos.io/zh-cn/docs/sdk.html

NacosConfigService.getConfig

 @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,如果为空,则为default-group
    group = blank2defaultGroup(group);
    //验证请求参数
    ParamUtils.checkKeyParam(dataId, group);
    //设置响应结果
    ConfigResponse cr = new ConfigResponse();

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

    // 优先使用本地配置
    String content = LocalConfigInfoProcessor.getFailover(worker.getAgentName(), dataId, group, tenant);
    //如果本地缓存中的内容不为空
    if (content != null) {
        LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}, config={}",
                    worker.getAgentName(), dataId, group, tenant, ContentUtils.truncateContent(content));
        //把内容设置到cr中,响应结果中。
        cr.setContent(content);
        //获取容灾配置的encryptedDataKey
        String encryptedDataKey = LocalEncryptedDataKeyProcessor
            .getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);
         //保存到cr
        cr.setEncryptedDataKey(encryptedDataKey);
        //执行过滤(目前好像没有实现)
        configFilterChainManager.doFilter(null, cr);
        //返回文件content
        content = cr.getContent();
        return content;
    }

    //如果本地文件中不存在相关内容,则发起远程调用
    try {
        ConfigResponse response = worker.getServerConfig(dataId, group, tenant, timeoutMs, false);
        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) {
        //如果出现NacosException,且不是403异常,则尝试通过本地的快照文件去获取配置进行返回。
        LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}",
                    worker.getAgentName(), dataId, group, tenant, ContentUtils.truncateContent(content));
    }
    cr.setContent(content);
    String encryptedDataKey = LocalEncryptedDataKeyProcessor
        .getEncryptDataKeySnapshot(agent.getName(), dataId, group, tenant);
    cr.setEncryptedDataKey(encryptedDataKey);
    configFilterChainManager.doFilter(null, cr);
    content = cr.getContent();
    return content;
}

从本地缓存读取配置

默认情况下,nacos先从本地缓存的配置中读取文件:

C:\Users\snail\nacos\config\fixed-121.5.102.117_8848_nacos

如果本地缓存内容存在,则返回内容数据,否则返回空值。

public static String getFailover(String serverName, String dataId, String group, String tenant) {
    File localPath = getFailoverFile(serverName, dataId, group, tenant);
    if (!localPath.exists() || !localPath.isFile()) {
        return null;
    }

    try {
        return readFile(localPath);
    } catch (IOException ioe) {
        LOGGER.error("[" + serverName + "] get failover error, " + localPath, ioe);
        return null;
    }
}

从指定文件目录下读取文件内容。

static File getFailoverFile(String serverName, String dataId, String group, String tenant) {
    File tmp = new File(LOCAL_SNAPSHOT_PATH, serverName + SUFFIX);
    tmp = new File(tmp, FAILOVER_FILE_CHILD_1);
    if (StringUtils.isBlank(tenant)) {
        tmp = new File(tmp, FAILOVER_FILE_CHILD_2);
    } else {
        tmp = new File(tmp, FAILOVER_FILE_CHILD_3);
        tmp = new File(tmp, tenant);
    }
    return new File(new File(tmp, group), dataId);
}

ClientWorker.getServerConfig

ClientWorker,表示客户端的一个工作类,它负责和服务端交互。

public ConfigResponse getServerConfig(String dataId, String group, String tenant, long readTimeout, boolean notify)
    throws NacosException {
    if (StringUtils.isBlank(group)) {
        group = Constants.DEFAULT_GROUP;
    }
    return this.agent.queryConfig(dataId, group, tenant, readTimeout, notify);
}  


  @Override
public ConfigResponse queryConfig(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));
    //构建一个grpc
    RpcClient rpcClient = getOneRunningClient();
    if (notify) {
        CacheData cacheData = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));
        if (cacheData != null) {
            rpcClient = ensureRpcClient(String.valueOf(cacheData.getTaskId()));
        }
    }
    //发起远程调用
    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());
        //配置文件的类型,如text、json、yaml等
        String configType;
        if (StringUtils.isNotBlank(response.getContentType())) {
            configType = response.getContentType();
        } else {
            configType = ConfigType.TEXT.getType();
        }
        //设置到configResponse中,后续要根据文件类型实现不同解析策略
        configResponse.setConfigType(configType);
        //获取加密数据的key
        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);

    }
}

ClientWorker的getServerConfig,这里的代码和nacos1.x已经完全不同了,nacos1.x是直接使用http请求来实现获取远程配置的,nacos2.0采用了grpc来实现客户端与服务端的交互

  • nacos1.x
 @Override
public HttpRestResult<String> httpGet(String path, Map<String, String> headers, Map<String, String> paramValues,
                                      String encode, long readTimeoutMs) throws Exception {
    final long endTime = System.currentTimeMillis() + readTimeoutMs;
    injectSecurityInfo(paramValues); //注入安全信息
    //获取当前服务器地址
    String currentServerAddr = serverListMgr.getCurrentServerAddr();
    //获取最大重试次数,默认3次
    int maxRetry = this.maxRetry;
    //配置HttpClient的属性,默认的readTimeOut超时时间是3s
    HttpClientConfig httpConfig = HttpClientConfig.builder()
        .setReadTimeOutMillis(Long.valueOf(readTimeoutMs).intValue())
.setConTimeOutMillis(ConfigHttpClientManager.getInstance().getConnectTimeoutOrDefault(100)).build();
    do {
        try {
            //设置header
            Header newHeaders = getSpasHeaders(paramValues, encode);
            if (headers != null) {
                newHeaders.addAll(headers);
            }
            //构建query查询条件
            Query query = Query.newInstance().initParams(paramValues);
            //发起http请求
            HttpRestResult<String> result = NACOS_RESTTEMPLATE
                .get(getUrl(currentServerAddr, path), httpConfig, newHeaders, query, String.class);
             //如果请求失败
            if (isFail(result)) {
                LOGGER.error("[NACOS ConnectException] currentServerAddr: {}, httpCode: {}",
                             serverListMgr.getCurrentServerAddr(), result.getCode());
            } else {
                // Update the currently available server addr
                serverListMgr.updateCurrentServerAddr(currentServerAddr);
                return result;
            }
        } catch (ConnectException connectException) {
            LOGGER.error("[NACOS ConnectException httpGet] currentServerAddr:{}, err : {}",
                         serverListMgr.getCurrentServerAddr(), connectException.getMessage());
        } catch (SocketTimeoutException socketTimeoutException) {
            LOGGER.error("[NACOS SocketTimeoutException httpGet] currentServerAddr:{}, err : {}",
                         serverListMgr.getCurrentServerAddr(), socketTimeoutException.getMessage());
        } catch (Exception ex) {
            LOGGER.error("[NACOS Exception httpGet] currentServerAddr: " + serverListMgr.getCurrentServerAddr(),
                         ex);
            throw ex;
        }
		//如果服务端列表有多个,并且当前请求失败,则尝试用下一个地址进行重试
        if (serverListMgr.getIterator().hasNext()) {
            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");
}
  • nacos2.x
 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");
     }
     return rpcClientInner.request(request, timeoutMills);
 }

public Response request(Request request, long timeoutMills) throws NacosException {
    int retryTimes = 0;
    Response response;
    Exception exceptionThrow = null;
    long start = System.currentTimeMillis();
    //获取最大重试次数,默认3次
    while (retryTimes < RETRY_TIMES && System.currentTimeMillis() < timeoutMills + start) {
        boolean waitReconnect = false;
        try {
            if (this.currentConnection == null || !isRunning()) {
                waitReconnect = true;
                throw new NacosException(NacosException.CLIENT_DISCONNECT,
                                         "Client not connected, current status:" + rpcClientStatus.get());
            }
            //GrpcConnection
            response = this.currentConnection.request(request, timeoutMills);
            if (response == null) {
                throw new NacosException(SERVER_ERROR, "Unknown Exception.");
            }
            if (response instanceof ErrorResponse) {
                if (response.getErrorCode() == NacosException.UN_REGISTER) {
                    synchronized (this) {
                        waitReconnect = true;
                        if (rpcClientStatus.compareAndSet(RpcClientStatus.RUNNING, RpcClientStatus.UNHEALTHY)) {
                            LoggerUtils.printIfErrorEnabled(LOGGER,
                                                            "Connection is unregistered, switch server, connectionId = {}, request = {}",
                                                            currentConnection.getConnectionId(), request.getClass().getSimpleName());
                            switchServerAsync();
                        }
                    }

                }
                throw new NacosException(response.getErrorCode(), response.getMessage());
            }
            // return response.
            lastActiveTimeStamp = System.currentTimeMillis();
            return response;

        } catch (Exception e) {
            if (waitReconnect) {
                try {
                    // wait client to reconnect.
                    Thread.sleep(Math.min(100, timeoutMills / 3));
                } catch (Exception exception) {
                    // Do nothing.
                }
            }

            LoggerUtils.printIfErrorEnabled(LOGGER, "Send request fail, request = {}, retryTimes = {}, errorMessage = {}", request, retryTimes, e.getMessage());

            exceptionThrow = e;

        }
        retryTimes++;

    }

    if (rpcClientStatus.compareAndSet(RpcClientStatus.RUNNING, RpcClientStatus.UNHEALTHY)) {
        switchServerAsyncOnRequestFail();
    }

    if (exceptionThrow != null) {
        throw (exceptionThrow instanceof NacosException) ? (NacosException) exceptionThrow
            : new NacosException(SERVER_ERROR, exceptionThrow);
    } else {
        throw new NacosException(SERVER_ERROR, "Request fail, unknown Error");
    }
}

Nacos Server端的配置获取

客户端向服务端加载配置,调用的接口是:/nacos/v1/cs/configs , 于是,在Nacos的源码中找到该接口

定位到Nacos源码中的ConfigController.getConfig中的方法,代码如下:

@GetMapping
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public void getConfig(HttpServletRequest request, HttpServletResponse response,
                      @RequestParam("dataId") String dataId, @RequestParam("group") String group,
                      @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant,
                      @RequestParam(value = "tag", required = false) String tag)
    throws IOException, ServletException, NacosException {
    // check tenant
    ParamUtils.checkTenant(tenant);
    //租户,也就是namespaceid
    tenant = NamespaceUtil.processNamespaceParameter(tenant);
    // check params
    //检查请求参数是否为空
    ParamUtils.checkParam(dataId, group, "datumId", "content");
    ParamUtils.checkParam(tag);

    //获取请求的ip
    final String clientIp = RequestUtil.getRemoteIp(request);
     //加载配置
    inner.doGetConfig(request, response, dataId, group, tenant, tag, clientIp);
}

inner.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);
    //尝试获取当前请求配置的读锁(避免读写冲突)
    int lockResult = tryConfigReadLock(groupKey);
    //请求端的ip
    final String requestIp = RequestUtil.getRemoteIp(request);
    boolean isBeta = false;
    //lockResult>0 ,表示CacheItem(也就是缓存的配置项)不为空,并且已经加了读锁,意味着这个缓存数据不能被删除。
    //lockResult=0 ,表示cacheItem为空,不需要加读锁
    //lockResult=-1 , 表示加锁失败,存在冲突。
    if (lockResult > 0) {
        // LockResult > 0 means cacheItem is not null and other thread can`t delete this cacheItem
        FileInputStream fis = null;
        try {
            String md5 = Constants.NULL;
            long lastModified = 0L;
            //从本地缓存中,根据groupKey获取CacheItem
            CacheItem cacheItem = ConfigCacheService.getContentCache(groupKey);
            //判断是否是beta发布,也就是测试版本
            if (cacheItem.isBeta() && cacheItem.getIps4Beta().contains(clientIp)) {
                isBeta = true;
            }

            //获取配置文件的类型
            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) {
                md5 = cacheItem.getMd54Beta();
                lastModified = cacheItem.getLastModifiedTs4Beta();
                if (PropertyUtil.isDirectRead()) {
                    configInfoBase = persistService.findConfigInfo4Beta(dataId, group, tenant);
                } else {
                     //从磁盘中获取文件,得到的是一个完整的File
                    file = DiskUtil.targetBetaFile(dataId, group, tenant);
                }
                response.setHeader("isBeta", "true");
            } else {
                //判断tag标签是否为空,tag对应的是nacos配置中心的标签选项
                if (StringUtils.isBlank(tag)) {
                    if (isUseTag(cacheItem, autoTag)) {
                        if (cacheItem.tagMd5 != null) {
                            md5 = cacheItem.tagMd5.get(autoTag);
                        }
                        if (cacheItem.tagLastModifiedTs != null) {
                            lastModified = cacheItem.tagLastModifiedTs.get(autoTag);
                        }
                        if (PropertyUtil.isDirectRead()) {
                            configInfoBase = persistService.findConfigInfo4Tag(dataId, group, tenant, autoTag);
                        } else {
                            file = DiskUtil.targetTagFile(dataId, group, tenant, autoTag);
                        }

                        response.setHeader("Vipserver-Tag",
                                           URLEncoder.encode(autoTag, StandardCharsets.UTF_8.displayName()));
                    //直接走这个逻辑(默认不会配置tag属性)
                    } else {
                        //获取缓存的md5
                        md5 = cacheItem.getMd5();
                        //获取最后更新时间
                        lastModified = cacheItem.getLastModifiedTs();
                        //判断是否是stamdalone模式且使用的是derby数据库,如果是,则从derby数据库加载数据
                        if (PropertyUtil.isDirectRead()) {
                            configInfoBase = persistService.findConfigInfo(dataId, group, tenant);
                        } else {
                            //否则,如果是数据库或者集群模式,先从本地磁盘得到文件
                            file = DiskUtil.targetFile(dataId, group, tenant);
                        }
                        //如果本地磁盘文件为空,并且configInfoBase为空,则表示配置数据不存在,直接返回null
                        if (configInfoBase == null && fileNotExist(file)) {
                            // FIXME CacheItem
                            // No longer exists. It is impossible to simply calculate the push delayed. Here, simply record it as - 1.
                            ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1,
                                                            ConfigTraceService.PULL_EVENT_NOTFOUND, -1, requestIp);

                            // pullLog.info("[client-get] clientIp={}, {},
                            // no data",
                            // new Object[]{clientIp, groupKey});

                            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                            response.getWriter().println("config data not exist");
                            return HttpServletResponse.SC_NOT_FOUND + "";
                        }
                    }
                } else {
                    //如果tag不为空,说明配置文件设置了tag标签
                    if (cacheItem.tagMd5 != null) {
                        md5 = cacheItem.tagMd5.get(tag);
                    }
                    if (cacheItem.tagLastModifiedTs != null) {
                        Long lm = cacheItem.tagLastModifiedTs.get(tag);
                        if (lm != null) {
                            lastModified = lm;
                        }
                    }
                    if (PropertyUtil.isDirectRead()) {
                        configInfoBase = persistService.findConfigInfo4Tag(dataId, group, tenant, tag);
                    } else {
                        file = DiskUtil.targetTagFile(dataId, group, tenant, tag);
                    }
                    if (configInfoBase == null && fileNotExist(file)) {
                        // FIXME CacheItem
                        // No longer exists. It is impossible to simply calculate the push delayed. Here, simply record it as - 1.
                        ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1,
                                                        ConfigTraceService.PULL_EVENT_NOTFOUND, -1, requestIp);

                        // pullLog.info("[client-get] clientIp={}, {},
                        // no data",
                        // new Object[]{clientIp, groupKey});

                        response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                        response.getWriter().println("config data not exist");
                        return HttpServletResponse.SC_NOT_FOUND + "";
                    }
                }
            }
			//把获取的数据结果设置到response中返回
            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();
             //否则,通过trasferTo
            } else {
                fis.getChannel()
                    .transferTo(0L, fis.getChannel().size(), Channels.newChannel(response.getOutputStream()));
            }

            LogUtil.PULL_CHECK_LOG.warn("{}|{}|{}|{}", groupKey, requestIp, md5, TimeUtils.getCurrentTimeStr());

            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);
        }
     //说明缓存为空
    } else if (lockResult == 0) {

        // FIXME CacheItem No longer exists. It is impossible to simply calculate the push delayed. Here, simply record it as - 1.
        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 {

        PULL_LOG.info("[client-get] clientIp={}, {}, get data during dump", clientIp, groupKey);

        response.setStatus(HttpServletResponse.SC_CONFLICT);
        response.getWriter().println("requested file is being modified, please try later.");
        return HttpServletResponse.SC_CONFLICT + "";

    }

    return HttpServletResponse.SC_OK + "";
}

persistService.findConfigInfo

从derby数据库中获取数据内容,这个就是一个基本的数据查询操作。

 @Override
public ConfigInfo findConfigInfo(final String dataId, final String group, final String tenant) {
    final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
    final String sql = "SELECT ID,data_id,group_id,tenant_id,app_name,content,md5,type FROM config_info "
        + " WHERE data_id=? AND group_id=? AND tenant_id=?";
    final Object[] args = new Object[] {dataId, group, tenantTmp};
    return databaseOperate.queryOne(sql, args, CONFIG_INFO_ROW_MAPPER);

}

DiskUtil.targetFile

从磁盘目录中获取目标文件,直接根据dataId/group/tenant ,查找指定目录下的文件即可

public static File targetFile(String dataId, String group, String tenant) {
    File file = null;
    if (StringUtils.isBlank(tenant)) {
        file = new File(EnvUtil.getNacosHome(), BASE_DIR);
    } else {
        file = new File(EnvUtil.getNacosHome(), TENANT_BASE_DIR);
        file = new File(file, tenant);
    }
    file = new File(file, group);
    file = new File(file, dataId);
    return file;
}

客户端配置缓存更新

当客户端拿到配置后,需要动态刷新,从而保证数据和服务器端是一致的,这个过程是如何实现的呢?

Nacos采用长轮训机制来实现数据变更的同步,原理如下:

整体工作流程如下:

  • 客户端发起长轮训请求

  • 服务端收到请求以后,先比较服务端缓存中的数据是否相同,如果不同,则直接返回

  • 如果相同,则通过schedule延迟29.5s之后再执行比较

  • 为了保证当服务端在29.5s之内发生数据变化能够及时通知给客户端,服务端采用事件订阅的方式来监听服务端本地数据变化的事件,一旦收到事件,则触发DataChangeTask的通知,并且遍历allStubs队列中的ClientLongPolling,把结果写回到客户端,就完成了一次数据的推送

  • 如果 DataChangeTask 任务完成了数据的 “推送” 之后,ClientLongPolling 中的调度任务又开始执行了怎么办呢? 很简单,只要在进行 “推送” 操作之前,先将原来等待执行的调度任务取消掉就可以了,这样就防止了推送操作写完响应数据之后,调度任务又去写响应数据,这时肯定会报错的。所以,在ClientLongPolling方法中,最开始的一个步骤就是删除订阅事件。

posted @ 2022-10-16 15:42  snail灬  阅读(815)  评论(0编辑  收藏  举报