nacos配置中心源码分析

这个源码入口怎么去找

1.以接口,核心方法作为切入点

2.既然是配置的话,那么我们可以从配置入手

复习下springboot的只是

  • bootstrap的优先级高于application,优先被加载
  • yml>yaml>properties

这里直接看nacos配置的优先级覆盖

PropertySource这个就是spring提供的,键值对

到了springboot里面就是这个org.springframework.boot.env.PropertySourceLoader类的load方法

PropertiesPropertySourceLoader实现了PropertySourceLoader接口:
	@Override
	public List<PropertySource<?>> load(String name, Resource resource)
			throws IOException {
		Map<String, ?> properties = loadProperties(resource);
		if (properties.isEmpty()) {
			return Collections.emptyList();
		}
		return Collections
				.singletonList(new OriginTrackedMapPropertySource(name, properties));
	}

 ApplicationContextInitializer这个类是spring的扩展点,在

ConfigurableApplicationContext的refresh方法之前调用,用于需要对应用上下文做初始化web应用,例如根据上下文环境注册属性源活激活配置文件等。因为是prepareContext(context, environment, listeners, applicationArguments, printedBanner);然后再
refreshContext(context);的

加载完配置就看

SpringApplication的run方法,其中prepareContext方法里面的applyInitializers(context)方法,会遍历实现ApplicationContextInitializer接口的类,

其中PropertySourceBootstrapConfiguration实现了这个接口,在spring-cloud-context中,使用spring的spi机制注入其中。

PropertySourceBootstrapConfiguration的initialize方法,其中

Collection<PropertySource<?>> source = locator.locateCollection(environment);

最后点到spring的类PropertySource<?> locate(Environment environment);

NacosPropertySourceLocator实现了PropertySourceLocator

我们来看NacosPropertySourceLocator的locate方法

@Override
	public PropertySource<?> locate(Environment env) {
		nacosConfigProperties.setEnvironment(env);
		ConfigService configService = nacosConfigManager.getConfigService();

		if (null == configService) {
			log.warn("no instance of config service found, can't load config from nacos");
			return null;
		}
		long timeout = nacosConfigProperties.getTimeout();
		nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
				timeout);
		String name = nacosConfigProperties.getName();

		String dataIdPrefix = nacosConfigProperties.getPrefix();
		if (StringUtils.isEmpty(dataIdPrefix)) {
			dataIdPrefix = name;
		}

		if (StringUtils.isEmpty(dataIdPrefix)) {
			dataIdPrefix = env.getProperty("spring.application.name");
		}

		CompositePropertySource composite = new CompositePropertySource(
				NACOS_PROPERTY_SOURCE_NAME);

		loadSharedConfiguration(composite);
		loadExtConfiguration(composite);
		loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
		return composite;
	}

其中的下面三行代码已经体现了一定的加载顺序性

loadSharedConfiguration(composite);
		loadExtConfiguration(composite);
		loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);

这里明显可以看出shard的优先级最低,其次是ext的,因为这边是下面的覆盖上面的配置

接下来继续看loadApplicationConfiguration方法

private void loadApplicationConfiguration(
			CompositePropertySource compositePropertySource, String dataIdPrefix,
			NacosConfigProperties properties, Environment environment) {
		String fileExtension = properties.getFileExtension();
		String nacosGroup = properties.getGroup();
		// load directly once by default
		loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
				fileExtension, true);
		// load with suffix, which have a higher priority than the default
		loadNacosDataIfPresent(compositePropertySource,
				dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
		// Loaded with profile, which have a higher priority than the suffix
		for (String profile : environment.getActiveProfiles()) {
			String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
			loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
					fileExtension, true);
		}

	}

这里的几个loadNacosDataIfPresent就体现了顺序性

优先级从高到低如下所示:

 ----------------------

 配置中心核心 是configservice

注册中心核心是namingservice

先看

 这个方法。

会定位到NacosConfigService的getConfigInner方法

