nacos-config工作原理解析

本文解析了nacos配置中心的工作原理,从java SDK入手,详细介绍了通过getConfig()方法直接获取配置信息以及通过receiveConfigInfo()监听回调获取配置的流程。在服务端,nacos处理长连接请求,判断数据变化并及时响应客户端更新。

nacos-config工作原理解析

说明:本文是基于nacos-1.1.0版本进行解读,请注意区分。

1. 什么是nacos?

nacos官方手册上对nacos的描述比较权威,这里不再赘述,手册地址https://nacos.io/zh-cn/docs/what-is-nacos.html

2. java的sdk

对工作原理的分析是从sdk入手的,如果你没有直接使用过sdk进行编程,建议先熟悉一下,具体可参考官方手册https://nacos.io/zh-cn/docs/sdk.html

3. nacos的工作流程

3.1 getConfig()方法直接获取配置信息

在这里插入图片描述
通过这种方式获取配置信息,工作流程比较简单,客户端每次发送http GET请求到server端获取最新的配置信息。

3.2 通过receiveConfigInfo()从监听中回调获取

在这里插入图片描述

3.2.1 源码解读
// sdk编程时的主入口
public static ConfigService createConfigService(Properties properties) throws NacosException {
    return ConfigFactory.createConfigService(properties);
}
public static ConfigService createConfigService(Properties properties) throws NacosException {
    try {
    	// 这里是通过反射创建一个NacosConfigService对象并返回
    	// 因此,我们sdk编程的时候其实最终使用的是这个类的实例
        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);
    }
}
public NacosConfigService(Properties properties) throws NacosException {
    String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE);
    if (StringUtils.isBlank(encodeTmp)) {
        encode = Constants.ENCODE;
    } else {
        encode = encodeTmp.trim();
    }
    // 这里是初始化命名空间
    initNamespace(properties);
    // 初始化http agent,后续与server端通信会用到
    agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
    agent.start();
    // 这里是客户端工作的核心,接下来我们要重点关注
    worker = new ClientWorker(agent, configFilterChainManager, properties);
}
// ClientWorker里面主要是初始化两个线程池
// executor线程池中延迟10ms启动了一个线程去执行checkConfigInfo()方法
public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager, final Properties properties) {
    this.agent = agent;
    this.configFilterChainManager = configFilterChainManager;

    // Initialize the timeout parameter

    init(properties);

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

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

    executor.scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
            try {
            	// 这个方法是客户端的处理核心
            	// 每隔10ms就会调度一次这个方法
                checkConfigInfo();
            } catch (Throwable e) {
                LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
            }
        }
    }, 1L, 10L, TimeUnit.MILLISECONDS);
}
// 这里是采用分批的方式防止一次请求数据过大导致失败
public void checkConfigInfo() {
    // 分任务
    int listenerSize = cacheMap.get().size();
    // 向上取整为批数
    int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());
    if (longingTaskCount > currentLongingTaskCount) {
        for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {
            // 要判断任务是否在执行 这块需要好好想想。 任务列表现在是无序的。变化过程可能有问题
            executorService.execute(new LongPollingRunnable(i));
        }
        currentLongingTaskCount = longingTaskCount;
    }
}
class LongPollingRunnable implements Runnable {
        private int taskId;

        public LongPollingRunnable(int taskId) {
            this.taskId = taskId;
        }

