Nacos源码学习(4)— 客户端监听机制

在上篇文章spring cloud自动刷新配置信息中,写到spring cloud刷新配置是通过添加listener来监听配置变化的。这篇文章将详细介绍nacos客户端的监听机制。

nacos添加监听listener是通过ClientWorker的addTenantListeners方法来实现的,会将需要监听的数据都缓存到内存的map中,key就是dataId,group,namespace拼接起来的字符串,value是CacheData对象,CacheData类里属性主要有listener数组,配置内容content,配置内容的md5摘要等。addTenantListeners方法实现代码如下:

public void addTenantListeners(String dataId, String group, List listeners)
            throws NacosException {
        group = null2defaultGroup(group);
        String tenant = agent.getTenant();
        //缓存需要监听的配置信息
        CacheData cache = addCacheDataIfAbsent(dataId, group, tenant);
        for (Listener listener : listeners) {
            //添加listener到监听列表中
            cache.addListener(listener);
        }
    }

监听配置变化的逻辑也是在ClientWorker类实现的,在创建ClientWorker对象时,会自动启动一个定时任务,定时去检查是否有新增的要监听配置:

this.executor.scheduleWithFixedDelay(new Runnable() {
            //每10秒执行一次定时任务
            @Override
            public void run() {
                try {
                    checkConfigInfo();
                } catch (Throwable e) {
                    LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
                }
            }
        }, 1L, 10L, TimeUnit.MILLISECONDS);

checkConfigInfo是检查配置变化的核心方法,会把缓存的数据默认分成每3000条一组,通过线程池去异步执行,实际上监听的数据应该都不会达到此数量级。后续此定时任务只用于检查是否新增监听配置超过了3000条,如果超过了,会再提交一组任务。

public void checkConfigInfo() {
    // Dispatch taskes.
    int listenerSize = cacheMap.get().size();
    //每3000个分组数据分成一组执行,实际上应该都不会有那么多
    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;
    }
}

LongPollingRunnable的run方法核心逻辑如下:如果启动了本地failover机制,会优先去本地查询配置是否变化,判断依据就是数据的md5值是否有变化。之后会调用nacos服务端接口,查询有哪些配置信息发生了变化,对于发生变化的配置信息,会再获取配置信息详情,通过listener回调方法刷新数据。最后,如果没有发生异常,会再把此任务提交到线程池循环执行;如果有异常,会停留一段时间再执行。

 @Override
        public void run() {
            
            List cacheDatas = new ArrayList();
            List inInitializingCacheList = new ArrayList();
            try {
                // 检查本地配置
                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);
                        }
                    }
                }
                
                //调用服务端接口,查询有哪些配置发生了变化
                List changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
                if (!CollectionUtils.isEmpty(changedGroupKeys)) {
                    LOGGER.info("get changedGroupKeys:" + changedGroupKeys);
                }
                
                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[] ct = getServerConfig(dataId, group, tenant, 3000L);
                        CacheData cache = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant));
                        cache.setContent(ct[0]);
                        if (null != ct[1]) {
                            cache.setType(ct[1]);
                        }
                        LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}, type={}",
                                agent.getName(), dataId, group, tenant, cache.getMd5(),
                                ContentUtils.truncateContent(ct[0]), ct[1]);
                    } 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))) {
                       //再次检查md5值,发生变化会通知listener
                        cacheData.checkListenerMd5();
                        cacheData.setInitializing(false);
                    }
                }
                inInitializingCacheList.clear();
                //没有异常,会循环接着执行
                executorService.execute(this);
                
            } catch (Throwable e) {
                
                // 如果发生了异常,会过一段再执行
                LOGGER.error("longPolling error : ", e);
                executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS);
            }
        }

CacheData的checkListenerMd5方法会匹配md5值,如果md5值发生了变化,会通知listener列表

void checkListenerMd5() {
        for (ManagerListenerWrap wrap : listeners) {
            if (!md5.equals(wrap.lastCallMd5)) {
                //通知listener
                safeNotifyListener(dataId, group, content, type, md5, wrap);
            }
        }
    }

你可能感兴趣的:(学习,java,spring)