private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
		group = null2defaultGroup(group);
		ParamUtils.checkKeyParam(dataId, group);
		ConfigResponse cr = new ConfigResponse();

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

		// 优先使用本地配置
		String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
		if (content != null) {
			log.warn(agent.getName(), "[get-config] get failover ok, dataId={}, group={}, tenant={}, config={}", dataId,
					group, tenant, ContentUtils.truncateContent(content));
			cr.setContent(content);
			configFilterChainManager.doFilter(null, cr);
			content = cr.getContent();
			return content;
		}

		try {
			content = worker.getServerConfig(dataId, group, tenant, timeoutMs);
			cr.setContent(content);
			configFilterChainManager.doFilter(null, cr);
			content = cr.getContent();
			return content;
		} catch (NacosException ioe) {
			if (NacosException.NO_RIGHT == ioe.getErrCode()) {
				throw ioe;
			}
			log.warn("NACOS-0003",
					LoggerHelper.getErrorCodeStr("NACOS", "NACOS-0003", "环境问题", "get from server error"));
			log.warn(agent.getName(), "[get-config] get from server error, dataId={}, group={}, tenant={}, msg={}",
					dataId, group, tenant, ioe.toString());
		}

		log.warn(agent.getName(), "[get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}", dataId,
				group, tenant, ContentUtils.truncateContent(content));
		content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);
		cr.setContent(content);
		configFilterChainManager.doFilter(null, cr);
		content = cr.getContent();
		return content;
	}

这里很明显

	// 优先使用本地配置
		String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);

优先使用本地配置,点进去

static public 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) {
    		log.error(serverName, "NACOS-XXXX","get failover error, " + localPath + ioe.toString());
    		return null;
    	}
    }


-----------
 static File getFailoverFile(String serverName, String dataId, String group, String tenant) {
    	File tmp = new File(LOCAL_SNAPSHOT_PATH, serverName + "_nacos");
    	tmp = new File(tmp, "data");
    	if (StringUtils.isBlank(tenant)) {
    		tmp = new File(tmp, "config-data");
    	} else
    	{
    		tmp = new File(tmp, "config-data-tenant");
    		tmp = new File(tmp, tenant);
    	}
    	return new File(new File(tmp, group), dataId);
    }

红色的圈是namespace ,优先读取本地配置,服务关闭这个配置会清掉 

这样就可以容错的,保证断网,配置中心挂掉了还可以用的。比如调用别的服务这样的。

没有的话再去调用服务端拉取配置,这里是http的get请求获取。

content = worker.getServerConfig(dataId, group, tenant, timeoutMs);

点进httpGet方法里面有一行

	HttpResult result = HttpSimpleClient.httpGet(
						getUrl(serverListMgr.getCurrentServerAddr(), path, isSSL), newHeaders, paramValues, encoding,
						readTimeoutMs, isSSL);

其中这就是轮询服务端

public String getCurrentServerAddr() {
		if (StringUtils.isBlank(currentServerAddr)) {
			currentServerAddr = iterator().next();
		}
		return currentServerAddr;
	}

接下来看配置中心的 自动刷新配置的原理:

在spring-cloud-starter-alibaba-nacos-config的spi机制里有这个类NacosConfigAutoConfiguration,会注入nacosContextRefresher这个类实现了ApplicationListener接口,我们直接看它的
@Override
	public void onApplicationEvent(ApplicationReadyEvent event) {
		// many Spring context
		if (this.ready.compareAndSet(false, true)) {
			this.registerNacosListenersForApplications();
		}
	}

为每个NacosPropertySource依次注册监听器,然后就可以动态感知服务端配置的变化

private void registerNacosListenersForApplications() {
		if (isRefreshEnabled()) {
			for (NacosPropertySource propertySource : NacosPropertySourceRepository
					.getAll()) {
				if (!propertySource.isRefreshable()) {
					continue;
				}
				String dataId = propertySource.getDataId();
				registerNacosListener(propertySource.getGroup(), dataId);
			}
		}
	}

上面的NacosPropertySource就是我们的六个nacos配置文件,打断点如下

接着看