        @Override
        public void run() {

            List<CacheData> cacheDatas = new ArrayList<CacheData>();
            List<String> inInitializingCacheList = new ArrayList<String>();
            try {
                // check failover config
                // cacheMap:增加监听时,将具体的监听对应的data封装成CacheData缓存进cacheMap
                // 在封装CacheData时,会计算出CacheData对应的taskId
                for (CacheData cacheData : cacheMap.get().values()) {
                    if (cacheData.getTaskId() == taskId) {
                        cacheDatas.add(cacheData);
                        try {
                        	// 检查内存中的缓存信息与文件中的缓存信息是否一致,并进行相应处理
                            checkLocalConfig(cacheData);
                            if (cacheData.isUseLocalConfigInfo()) {
                            	// 监听对象创建时,会在对象中缓存一个当前的信息值
                            	// 判断监听的信息是否好内存中缓存的一致,不一致就回调监听中的回调的方法
                                cacheData.checkListenerMd5();
                            }
                        } catch (Exception e) {
                            LOGGER.error("get local config info error", e);
                        }
                    }
                }

                // check server config
                // 这里就是大名鼎鼎的长轮询
                // 通过长轮询的方式,从服务端获取data变化的dataId
                // 默认是30s超时
                List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);

				// 获得变化的dataId清单之后,循环从server端查询最新的配置信息,并更新到本地的缓存文件中
                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 {
                        String content = getServerConfig(dataId, group, tenant, 3000L);
                        CacheData cache = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));
                        cache.setContent(content);
                        LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}",
                            agent.getName(), dataId, group, tenant, cache.getMd5(),
                            ContentUtils.truncateContent(content));
                    } catch (NacosException ioe) {
                        String message = String.format(
                            "[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s",
                            agent.getName(), dataId, group, tenant);
                        LOGGER.error(message, 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();

                executorService.execute(this);

            } catch (Throwable e) {

                // If the rotation training task is abnormal, the next execution time of the task will be punished
                LOGGER.error("longPolling error : ", e);
                executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS);
            }
        }
    }
List<String> checkUpdateDataIds(List<CacheData> cacheDatas, List<String> inInitializingCacheList) throws IOException {
    // 拼接约定格式的参数字符串,服务端会按照约定的格式进行解析
    // 会将group,dataId,content拼接到参数中
    StringBuilder sb = new StringBuilder();
    for (CacheData cacheData : cacheDatas) {
        if (!cacheData.isUseLocalConfigInfo()) {
            sb.append(cacheData.dataId).append(WORD_SEPARATOR);
            sb.append(cacheData.group).append(WORD_SEPARATOR);
            if (StringUtils.isBlank(cacheData.tenant)) {
                sb.append(cacheData.getMd5()).append(LINE_SEPARATOR);
            } else {
                sb.append(cacheData.getMd5()).append(WORD_SEPARATOR);
                sb.append(cacheData.getTenant()).append(LINE_SEPARATOR);
            }
            if (cacheData.isInitializing()) {
                // cacheData 首次出现在cacheMap中&首次check更新
                inInitializingCacheList
                    .add(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant));
            }
        }
    }
    boolean isInitializingCacheList = !inInitializingCacheList.isEmpty();
    return checkUpdateConfigStr(sb.toString(), isInitializingCacheList);
}
List<String> checkUpdateConfigStr(String probeUpdateString, boolean isInitializingCacheList) throws IOException {

    List<String> params = Arrays.asList(Constants.PROBE_MODIFY_REQUEST, probeUpdateString);

    List<String> headers = new ArrayList<String>(2);
    headers.add("Long-Pulling-Timeout");
    headers.add("" + timeout);

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

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

    try {
        // 这里的调用就是所谓的长轮询
        // url:   http://ip:port/v1/cs/configs/listener
        // 超时时间是默认值30s
        HttpResult result = agent.httpPost(Constants.CONFIG_CONTROLLER_PATH + "/listener", headers, params,
            agent.getEncode(), timeout);

        if (HttpURLConnection.HTTP_OK == result.code) {
            setHealthServer(true);
            // 将返回的dataId清单进行解析并返回到上层
            return parseUpdateDataIdResponse(result.content);
        } else {
            setHealthServer(false);
            LOGGER.error("[{}] [check-update] get changed dataId error, code: {}", agent.getName(), result.code);
        }
    } catch (IOException e) {
        setHealthServer(false);
        LOGGER.error("[" + agent.getName() + "] [check-update] get changed dataId exception", e);
        throw e;
    }
    return Collections.emptyList();
}

以上是客户端的处理逻辑,我们来看一下服务端收到请求后的处理逻辑

