nacos 配置源码初解

入口 

//NacosConfigService
onfigService configService = NacosFactory.createConfigService(properties);
String content = configService.getConfig(dataId, group, 5000);
System.out.println(content);
configService.addListener(dataId, group, new Listener() {
	@Override
	public void receiveConfigInfo(String configInfo) {
		System.out.println("recieve1:" + configInfo);
	}
	@Override
	public Executor getExecutor() {
		return null;
	}
});

进入nacosConfigService构造方法 

  public NacosConfigService(Properties properties) throws NacosException {
       
        this.initNamespace(properties);
        //监控统计代理
        this.agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
        this.agent.start();
        this.worker = new ClientWorker(this.agent, this.configFilterChainManager, properties);
    }

初始化了一个http代理和worker 

在work里进行本地和远程配置的检查 

public ClientWorker(final HttpAgent agent, ConfigFilterChainManager configFilterChainManager, Properties properties) {
        this.agent = agent;
        this.configFilterChainManager = configFilterChainManager;
        this.init(properties);
        this.executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("com.alibaba.nacos.client.Worker." + agent.getName());
                t.setDaemon(true);
                return t;
            }
        });
//长轮训初始化
        this.executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
            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;
            }
        });

        //每10毫秒检测配置
        this.executor.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                try {
                    ClientWorker.this.checkConfigInfo();
                } catch (Throwable var2) {
                    ClientWorker.LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", var2);
                }

            }
        }, 1L, 10L, TimeUnit.MILLISECONDS);
    }

分块任务,向上取整,进行长轮训的检测本地缓存配置



   public void checkConfigInfo() {
        int listenerSize = ((Map)this.cacheMap.get()).size();
        int longingTaskCount = (int)Math.ceil((double)listenerSize / ParamUtil.getPerTaskConfigSize());
        if ((double)longingTaskCount > this.currentLongingTaskCount) {
            for(int i = (int)this.currentLongingTaskCount; i < longingTaskCount; ++i) {
                this.executorService.execute(new ClientWorker.LongPollingRunnable(i));
            }

            this.currentLongingTaskCount = (double)longingTaskCount;
        }

    }


ClientWorker.this.checkLocalConfig(cacheData);
      if (cacheData.isUseLocalConfigInfo()) {
            cacheData.checkListenerMd5();//如有有不一样回调入口方法的监听
        }
private void checkLocalConfig(CacheData cacheData) {
        String dataId = cacheData.dataId;
        String group = cacheData.group;
        String tenant = cacheData.tenant;
        File path = LocalConfigInfoProcessor.getFailoverFile(this.agent.getName(), dataId, group, tenant);
        String content;
        String md5;
        //将本地文件读取到本地配置
        if (!cacheData.isUseLocalConfigInfo() && path.exists()) {
            content = LocalConfigInfoProcessor.getFailover(this.agent.getName(), dataId, group, tenant);
            md5 = MD5.getInstance().getMD5String(content);
            cacheData.setUseLocalConfigInfo(true);
            cacheData.setLocalConfigInfoVersion(path.lastModified());
            cacheData.setContent(content);
            LOGGER.warn("[{}] [failover-change] failover file created. dataId={}, group={}, tenant={}, md5={}, content={}", new Object[]{this.agent.getName(), dataId, group, tenant, md5, ContentUtils.truncateContent(content)});
        //    有->没有不通知业务监听器,从server拿到配置后通知
        } else if (cacheData.isUseLocalConfigInfo() && !path.exists()) {
            cacheData.setUseLocalConfigInfo(false);
            LOGGER.warn("[{}] [failover-change] failover file deleted. dataId={}, group={}, tenant={}", new Object[]{this.agent.getName(), dataId, group, tenant});
        } else {
            //    有变更
            if (cacheData.isUseLocalConfigInfo() && path.exists() && cacheData.getLocalConfigInfoVersion() != path.lastModified()) {
                content = LocalConfigInfoProcessor.getFailover(this.agent.getName(), dataId, group, tenant);
                md5 = MD5.getInstance().getMD5String(content);
                cacheData.setUseLocalConfigInfo(true);
                cacheData.setLocalConfigInfoVersion(path.lastModified());
                cacheData.setContent(content);
                LOGGER.warn("[{}] [failover-change] failover file changed. dataId={}, group={}, tenant={}, md5={}, content={}", new Object[]{this.agent.getName(), dataId, group, tenant, md5, ContentUtils.truncateContent(content)});
            }

        }
    }

远程配置检查