private void registerNacosListener(final String groupKey, final String dataKey) {
		String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
		Listener listener = listenerMap.computeIfAbsent(key,
				lst -> new AbstractSharedListener() {
					@Override
					public void innerReceive(String dataId, String group,
							String configInfo) {
						refreshCountIncrement();
						nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
						// todo feature: support single refresh for listening
						applicationContext.publishEvent(
								new RefreshEvent(this, null, "Refresh Nacos config"));
						if (log.isDebugEnabled()) {
							log.debug(String.format(
									"Refresh Nacos config group=%s,dataId=%s,configInfo=%s",
									group, dataId, configInfo));
						}
					}
				});
		try {
			configService.addListener(dataKey, groupKey, listener);
		}
		catch (NacosException e) {
			log.warn(String.format(
					"register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey,
					groupKey), e);
		}
	}

看这一行:

applicationContext.publishEvent(
								new RefreshEvent(this, null, "Refresh Nacos config"));

发布一个RefreshEvent事件,这个时间会在RefreshEventListener(最终实现ApplicationListener)

来看

@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationReadyEvent) {
			handle((ApplicationReadyEvent) event);
		}
		else if (event instanceof RefreshEvent) {
			handle((RefreshEvent) event);
		}
	}

重点看这个else  if的方法

public void handle(RefreshEvent event) {
		if (this.ready.get()) { // don't handle events before app is ready
			log.debug("Event received " + event.getEventDesc());
			Set<String> keys = this.refresh.refresh();
			log.info("Refresh keys changed: " + keys);
		}
	}

重点看Set<String> keys = this.refresh.refresh();

public synchronized Set<String> refresh() {
		Set<String> keys = refreshEnvironment();
		this.scope.refreshAll();
		return keys;
	}

先看环境相关的refreshEnvironment:

public synchronized Set<String> refreshEnvironment() {
        //抽取出除了system,jndi,servlet之外的所有参数变量
		Map<String, Object> before = extract(
				this.context.getEnvironment().getPropertySources());
        //把原来的environment里面的参数放到一个新建的 spring context容器下重新加载,完事之后关闭新容器,这里就是获取新的参数值了,这里面有个run方法ConfigurableApplicationContext就是SpringApplication的run方法调用
		addConfigFilesToEnvironment();
        //获取新的参数值,并和之前的参数值进行比较找出改变的参数值
		Set<String> keys = changes(before,
			extract(this.context.getEnvironment().getPropertySources())).keySet();
        //发布环境变更事件,并带上改变的参数值
		this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
		return keys;
	}


然后看刷新bean的

this.scope.refreshAll();
----------
public void refreshAll() {
		super.destroy();
		this.context.publishEvent(new RefreshScopeRefreshedEvent());
	}

其中RefreshScopeRefreshedEvent是一个留给我们的扩展点,我们可以自己去监听这个变更的事件,这里没有实现这个类的监听。

这类的scope是RefreshScope,会调用父类GenericScope的销毁方法(清除scope的缓存,下次会从beanfactory里面获取一个新的实例,这个实例使用新的配置):

@Override
	public void destroy() {
		List<Throwable> errors = new ArrayList<Throwable>();
		Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
		for (BeanLifecycleWrapper wrapper : wrappers) {
			try {
				Lock lock = this.locks.get(wrapper.getName()).writeLock();
				lock.lock();
				try {
					wrapper.destroy();
				}
				finally {
					lock.unlock();
				}
			}
			catch (RuntimeException e) {
				errors.add(e);
			}
		}
		if (!errors.isEmpty()) {
			throw wrapIfNecessary(errors.get(0));
		}
		this.errors.clear();
	}


-----
@Override
	public Object get(String name, ObjectFactory<?> objectFactory) {
		BeanLifecycleWrapper value = this.cache.put(name,
				new BeanLifecycleWrapper(name, objectFactory));
		this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
		try {
			return value.getBean();
		}
		catch (RuntimeException e) {
			this.errors.put(name, e);
			throw e;
		}
	}

这里清除了缓存,重点看下BeanLifecycleWrapper

private static class BeanLifecycleWrapper {

		private final String name;

		private final ObjectFactory<?> objectFactory;

		private Object bean;

		private Runnable callback;

		BeanLifecycleWrapper(String name, ObjectFactory<?> objectFactory) {
			this.name = name;
			this.objectFactory = objectFactory;
		}