@RequestMapping(value = "/listener", method = RequestMethod.POST)
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());
}
public String doPollingConfig(HttpServletRequest request, HttpServletResponse response,
                                  Map<String, String> clientMd5Map, int probeRequestSize)
        throws IOException, ServletException {

    // 长轮询
    if (LongPollingService.isSupportLongPolling(request)) {
    	// 这里就是真正进行处理的入口了,下面我们重点分析这一部分逻辑
        longPollingService.addLongPollingClient(request, response, clientMd5Map, probeRequestSize);
        return HttpServletResponse.SC_OK + "";
    }
    
    // 下面的逻辑可以忽略了

    // else 兼容短轮询逻辑
    List<String> changedGroups = MD5Util.compareMd5(request, response, clientMd5Map);

    // 兼容短轮询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);

    /**
     * 2.0.4版本以前, 返回值放入header中
     */
    if (versionNum < START_LONGPOLLING_VERSION_NUM) {
        response.addHeader(Constants.PROBE_MODIFY_RESPONSE, oldResult);
        response.addHeader(Constants.PROBE_MODIFY_RESPONSE_NEW, newResult);
    } else {
        request.setAttribute("content", newResult);
    }

    // 禁用缓存
    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 + "";
}
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);
    /**
     * 提前500ms返回响应,为避免客户端超时 @qiaoyi.dingqy 2013.10.22改动  add delay time for LoadBalance
     */
    long timeout = Math.max(10000, Long.parseLong(str) - delayTime);
    if (isFixedPolling()) {
        timeout = Math.max(10000, getFixedPollingInterval());
        // do nothing but set fix polling timeout
    } else {
        long start = System.currentTimeMillis();
        // 比较客户端的md5与当前server端的是否一致,不一致的返回到changedGroups
        List<String> changedGroups = MD5Util.compareMd5(req, rsp, clientMd5Map);
        // 当前有MD5不一致的,生成响应信息直接返回
        if (changedGroups.size() > 0) {
            generateResponse(req, rsp, changedGroups);
            LogUtil.clientLog.info("{}|{}|{}|{}|{}|{}|{}",
                System.currentTimeMillis() - start, "instant", RequestUtil.getRemoteIp(req), "polling",
                clientMd5Map.size(), probeRequestSize, changedGroups.size());
            return;
        }
        // 当前无MD5不一致的,判断客户端传过来的Long-Pulling-Timeout-No-Hangup是否为true
        // 如果为true,不需要Hangup,直接返回
        // 客户端只在首次查询时,才会将此值设置为true,之后的查询都需要进行Hangup处理
        else if (noHangUpFlag != null && noHangUpFlag.equalsIgnoreCase(TRUE_STR)) {
            LogUtil.clientLog.info("{}|{}|{}|{}|{}|{}|{}", System.currentTimeMillis() - start, "nohangup",
                RequestUtil.getRemoteIp(req), "polling", clientMd5Map.size(), probeRequestSize,
                changedGroups.size());
            return;
        }
    }
    String ip = RequestUtil.getRemoteIp(req);
    // 一定要由HTTP线程调用,否则离开后容器会立即发送响应
    final AsyncContext asyncContext = req.startAsync();
    // AsyncContext.setTimeout()的超时时间不准,所以只能自己控制
    asyncContext.setTimeout(0L);

    // 执行长轮询的线程
    scheduler.execute(
        new ClientLongPolling(asyncContext, clientMd5Map, ip, probeRequestSize, timeout, appName, tag));
}
@Override
public void run() {
    asyncTimeoutFuture = scheduler.schedule(new Runnable() {
        @Override
        public void run() {
            try {
                getRetainIps().put(ClientLongPolling.this.ip, System.currentTimeMillis());

                // 到达超时时间,当前ClientLongPolling不需要再维持订阅关系
                /**
                 * 删除订阅关系
                 */
                allSubs.remove(ClientLongPolling.this);

                // 默认不会走该分支
                if (isFixedPolling()) {
                    LogUtil.clientLog.info("{}|{}|{}|{}|{}|{}",
                        (System.currentTimeMillis() - createTime),
                        "fix", RequestUtil.getRemoteIp((HttpServletRequest)asyncContext.getRequest()),
                        "polling",
                        clientMd5Map.size(), probeRequestSize);
                    List<String> changedGroups = MD5Util.compareMd5(
                        (HttpServletRequest)asyncContext.getRequest(),
                        (HttpServletResponse)asyncContext.getResponse(), clientMd5Map);
                    if (changedGroups.size() > 0) {
                        sendResponse(changedGroups);
                    } else {
                        sendResponse(null);
                    }
                } else {
                    LogUtil.clientLog.info("{}|{}|{}|{}|{}|{}",
                        (System.currentTimeMillis() - createTime),
                        "timeout", RequestUtil.getRemoteIp((HttpServletRequest)asyncContext.getRequest()),
                        "polling",
                        clientMd5Map.size(), probeRequestSize);
                    // 到达超时时间,结束Hangup,生成响应信息并返回
                    sendResponse(null);
                }
            } catch (Throwable t) {
                LogUtil.defaultLog.error("long polling error:" + t.getMessage(), t.getCause());
            }

        }

    }, timeoutTime, TimeUnit.MILLISECONDS);

    // 上面定义的任务会延迟29.5s执行,定义完成之后,会执行到这里,将当前ClientLongPolling添加到订阅者队列中
    // 这个队列是在什么时候被初始化的呢,它是什么结构呢,下面我们去看看
    allSubs.add(this);
}
public LongPollingService() {

    // 会隐式的调用父类AbstractEventListener中的构造方法
    //        public AbstractEventListener() {
    //            /**
    //             * automatic register
    //             */
    //            EventDispatcher.addEventListener(this);
    //        }

    // 监听的对象会被注册到这里,当监听的事件被触发时,会遍历这里的监听者,进行相应的处理
    // DataChangeTask 中会对allSubs进行遍历处理
    // DataChangeTask 是何时被启动的呢,我们发现onEvent()方法中会调度DataChangeTask 线程
    allSubs = new ConcurrentLinkedQueue<ClientLongPolling>();

    scheduler = Executors.newScheduledThreadPool(1, new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setDaemon(true);
            t.setName("com.alibaba.nacos.LongPolling");
            return t;
        }
    });
    scheduler.scheduleWithFixedDelay(new StatTask(), 0L, 10L, TimeUnit.SECONDS);
}
class DataChangeTask implements Runnable {
    @Override
    public void run() {
        try {
            ConfigService.getContentBetaMd5(groupKey);
            for (Iterator<ClientLongPolling> iter = allSubs.iterator(); iter.hasNext(); ) {
                ClientLongPolling clientSub = iter.next();
                if (clientSub.clientMd5Map.containsKey(groupKey)) {
                    // 如果beta发布且不在beta列表直接跳过
                    if (isBeta && !betaIps.contains(clientSub.ip)) {
                        continue;
                    }

                    // 如果tag发布且不在tag列表直接跳过
                    if (StringUtils.isNotBlank(tag) && !tag.equals(clientSub.tag)) {
                        continue;
                    }

                    getRetainIps().put(clientSub.ip, System.currentTimeMillis());
                    iter.remove(); // 删除订阅关系
                    LogUtil.clientLog.info("{}|{}|{}|{}|{}|{}|{}",
                        (System.currentTimeMillis() - changeTime),
                        "in-advance",
                        RequestUtil.getRemoteIp((HttpServletRequest)clientSub.asyncContext.getRequest()),
                        "polling",
                        clientSub.clientMd5Map.size(), clientSub.probeRequestSize, groupKey);
                    clientSub.sendResponse(Arrays.asList(groupKey));
                }
            }
        } catch (Throwable t) {
            LogUtil.defaultLog.error("data change error:" + t.getMessage(), t.getCause());
        }
    }

