• nacos配置中心源码分析


    这个源码入口怎么去找

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

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

    复习下springboot的只是

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

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

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

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

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

     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> source = locator.locateCollection(environment);

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

    NacosPropertySourceLocator实现了PropertySourceLocator

    我们来看NacosPropertySourceLocator的locate方法

    1. @Override
    2. public PropertySource locate(Environment env) {
    3. nacosConfigProperties.setEnvironment(env);
    4. ConfigService configService = nacosConfigManager.getConfigService();
    5. if (null == configService) {
    6. log.warn("no instance of config service found, can't load config from nacos");
    7. return null;
    8. }
    9. long timeout = nacosConfigProperties.getTimeout();
    10. nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
    11. timeout);
    12. String name = nacosConfigProperties.getName();
    13. String dataIdPrefix = nacosConfigProperties.getPrefix();
    14. if (StringUtils.isEmpty(dataIdPrefix)) {
    15. dataIdPrefix = name;
    16. }
    17. if (StringUtils.isEmpty(dataIdPrefix)) {
    18. dataIdPrefix = env.getProperty("spring.application.name");
    19. }
    20. CompositePropertySource composite = new CompositePropertySource(
    21. NACOS_PROPERTY_SOURCE_NAME);
    22. loadSharedConfiguration(composite);
    23. loadExtConfiguration(composite);
    24. loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
    25. return composite;
    26. }

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

    1. loadSharedConfiguration(composite);
    2. loadExtConfiguration(composite);
    3. loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);

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

    接下来继续看loadApplicationConfiguration方法

    1. private void loadApplicationConfiguration(
    2. CompositePropertySource compositePropertySource, String dataIdPrefix,
    3. NacosConfigProperties properties, Environment environment) {
    4. String fileExtension = properties.getFileExtension();
    5. String nacosGroup = properties.getGroup();
    6. // load directly once by default
    7. loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
    8. fileExtension, true);
    9. // load with suffix, which have a higher priority than the default
    10. loadNacosDataIfPresent(compositePropertySource,
    11. dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
    12. // Loaded with profile, which have a higher priority than the suffix
    13. for (String profile : environment.getActiveProfiles()) {
    14. String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
    15. loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
    16. fileExtension, true);
    17. }
    18. }

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

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

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

     配置中心核心 是configservice

    注册中心核心是namingservice

    先看

     这个方法。

    会定位到NacosConfigService的getConfigInner方法

    1. private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
    2. group = null2defaultGroup(group);
    3. ParamUtils.checkKeyParam(dataId, group);
    4. ConfigResponse cr = new ConfigResponse();
    5. cr.setDataId(dataId);
    6. cr.setTenant(tenant);
    7. cr.setGroup(group);
    8. // 优先使用本地配置
    9. String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
    10. if (content != null) {
    11. log.warn(agent.getName(), "[get-config] get failover ok, dataId={}, group={}, tenant={}, config={}", dataId,
    12. group, tenant, ContentUtils.truncateContent(content));
    13. cr.setContent(content);
    14. configFilterChainManager.doFilter(null, cr);
    15. content = cr.getContent();
    16. return content;
    17. }
    18. try {
    19. content = worker.getServerConfig(dataId, group, tenant, timeoutMs);
    20. cr.setContent(content);
    21. configFilterChainManager.doFilter(null, cr);
    22. content = cr.getContent();
    23. return content;
    24. } catch (NacosException ioe) {
    25. if (NacosException.NO_RIGHT == ioe.getErrCode()) {
    26. throw ioe;
    27. }
    28. log.warn("NACOS-0003",
    29. LoggerHelper.getErrorCodeStr("NACOS", "NACOS-0003", "环境问题", "get from server error"));
    30. log.warn(agent.getName(), "[get-config] get from server error, dataId={}, group={}, tenant={}, msg={}",
    31. dataId, group, tenant, ioe.toString());
    32. }
    33. log.warn(agent.getName(), "[get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}", dataId,
    34. group, tenant, ContentUtils.truncateContent(content));
    35. content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);
    36. cr.setContent(content);
    37. configFilterChainManager.doFilter(null, cr);
    38. content = cr.getContent();
    39. return content;
    40. }

    这里很明显

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

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

    1. static public String getFailover(String serverName, String dataId, String group, String tenant) {
    2. File localPath = getFailoverFile(serverName, dataId, group, tenant);
    3. if (!localPath.exists() || !localPath.isFile()) {
    4. return null;
    5. }
    6. try {
    7. return readFile(localPath);
    8. } catch (IOException ioe) {
    9. log.error(serverName, "NACOS-XXXX","get failover error, " + localPath + ioe.toString());
    10. return null;
    11. }
    12. }
    13. -----------
    14. static File getFailoverFile(String serverName, String dataId, String group, String tenant) {
    15. File tmp = new File(LOCAL_SNAPSHOT_PATH, serverName + "_nacos");
    16. tmp = new File(tmp, "data");
    17. if (StringUtils.isBlank(tenant)) {
    18. tmp = new File(tmp, "config-data");
    19. } else
    20. {
    21. tmp = new File(tmp, "config-data-tenant");
    22. tmp = new File(tmp, tenant);
    23. }
    24. return new File(new File(tmp, group), dataId);
    25. }

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

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

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

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

    点进httpGet方法里面有一行

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

    其中这就是轮询服务端

    1. public String getCurrentServerAddr() {
    2. if (StringUtils.isBlank(currentServerAddr)) {
    3. currentServerAddr = iterator().next();
    4. }
    5. return currentServerAddr;
    6. }

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

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

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

    1. private void registerNacosListenersForApplications() {
    2. if (isRefreshEnabled()) {
    3. for (NacosPropertySource propertySource : NacosPropertySourceRepository
    4. .getAll()) {
    5. if (!propertySource.isRefreshable()) {
    6. continue;
    7. }
    8. String dataId = propertySource.getDataId();
    9. registerNacosListener(propertySource.getGroup(), dataId);
    10. }
    11. }
    12. }

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

    接着看

    1. private void registerNacosListener(final String groupKey, final String dataKey) {
    2. String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
    3. Listener listener = listenerMap.computeIfAbsent(key,
    4. lst -> new AbstractSharedListener() {
    5. @Override
    6. public void innerReceive(String dataId, String group,
    7. String configInfo) {
    8. refreshCountIncrement();
    9. nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
    10. // todo feature: support single refresh for listening
    11. applicationContext.publishEvent(
    12. new RefreshEvent(this, null, "Refresh Nacos config"));
    13. if (log.isDebugEnabled()) {
    14. log.debug(String.format(
    15. "Refresh Nacos config group=%s,dataId=%s,configInfo=%s",
    16. group, dataId, configInfo));
    17. }
    18. }
    19. });
    20. try {
    21. configService.addListener(dataKey, groupKey, listener);
    22. }
    23. catch (NacosException e) {
    24. log.warn(String.format(
    25. "register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey,
    26. groupKey), e);
    27. }
    28. }

    看这一行:

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

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

    来看

    1. @Override
    2. public void onApplicationEvent(ApplicationEvent event) {
    3. if (event instanceof ApplicationReadyEvent) {
    4. handle((ApplicationReadyEvent) event);
    5. }
    6. else if (event instanceof RefreshEvent) {
    7. handle((RefreshEvent) event);
    8. }
    9. }

    重点看这个else  if的方法

    1. public void handle(RefreshEvent event) {
    2. if (this.ready.get()) { // don't handle events before app is ready
    3. log.debug("Event received " + event.getEventDesc());
    4. Set keys = this.refresh.refresh();
    5. log.info("Refresh keys changed: " + keys);
    6. }
    7. }

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

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

    先看环境相关的refreshEnvironment:

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

    然后看刷新bean的

    1. this.scope.refreshAll();
    2. ----------
    3. public void refreshAll() {
    4. super.destroy();
    5. this.context.publishEvent(new RefreshScopeRefreshedEvent());
    6. }

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

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

    1. @Override
    2. public void destroy() {
    3. List errors = new ArrayList();
    4. Collection wrappers = this.cache.clear();
    5. for (BeanLifecycleWrapper wrapper : wrappers) {
    6. try {
    7. Lock lock = this.locks.get(wrapper.getName()).writeLock();
    8. lock.lock();
    9. try {
    10. wrapper.destroy();
    11. }
    12. finally {
    13. lock.unlock();
    14. }
    15. }
    16. catch (RuntimeException e) {
    17. errors.add(e);
    18. }
    19. }
    20. if (!errors.isEmpty()) {
    21. throw wrapIfNecessary(errors.get(0));
    22. }
    23. this.errors.clear();
    24. }
    25. -----
    26. @Override
    27. public Object get(String name, ObjectFactory objectFactory) {
    28. BeanLifecycleWrapper value = this.cache.put(name,
    29. new BeanLifecycleWrapper(name, objectFactory));
    30. this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
    31. try {
    32. return value.getBean();
    33. }
    34. catch (RuntimeException e) {
    35. this.errors.put(name, e);
    36. throw e;
    37. }
    38. }

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

    1. private static class BeanLifecycleWrapper {
    2. private final String name;
    3. private final ObjectFactory objectFactory;
    4. private Object bean;
    5. private Runnable callback;
    6. BeanLifecycleWrapper(String name, ObjectFactory objectFactory) {
    7. this.name = name;
    8. this.objectFactory = objectFactory;
    9. }
    10. public String getName() {
    11. return this.name;
    12. }
    13. public void setDestroyCallback(Runnable callback) {
    14. this.callback = callback;
    15. }
    16. public Object getBean() {
    17. if (this.bean == null) {
    18. synchronized (this.name) {
    19. if (this.bean == null) {
    20. this.bean = this.objectFactory.getObject();
    21. }
    22. }
    23. }
    24. return this.bean;
    25. }
    26. public void destroy() {
    27. if (this.callback == null) {
    28. return;
    29. }
    30. synchronized (this.name) {
    31. Runnable callback = this.callback;
    32. if (callback != null) {
    33. callback.run();
    34. }
    35. this.callback = null;
    36. this.bean = null;
    37. }
    38. }
    39. 。。。。。。省略

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

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

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

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

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

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

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

    1. /**
    2. * Returns the path of cache file in server.
    3. */
    4. public static File targetBetaFile(String dataId, String group, String tenant) {
    5. File file = null;
    6. if (StringUtils.isBlank(tenant)) {
    7. file = new File(EnvUtil.getNacosHome(), BETA_DIR);
    8. } else {
    9. file = new File(EnvUtil.getNacosHome(), TENANT_BETA_DIR);
    10. file = new File(file, tenant);
    11. }
    12. file = new File(file, group);
    13. file = new File(file, dataId);
    14. return file;
    15. }

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

    其中

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

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

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

    从DumpService开始

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

    我们看ExternalDumpService

    1. @PostConstruct
    2. @Override
    3. protected void init() throws Throwable {
    4. dumpOperate(processor, dumpAllProcessor, dumpAllBetaProcessor, dumpAllTagProcessor);
    5. }

    然后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事件 

    1. updateMd5(groupKey, md5, lastModifiedTs);
    2. ---------
    3. public static void updateMd5(String groupKey, String md5, long lastModifiedTs) {
    4. CacheItem cache = makeSure(groupKey);
    5. if (cache.md5 == null || !cache.md5.equals(md5)) {
    6. cache.md5 = md5;
    7. cache.lastModifiedTs = lastModifiedTs;
    8. NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey));
    9. }
    10. }

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

    1. public LongPollingService() {
    2. allSubs = new ConcurrentLinkedQueue();
    3. ConfigExecutor.scheduleLongPolling(new StatTask(), 0L, 10L, TimeUnit.SECONDS);
    4. // Register LocalDataChangeEvent to NotifyCenter.
    5. NotifyCenter.registerToPublisher(LocalDataChangeEvent.class, NotifyCenter.ringBufferSize);
    6. // Register A Subscriber to subscribe LocalDataChangeEvent.
    7. NotifyCenter.registerSubscriber(new Subscriber() {
    8. @Override
    9. public void onEvent(Event event) {
    10. if (isFixedPolling()) {
    11. // Ignore.
    12. } else {
    13. if (event instanceof LocalDataChangeEvent) {
    14. LocalDataChangeEvent evt = (LocalDataChangeEvent) event;
    15. ConfigExecutor.executeLongPolling(new DataChangeTask(evt.groupKey, evt.isBeta, evt.betaIps));
    16. }
    17. }
    18. }
    19. @Override
    20. public Class subscribeType() {
    21. return LocalDataChangeEvent.class;
    22. }
    23. });
    24. }

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

    Iterator iter = allSubs.iterator(); 

    然后响应变化发生的key

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

    其中 run方法的

    for (Iterator iter = allSubs.iterator(); iter.hasNext(); )

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

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

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

    回到上面的核心方法

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

    最终定位到DefaultPublisher的publish方法

    1. @Override
    2. public boolean publish(Event event) {
    3. checkIsStart();
    4. boolean success = this.queue.offer(event);
    5. if (!success) {
    6. LOGGER.warn("Unable to plug in due to interruption, synchronize sending time, event : {}", event);
    7. receiveEvent(event);
    8. return true;
    9. }
    10. return true;
    11. }

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

    其中

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

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

    1. @Autowired
    2. public AsyncNotifyService(ServerMemberManager memberManager) {
    3. this.memberManager = memberManager;
    4. // Register ConfigDataChangeEvent to NotifyCenter.
    5. NotifyCenter.registerToPublisher(ConfigDataChangeEvent.class, NotifyCenter.ringBufferSize);
    6. // Register A Subscriber to subscribe ConfigDataChangeEvent.
    7. NotifyCenter.registerSubscriber(new Subscriber() {
    8. @Override
    9. public void onEvent(Event event) {
    10. // Generate ConfigDataChangeEvent concurrently
    11. if (event instanceof ConfigDataChangeEvent) {
    12. ConfigDataChangeEvent evt = (ConfigDataChangeEvent) event;
    13. long dumpTs = evt.lastModifiedTs;
    14. String dataId = evt.dataId;
    15. String group = evt.group;
    16. String tenant = evt.tenant;
    17. String tag = evt.tag;
    18. Collection<Member> ipList = memberManager.allMembers();
    19. // In fact, any type of queue here can be
    20. Queue<NotifySingleTask> queue = new LinkedList<NotifySingleTask>();
    21. for (Member member : ipList) {
    22. queue.add(new NotifySingleTask(dataId, group, tenant, tag, dumpTs, member.getAddress(),
    23. evt.isBeta));
    24. }
    25. ConfigExecutor.executeAsyncNotify(new AsyncTask(nacosAsyncRestTemplate, queue));
    26. }
    27. }
    28. @Override
    29. public Classextends Event> subscribeType() {
    30. return ConfigDataChangeEvent.class;
    31. }
    32. });
    33. }

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

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

    再补充一点客户端的ClientWorker

    1. @SuppressWarnings("PMD.ThreadPoolCreationRule")
    2. public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager,
    3. final Properties properties) {
    4. this.agent = agent;
    5. this.configFilterChainManager = configFilterChainManager;
    6. // Initialize the timeout parameter
    7. init(properties);
    8. this.executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
    9. @Override
    10. public Thread newThread(Runnable r) {
    11. Thread t = new Thread(r);
    12. t.setName("com.alibaba.nacos.client.Worker." + agent.getName());
    13. t.setDaemon(true);
    14. return t;
    15. }
    16. });
    17. this.executorService = Executors
    18. .newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
    19. @Override
    20. public Thread newThread(Runnable r) {
    21. Thread t = new Thread(r);
    22. t.setName("com.alibaba.nacos.client.Worker.longPolling." + agent.getName());
    23. t.setDaemon(true);
    24. return t;
    25. }
    26. });
    27. this.executor.scheduleWithFixedDelay(new Runnable() {
    28. @Override
    29. public void run() {
    30. try {
    31. checkConfigInfo();
    32. } catch (Throwable e) {
    33. LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
    34. }
    35. }
    36. }, 1L, 10L, TimeUnit.MILLISECONDS);
    37. }

    其中

    1. public void checkConfigInfo() {
    2. // Dispatch taskes.
    3. int listenerSize = cacheMap.size();
    4. // Round up the longingTaskCount.
    5. int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());
    6. if (longingTaskCount > currentLongingTaskCount) {
    7. for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {
    8. // The task list is no order.So it maybe has issues when changing.
    9. executorService.execute(new LongPollingRunnable(i));
    10. }
    11. currentLongingTaskCount = longingTaskCount;
    12. }
    13. }

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

  • 相关阅读:
    v-for 中 key的作用和原理
    Javascript知识【BootStrap】
    【三】kubernetes kuboard部署分布式系统
    Maven学习
    Unity入门04——Unity重要组件和API(1)
    rust 多线程
    数学建模2019B “同心协力”策略研究
    JAVACPU占用过高、内存泄漏问题排查
    一个越南程序员的阿里巴巴之旅
    力扣2860 补9.20
  • 原文地址:https://blog.csdn.net/xjk201/article/details/125891788