		public String getName() {
			return this.name;
		}

		public void setDestroyCallback(Runnable callback) {
			this.callback = callback;
		}

		public Object getBean() {
			if (this.bean == null) {
				synchronized (this.name) {
					if (this.bean == null) {
						this.bean = this.objectFactory.getObject();
					}
				}
			}
			return this.bean;
		}

		public void destroy() {
			if (this.callback == null) {
				return;
			}
			synchronized (this.name) {
				Runnable callback = this.callback;
				if (callback != null) {
					callback.run();
				}
				this.callback = null;
				this.bean = null;
			}
		}

。。。。。。省略

那就很明显了,缓存清除的话,然后每次获取就从GenericScope的get方法获取bean这里会调用到

BeanLifecycleWrapper的getBean方法,缓存清除就木有bean就会调用ObjectFactory的getobject方法(这就和spring的factorybean类似)

这里再补充一点@RefreshScope 导致@Scheduled定时任务失效问题

当利用@RefreshScope刷新配置后会导致定时任务失效​​​​​​​​​​​​​​

@SpringBootApplication
@EnableScheduling   // 开启定时任务功能
public class NacosConfigApplication {
}

@RestController
@RefreshScope  //动态感知修改后的值
public class TestController {

    @Value("${common.age}")
     String age;
    @Value("${common.name}")
     String name;

    @GetMapping("/common")
    public String hello() {
        return name+","+age;
    }

    //触发@RefreshScope执行逻辑会导致@Scheduled定时任务失效
    @Scheduled(cron = "*/3 * * * * ?")  //定时任务每隔3s执行一次
    public void execute() {
        System.out.println("定时任务正常执行。。。。。。");
    }


}

测试结果:

原因:@RefreshScope修饰的bean的属性发生变更后,会从缓存中清除。此时没有这个bean,定时任务当然也就不生效了。

这里补充一点因为是懒加载的,所以清除之后,只要你不是再次get使用这个bean,那么就不会走spring的bean生命周期,所以清除完这个bean(也就是刷新摧毁这个bean的缓存后到下一次使用这个bean之前这一段时间定时任务都没效果了)

解决方案:重新触发get方法或者

实现Spring事件监听器,监听 RefreshScopeRefreshedEvent事件,监听方法中进行一次定时方法的调用

@RestController
@RefreshScope  //动态感知修改后的值
public class TestController implements ApplicationListener<RefreshScopeRefreshedEvent>{

    @Value("${common.age}")
     String age;
    @Value("${common.name}")
     String name;

    @GetMapping("/common")
    public String hello() {
        return name+","+age;
    }

    //触发@RefreshScope执行逻辑会导致@Scheduled定时任务失效
    @Scheduled(cron = "*/3 * * * * ?")  //定时任务每隔3s执行一次
    public void execute() {
        System.out.println("定时任务正常执行。。。。。。");
    }


    @Override
    public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
        this.execute();
    }
}

------------------

然后看服务端的配置的逻辑:

入口: com.alibaba.nacos.config.server.controller.ConfigController##getConfig

-->inner.doGetConfig(request, response, dataId, group, tenant, tag, clientIp);

其中DiskUtil.targetBetaFile(dataId, group, tenant);

   
    /**
     * Returns the path of cache file in server.
     */
    public static File targetBetaFile(String dataId, String group, String tenant) {
        File file = null;
        if (StringUtils.isBlank(tenant)) {
            file = new File(EnvUtil.getNacosHome(), BETA_DIR);
        } else {
            file = new File(EnvUtil.getNacosHome(), TENANT_BETA_DIR);
            file = new File(file, tenant);
        }
        file = new File(file, group);
        file = new File(file, dataId);
        return file;
    }
    

服务端没有查数据库,直接从本地磁盘缓存文件读取,所以直接修改mysql表里面配置是不行的,一定要发布ConfigDataChangeEvent事件。触发本地文件和内存的更新

其中

  md5 = cacheItem.getMd5();
  lastModified = cacheItem.getLastModifiedTs();

 服务端判断文件发生编码,对数据进行MD5,然后比较MD5(同样也可以hash之类的去比较)