    DataChangeTask(String groupKey) {
        this(groupKey, false, null);
    }

    DataChangeTask(String groupKey, boolean isBeta, List<String> betaIps) {
        this(groupKey, isBeta, betaIps, null);
    }

    DataChangeTask(String groupKey, boolean isBeta, List<String> betaIps, String tag) {
        this.groupKey = groupKey;
        this.isBeta = isBeta;
        this.betaIps = betaIps;
        this.tag = tag;
    }

    final String groupKey;
    final long changeTime = System.currentTimeMillis();
    final boolean isBeta;
    final List<String> betaIps;
    final String tag;
}
// 这个方法是什么时候触发的呢
// 我们发现LongPollingService的父类构造方法中会调用EventDispatcher.addEventListener(this);
// EventDispatcher里面做了什么呢?
public void onEvent(Event event) {
    if (isFixedPolling()) {
        // ignore
    } else {
        if (event instanceof LocalDataChangeEvent) {
            LocalDataChangeEvent evt = (LocalDataChangeEvent)event;
            scheduler.execute(new DataChangeTask(evt.groupKey, evt.isBeta, evt.betaIps));
        }
    }
}
static public void addEventListener(AbstractEventListener listener) {
    // 对于LongPollingService而言,感兴趣的事件是LocalDataChangeEvent,下面是LongPollingService中的interest方法
    // public List<Class<? extends Event>> interest() {
    //    List<Class<? extends Event>> eventTypes = new ArrayList<Class<? extends Event>>();
    //    eventTypes.add(LocalDataChangeEvent.class);
    //    return eventTypes;
    // }
    for (Class<? extends Event> type : listener.interest()) {
        getEntry(type).listeners.addIfAbsent(listener);
    }
}