//从server里获取值变化了的DataID列表,返回的对象只有dataId和groupId是有效的
List<String> changedGroupKeys = ClientWorker.this.checkUpdateDataIds(cacheDatas, inInitializingCacheList);


List<String> checkUpdateConfigStr(String probeUpdateString, boolean isInitializingCacheList) throws IOException {
        List<String> params = new ArrayList(2);
        params.add("Listening-Configs");
        params.add(probeUpdateString);
        List<String> headers = new ArrayList(2);
        headers.add("Long-Pulling-Timeout");
        headers.add("" + this.timeout);
        if (isInitializingCacheList) {
            headers.add("Long-Pulling-Timeout-No-Hangup");
            headers.add("true");
        }

        if (StringUtils.isBlank(probeUpdateString)) {
            return Collections.emptyList();
        } else {
            try {
                long readTimeoutMs = this.timeout + (long)Math.round((float)(this.timeout >> 1));
//客户端长轮训的时间间隔30s.在配置没有发生变化的情况下,客户端会等29.5s以上如果在配置变更的情况下,由于客户端基于长轮训的连接保持,所以返回的时间会非常的短
                HttpResult result = this.agent.httpPost("/v1/cs/configs/listener", headers, params, this.agent.getEncode(), readTimeoutMs);
                if (200 == result.code) {
                    this.setHealthServer(true);
                    return this.parseUpdateDataIdResponse(result.content);
                }

                this.setHealthServer(false);
                LOGGER.error("[{}] [check-update] get changed dataId error, code: {}", this.agent.getName(), result.code);
            } catch (IOException var8) {
                this.setHealthServer(false);
                LOGGER.error("[" + this.agent.getName() + "] [check-update] get changed dataId exception", var8);
                throw var8;
            }

            return Collections.emptyList();
        }
    }

服务端是如何处理客户端的长轮训

ConfigController

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

 

简单总结一下刚刚分析的整个过程。

  • 客户端发起长轮训请求
  • 服务端收到请求以后,先比较服务端缓存中的数据是否相同,如果不同,则直接返回
  • 如果相同,则通过schedule延迟29.5s之后再执行比较
  • 为了保证当服务端在29.5s之内发生数据变化能够及时通知给客户端,服务端采用事件订阅的方式 来监听服务端本地数据变化的事件,一旦收到事件,则触发DataChangeTask的通知,并且遍历 allStubs队列中的ClientLongPolling,把结果写回到客户端,就完成了一次数据的推送
  • 如果 DataChangeTask 任务完成了数据的 “推送” 之后,ClientLongPolling 中的调度任务又开始执 行了怎么办呢? 很简单,只要在进行 “推送” 操作之前,先将原来等待执行的调度任务取消掉就可以了,这样就防 止了推送操作写完响应数据之后,调度任务又去写响应数据,这时肯定会报错的。所以,在 ClientLongPolling方法中,最开始的一个步骤就是删除订阅事件
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Nacos配置中心控制台的源码分析可以帮助我们深入理解其实现细节和工作原理。以下是一个大致的源码分析过程: 1. 入口类分析:首先,我们需要找到Nacos配置中心控制台的入口类。该类通常是一个Spring Boot应用的启动类,负责初始化和启动整个应用。我们可以查找包含main方法的类,或者在启动脚本中找到应用的入口点。 2. 依赖分析:接下来,我们需要分析应用所依赖的第三方库和框架。查看应用的pom.xml文件或者build.gradle文件,可以获取到所依赖的各个库和对应版本。这些依赖通常包括Spring框架、Nacos客户端等。 3. 配置加载与解析:Nacos配置中心控制台需要加载和解析配置,包括数据库配置Nacos服务地址配置等。我们可以查找相关的配置文件或者代码片段,了解配置的加载和解析过程。 4. 控制器与路由:控制台通常提供了一些Web接口供前端调用。我们可以查找控制器类,分析其中的方法和注解,了解各个接口的功能和路由规则。 5. 页面模板与前端交互:配置中心控制台通常包含一些页面模板和与前端的交互逻辑。我们可以查找相关的HTML、CSS和JavaScript文件,分析页面的结构和交互逻辑。 6. 调用Nacos API:控制台需要与Nacos服务器进行通信,调用Nacos的API获取和修改配置信息。我们可以查找相关的API调用,了解控制台是如何与Nacos服务器进行通信的。 通过以上分析,我们可以逐步了解Nacos配置中心控制台的实现细节和工作原理。需要注意的是,具体的源码分析过程会因项目结构和代码风格而有所不同。以上只是一个大致的指导,具体分析还需根据实际情况来进行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值