接下来看写入磁盘的过程:

从DumpService开始

两个实现类一个是内置一个是嵌入式的。

我们看ExternalDumpService

 @PostConstruct
    @Override
    protected void init() throws Throwable {
        dumpOperate(processor, dumpAllProcessor, dumpAllBetaProcessor, dumpAllTagProcessor);
    }

然后dumpConfigInfo(dumpAllProcessor);全量的dump配置信息,dumpAllProcessor.process(new DumpAllTask());这个里面先查出mysql最大的主键量,然后分页每次1000条写入磁盘和内存 ConfigCacheService .dump(cf.getDataId(), cf.getGroup(), cf.getTenant(), cf.getContent(), cf.getLastModified(), cf.getType());接着调用DiskUtil.saveToDisk(dataId, group, tenant, content);写入磁盘。

其中这个是核心方法,判断MD5,然后发布LocalDataChangeEvent事件 

 updateMd5(groupKey, md5, lastModifiedTs);
---------
 public static void updateMd5(String groupKey, String md5, long lastModifiedTs) {
        CacheItem cache = makeSure(groupKey);
        if (cache.md5 == null || !cache.md5.equals(md5)) {
            cache.md5 = md5;
            cache.lastModifiedTs = lastModifiedTs;
            NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey));
        }
    }

这个事件在ConfigExecutor.executeLongPolling(new DataChangeTask(evt.groupKey, evt.isBeta, evt.betaIps));调用,LongPollingService这个类的构造方法

    public LongPollingService() {
        allSubs = new ConcurrentLinkedQueue<ClientLongPolling>();
        
        ConfigExecutor.scheduleLongPolling(new StatTask(), 0L, 10L, TimeUnit.SECONDS);
        
        // Register LocalDataChangeEvent to NotifyCenter.
        NotifyCenter.registerToPublisher(LocalDataChangeEvent.class, NotifyCenter.ringBufferSize);
        
        // Register A Subscriber to subscribe LocalDataChangeEvent.
        NotifyCenter.registerSubscriber(new Subscriber() {
            
            @Override
            public void onEvent(Event event) {
                if (isFixedPolling()) {
                    // Ignore.
                } else {
                    if (event instanceof LocalDataChangeEvent) {
                        LocalDataChangeEvent evt = (LocalDataChangeEvent) event;
                        ConfigExecutor.executeLongPolling(new DataChangeTask(evt.groupKey, evt.isBeta, evt.betaIps));
                    }
                }
            }
            
            @Override
            public Class<? extends Event> subscribeType() {
                return LocalDataChangeEvent.class;
            }
        });
        
    }

然后是DataChangeTask的run方法,迭代所有的sub队列(发布订阅模式)

Iterator<ClientLongPolling> iter = allSubs.iterator(); 

然后响应变化发生的key

clientSub.sendResponse(Arrays.asList(groupKey));

其中 run方法的

for (Iterator<ClientLongPolling> iter = allSubs.iterator(); iter.hasNext(); )

这一行就说明有任务的话不需要等待29.5秒时间,直接拉取到变更的配置。