// 这里会调用listener.onEvent(event)去触发onEvent方法
// 对于LongPollingService而言,要触发onEvent()方法,必须是通过EventDispatcher.fireEvent(LocalDataChangeEvent)来触发的
// 那么什么时候才会触发这个动作呢?
// 我们发现原来在DumpService中会最终触发这个动作,下面我们去看看这里到底是怎么做的
static public void fireEvent(Event event) {
    if (null == event) {
        throw new IllegalArgumentException();
    }

    for (AbstractEventListener listener : getEntry(event.getClass()).listeners) {
        try {
            listener.onEvent(event);
        } catch (Exception e) {
            log.error(e.toString(), e);
        }
    }
}
// 类初始化以后执行此方法
// 这里定义了一些任务管理器TaskManager,这些任务管理器会管理DumpXXXProcessor的调度
// 最终DumpXXXProcessor中会触发EventDispatcher.fireEvent(LocalDataChangeEvent)
@PostConstruct
public void init() {
    LogUtil.defaultLog.warn("DumpService start");
    DumpProcessor processor = new DumpProcessor(this);
    DumpAllProcessor dumpAllProcessor = new DumpAllProcessor(this);
    DumpAllBetaProcessor dumpAllBetaProcessor = new DumpAllBetaProcessor(this);
    DumpAllTagProcessor dumpAllTagProcessor = new DumpAllTagProcessor(this);

    // 这里会启动后台线程
    // TaskManager(DumpProcessor(DumpService()))
    dumpTaskMgr = new TaskManager(
        "com.alibaba.nacos.server.DumpTaskManager");
    dumpTaskMgr.setDefaultTaskProcessor(processor);

    dumpAllTaskMgr = new TaskManager(
        "com.alibaba.nacos.server.DumpAllTaskManager");
    dumpAllTaskMgr.setDefaultTaskProcessor(dumpAllProcessor);

    Runnable dumpAll = new Runnable() {
        @Override
        public void run() {
            dumpAllTaskMgr.addTask(DumpAllTask.TASK_ID, new DumpAllTask());
        }
    };

    Runnable dumpAllBeta = new Runnable() {
        @Override
        public void run() {
            dumpAllTaskMgr.addTask(DumpAllBetaTask.TASK_ID, new DumpAllBetaTask());
        }
    };

    Runnable clearConfigHistory = new Runnable() {
        @Override
        public void run() {
            log.warn("clearConfigHistory start");
            if (ServerListService.isFirstIp()) {
                try {
                    Timestamp startTime = getBeforeStamp(TimeUtils.getCurrentTime(), 24 * getRetentionDays());
                    int totalCount = persistService.findConfigHistoryCountByTime(startTime);
                    if (totalCount > 0) {
                        int pageSize = 1000;
                        int removeTime = (totalCount + pageSize - 1) / pageSize;
                        log.warn("clearConfigHistory, getBeforeStamp:{}, totalCount:{}, pageSize:{}, removeTime:{}",
                            new Object[] {startTime, totalCount, pageSize, removeTime});
                        while (removeTime > 0) {
                            // 分页删除,以免批量太大报错
                            persistService.removeConfigHistory(startTime, pageSize);
                            removeTime--;
                        }
                    }
                } catch (Throwable e) {
                    log.error("clearConfigHistory error", e);
                }
            }
        }
    };

    try {
        dumpConfigInfo(dumpAllProcessor);

        // 更新beta缓存
        LogUtil.defaultLog.info("start clear all config-info-beta.");
        DiskUtil.clearAllBeta();
        if (persistService.isExistTable(BETA_TABLE_NAME)) {
            dumpAllBetaProcessor.process(DumpAllBetaTask.TASK_ID, new DumpAllBetaTask());
        }
        // 更新Tag缓存
        LogUtil.defaultLog.info("start clear all config-info-tag.");
        DiskUtil.clearAllTag();
        if (persistService.isExistTable(TAG_TABLE_NAME)) {
            dumpAllTagProcessor.process(DumpAllTagTask.TASK_ID, new DumpAllTagTask());
        }

        // add to dump aggr
        List<ConfigInfoChanged> configList = persistService.findAllAggrGroup();
        if (configList != null && !configList.isEmpty()) {
            total = configList.size();
            List<List<ConfigInfoChanged>> splitList = splitList(configList, INIT_THREAD_COUNT);
            for (List<ConfigInfoChanged> list : splitList) {
                MergeAllDataWorker work = new MergeAllDataWorker(list);
                work.start();
            }
            log.info("server start, schedule merge end.");
        }
    } catch (Exception e) {
        LogUtil.fatalLog.error(
            "Nacos Server did not start because dumpservice bean construction failure :\n" + e.getMessage(),
            e.getCause());
        throw new RuntimeException(
            "Nacos Server did not start because dumpservice bean construction failure :\n" + e.getMessage());
    }
    if (!STANDALONE_MODE) {
        Runnable heartbeat = new Runnable() {
            @Override
            public void run() {
                String heartBeatTime = TimeUtils.getCurrentTime().toString();
                // write disk
                try {
                    DiskUtil.saveHeartBeatToDisk(heartBeatTime);
                } catch (IOException e) {
                    LogUtil.fatalLog.error("save heartbeat fail" + e.getMessage());
                }
            }
        };

        TimerTaskService.scheduleWithFixedDelay(heartbeat, 0, 10, TimeUnit.SECONDS);

        long initialDelay = new Random().nextInt(INITIAL_DELAY_IN_MINUTE) + 10;
        LogUtil.defaultLog.warn("initialDelay:{}", initialDelay);

        TimerTaskService.scheduleWithFixedDelay(dumpAll, initialDelay, DUMP_ALL_INTERVAL_IN_MINUTE,
            TimeUnit.MINUTES);

        TimerTaskService.scheduleWithFixedDelay(dumpAllBeta, initialDelay, DUMP_ALL_INTERVAL_IN_MINUTE,
            TimeUnit.MINUTES);
    }

    TimerTaskService.scheduleWithFixedDelay(clearConfigHistory, 10, 10, TimeUnit.MINUTES);

}
3.2.2 总结
  • 客户端:
    1.启动一个后台线程,校验内存中缓存的数据是否和缓存文件中的一致,并进行相应处理,检查监听的数据是否和内存中的一致,不一致回调监听
    2.后台线程进行长轮询(默认超时时间30s),从服务端查询变化的数据对应的dataId列表
    3.根据dataId列表循环查询最新的数据并写入缓存文件

  • 服务端:
    1.接收到长连接请求后,先判断当前数据是否和客户端发过来的一致,不一致则返回对应的dataId列表
    2.如果一致,则启动一个延迟执行的线程S,这个线程会在29.5s后执行判断逻辑,判断当前数据是否和客户端发过来的一致,并最终返回
    3.如果在S线程执行前,有数据变化,同样会触发返回
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值