这里就是客户端和服务端存在长轮询,一个可以推,一个可以拉,这里是服务端push模式(服务端会根据心跳文件中保存的最后一次心跳时间,来判断到底是从数据库 dump 全量配置数据还是部分增量配置数据(如果机器上次心跳间隔是 6h 以内的话)。有变更的话

DataChangeTask任务持有一个 AsyncContext 响应对象,通过定时线程池延后 29.5s 执行。比客户端 30s 的超时时间提前 500ms 返回是为了最大程度上保证客户端不会因为网络延时造成超时。

回到上面的核心方法

  public static boolean publishEvent(final Event event) {
        try {
            return publishEvent(event.getClass(), event);
        } catch (Throwable ex) {
            LOGGER.error("There was an exception to the message publishing : {}", ex);
            return false;
        }
    }
    

最终定位到DefaultPublisher的publish方法

 @Override
    public boolean publish(Event event) {
        checkIsStart();
        boolean success = this.queue.offer(event);
        if (!success) {
            LOGGER.warn("Unable to plug in due to interruption, synchronize sending time, event : {}", event);
            receiveEvent(event);
            return true;
        }
        return true;
    }

这里就是典型的生产者消费者模式,使用阻塞队列接受任务

其中

void receiveEvent(Event event) {
        final long currentEventSequence = event.sequence();
        
        // Notification single event listener
        for (Subscriber subscriber : subscribers) {
            // Whether to ignore expiration events
            if (subscriber.ignoreExpireEvent() && lastEventSequence > currentEventSequence) {
                LOGGER.debug("[NotifyCenter] the {} is unacceptable to this subscriber, because had expire",
                        event.getClass());
                continue;
            }
            
            // Because unifying smartSubscriber and subscriber, so here need to think of compatibility.
            // Remove original judge part of codes.
            notifySubscriber(subscriber, event);
        }
    }

其中抽象类Subscriber的两个子类,一个是通知集群其他节点AsyncNotifyService,一个LongPollingService客户端服务端长轮询用的。这里看下AsyncNotifyService

 @Autowired
    public AsyncNotifyService(ServerMemberManager memberManager) {
        this.memberManager = memberManager;
        
        // Register ConfigDataChangeEvent to NotifyCenter.
        NotifyCenter.registerToPublisher(ConfigDataChangeEvent.class, NotifyCenter.ringBufferSize);
        
        // Register A Subscriber to subscribe ConfigDataChangeEvent.
        NotifyCenter.registerSubscriber(new Subscriber() {
            
            @Override
            public void onEvent(Event event) {
                // Generate ConfigDataChangeEvent concurrently
                if (event instanceof ConfigDataChangeEvent) {
                    ConfigDataChangeEvent evt = (ConfigDataChangeEvent) event;
                    long dumpTs = evt.lastModifiedTs;
                    String dataId = evt.dataId;
                    String group = evt.group;
                    String tenant = evt.tenant;
                    String tag = evt.tag;
                    Collection<Member> ipList = memberManager.allMembers();
                    
                    // In fact, any type of queue here can be
                    Queue<NotifySingleTask> queue = new LinkedList<NotifySingleTask>();
                    for (Member member : ipList) {
                        queue.add(new NotifySingleTask(dataId, group, tenant, tag, dumpTs, member.getAddress(),
                                evt.isBeta));
                    }
                    ConfigExecutor.executeAsyncNotify(new AsyncTask(nacosAsyncRestTemplate, queue));
                }
            }
            
            @Override
            public Class<? extends Event> subscribeType() {
                return ConfigDataChangeEvent.class;
            }
        });
    }

获取Collection<Member> ipList = memberManager.allMembers();集群所有节点目标

然后异步发送ConfigExecutor.executeAsyncNotify(new AsyncTask(nacosAsyncRestTemplate, queue));

再补充一点客户端的ClientWorker

  @SuppressWarnings("PMD.ThreadPoolCreationRule")
    public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager,
            final Properties properties) {
        this.agent = agent;
        this.configFilterChainManager = configFilterChainManager;
        
        // Initialize the timeout parameter
        
        init(properties);
        
        this.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;
            }
        });
        
        this.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;
                    }
                });
        
        this.executor.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                try {
                    checkConfigInfo();
                } catch (Throwable e) {
                    LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
                }
            }
        }, 1L, 10L, TimeUnit.MILLISECONDS);
    }

其中

  public void checkConfigInfo() {
        // Dispatch taskes.
        int listenerSize = cacheMap.size();
        // Round up the longingTaskCount.
        int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());
        if (longingTaskCount > currentLongingTaskCount) {
            for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {
                // The task list is no order.So it maybe has issues when changing.
                executorService.execute(new LongPollingRunnable(i));
            }
            currentLongingTaskCount = longingTaskCount;
        }
    }

这里可以看到长轮询是定时任务线程池,客户端主动拉取配置的

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
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配置中心控制台的实现细节和工作原理。需要注意的是,具体的源码分析过程会因项目结构和代码风格而有所不同。以上只是一个大致的指导,具体分析还需根据实际情况来进行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值