nacos統一配置中心原始碼解析

努力工作的小碼農發表於2021-01-11

配置檔案想必大家都很熟悉,無論什麼架構 都離不開配置,雖然spring boot已經大大簡化了配置,但如果服務很多 環境也好幾個,管理配置起來還是很麻煩,並且每次改完配置都需要重啟服務,nacos config出現就解決了這些問題,它把配置統一放到服務進行管理,客戶端這邊進行有需要的獲取,可以實時對配置進行修改和釋出

如何使用nacos config

首先需要引入nacos config jar包

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>2.2.1.RELEASE</version>
</dependency>

 在nacos控制檯提前配置需要的配置檔案

 

 配置檔案格式支援text、json、xml、yaml、html、properties,注意spring boot啟動支援的配置檔案格式只能為yaml或properties格式,其它格式的配置檔案需要後續我們自己寫程式碼去獲取

我們來看db.properties也是就資料庫配置

 

 data id就是對應配置檔案id,group為分組,配置內容就是properties格式的

再來看bootstrap.properties如何引用這個配置檔案

spring.application.name=nacos-config
server.port=20200

#名稱空間
spring.cloud.nacos.config.namespace=${nacos_register_namingspace:0ca74337-8f42-49c3-aec9-32f268a937c4}
#組名
spring.cloud.nacos.config.group=${spring.application.name}
#檔案格式
spring.cloud.nacos.config.file-extension=properties
#nacos server地址
spring.cloud.nacos.config.server-addr=localhost:8848

#載入配置檔案
spring.cloud.nacos.config.ext-config[0].data-id=nacos.properties
spring.cloud.nacos.config.ext-config[1].data-id=db.properties
spring.cloud.nacos.config.ext-config[2].data-id=mybatis-plus.properties

 

注意 載入配置檔案的分組名預設為DEFAULT_GROUP,如需指定分組 需要再指定

spring.cloud.nacos.config.ext-config[0].data-id=nacos.properties
spring.cloud.nacos.config.ext-config[0].group=${spring.cloud.nacos.config.group}
#或者
spring.cloud.nacos.config.ext-config[1].data-id=undertow.properties
spring.cloud.nacos.config.ext-config[1].group=MY_DEFAULT

 

在這裡解釋下namespace和group的概念,namespace可以用來解決不同環境的問題,group是來管理配置分組的,它們的關係如下圖

 spring boot啟動容器如何載入nacos config配置檔案

 

 

這個配置作用是spring在啟動之間準備上下文時會啟用這個配置 來匯入nacos相關配置檔案,為後續容器啟動做準備

來看NacosConfigBootstrapConfiguration這個配置類

 

 

NacosConfigProperties:對應我們上面在bootstrap.properties中對應的配置資訊

NacosConfigManager: 持有NacosConfigProperties和ConfigService,ConfigService用來查詢 釋出配置的相關介面

NacosPropertySourceLocator:它實現了PropertySourceLocator ,spring boot啟動時呼叫PropertySourceLocator.locate(env)用來載入配置資訊,下面來看相關原始碼

/******************************************NacosPropertySourceLocator******************************************/
public PropertySource<?> locate(Environment env) {
    ConfigService configService = this.nacosConfigProperties.configServiceInstance();
    if (null == configService) {
        log.warn("no instance of config service found, can't load config from nacos");
        return null;
    } else {
        long timeout = (long)this.nacosConfigProperties.getTimeout();
        this.nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
        String name = this.nacosConfigProperties.getName();
        String dataIdPrefix = this.nacosConfigProperties.getPrefix();
        if (StringUtils.isEmpty(dataIdPrefix)) {
            dataIdPrefix = name;
        }

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

        CompositePropertySource composite = new CompositePropertySource("NACOS");
        // 載入共享的配置檔案 不同指定分組 預設DEFAULT_GROUP,對應配置spring.cloud.nacos.config.sharedDataids=shared_1.properties,shared_2.properties
        this.loadSharedConfiguration(composite);
        // 對應spring.cloud.nacos.config.ext-config[0].data-id=nacos.properties的配置
        this.loadExtConfiguration(composite);
        // 載入當前應用配置
        this.loadApplicationConfiguration(composite, dataIdPrefix, this.nacosConfigProperties, env);
        return composite;
    }
}

// 看一個載入實現即可 流程都差不多 具體實現在NacosPropertySourceBuilder.loadNacosData()方法完成
/******************************************具體實現在NacosPropertySourceBuilder******************************************/
private Properties loadNacosData(String dataId, String group, String fileExtension) {
        String data = null;

        try {
            // 向nacos server拉取配置檔案
            data = this.configService.getConfig(dataId, group, this.timeout);
            if (!StringUtils.isEmpty(data)) {
                log.info(String.format("Loading nacos data, dataId: '%s', group: '%s'", dataId, group));
                // spring boot配置當然只支援properties和yaml檔案格式
                if (fileExtension.equalsIgnoreCase("properties")) {
                    Properties properties = new Properties();
                    properties.load(new StringReader(data));
                    return properties;
                }

                if (fileExtension.equalsIgnoreCase("yaml") || fileExtension.equalsIgnoreCase("yml")) {
                    YamlPropertiesFactoryBean yamlFactory = new YamlPropertiesFactoryBean();
                    yamlFactory.setResources(new Resource[]{new ByteArrayResource(data.getBytes())});
                    return yamlFactory.getObject();
                }
            }
        } catch (NacosException var6) {
            log.error("get data from Nacos error,dataId:{}, ", dataId, var6);
        } catch (Exception var7) {
            log.error("parse data from Nacos error,dataId:{},data:{},", new Object[]{dataId, data, var7});
        }

        return EMPTY_PROPERTIES;
    }

至此我們在nacos上配置的properties和yaml檔案都載入到spring配置檔案中來了,後面可通過context.Environment.getProperty(propertyName)來獲取相關配置資訊

配置如何隨spring boot載入進來我們說完了,接來下來看修改完配置後如何實時重新整理

nacos config動態重新整理

 當nacos config更新後,根據配置中的refresh屬性來判斷是否重新整理配置,配置如下

spring.cloud.nacos.config.ext-config[0].refresh=true

首先sprin.factories 配置了EnableAutoConfiguration=NacosConfigAutoConfiguration,NacosConfigAutoConfiguration配置類會注入一個NacosContextRefresher,它首先監聽了ApplicationReadyEvent,然後註冊一個nacos listener用來監聽nacos config配置修改後釋出一個spring refreshEvent用來重新整理配置和應用

public class NacosContextRefresher implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware

public void onApplicationEvent(ApplicationReadyEvent event) {
    // 只註冊一次
    if (this.ready.compareAndSet(false, true)) {
        this.registerNacosListenersForApplications();
    }
}
    
private void registerNacosListenersForApplications() {
    if (this.refreshProperties.isEnabled()) {
        Iterator var1 = NacosPropertySourceRepository.getAll().iterator();
        while(var1.hasNext()) {
            NacosPropertySource nacosPropertySource = (NacosPropertySource)var1.next();
            // 對應剛才所說的配置 需要配置檔案是否需要重新整理
            if (nacosPropertySource.isRefreshable()) {
                String dataId = nacosPropertySource.getDataId();
                // 註冊nacos監聽器
                this.registerNacosListener(nacosPropertySource.getGroup(), dataId);
            }
        }
    }

}
    
private void registerNacosListener(final String group, final String dataId) {
    Listener listener = (Listener)this.listenerMap.computeIfAbsent(dataId, (i) -> {
        return new Listener() {
            public void receiveConfigInfo(String configInfo) {
                NacosContextRefresher.refreshCountIncrement();
                String md5 = "";
                if (!StringUtils.isEmpty(configInfo)) {
                    try {
                        MessageDigest md = MessageDigest.getInstance("MD5");
                        md5 = (new BigInteger(1, md.digest(configInfo.getBytes("UTF-8")))).toString(16);
                    } catch (UnsupportedEncodingException | NoSuchAlgorithmException var4) {
                        NacosContextRefresher.log.warn("[Nacos] unable to get md5 for dataId: " + dataId, var4);
                    }
                }
                // 新增重新整理記錄
                NacosContextRefresher.this.refreshHistory.add(dataId, md5);
                // 釋出一個spring refreshEvent事件 對應監聽器為RefreshEventListener 該監聽器會完成配置的更新應用
                NacosContextRefresher.this.applicationContext.publishEvent(new RefreshEvent(this, (Object)null, "Refresh Nacos config"));
                if (NacosContextRefresher.log.isDebugEnabled()) {
                    NacosContextRefresher.log.debug("Refresh Nacos config group " + group + ",dataId" + dataId);
                }

            }
            public Executor getExecutor() {
                return null;
            }
        };
    });

    try {
        this.configService.addListener(dataId, group, listener);
    } catch (NacosException var5) {
        var5.printStackTrace();
    }

}

我們說完了nacos config動態重新整理,那麼肯定有對應的動態監聽,nacos config會監聽nacos server上配置的更新狀態

nacos config動態監聽

一般來說客戶端和服務端資料互動無非就兩種方式

pull:客戶端主動從伺服器拉取資料

push: 由服務端主動向客戶端推送資料

這兩種模式優缺點各不一樣,pull模式需要考慮的是什麼時候向服務端拉取資料 可能會存在資料延遲問題,而push模式需要客戶端和服務端維護一個長連線 如果客戶端較多會給服務端造成壓力 但它的實時性會更好

nacos採用的是pull模式,但它作了優化 可以看做是pull+push,客戶端會輪詢向服務端發出一個長連線請求,這個長連線最多30s就會超時,服務端收到客戶端的請求會先判斷當前是否有配置更新,有則立即返回

如果沒有服務端會將這個請求拿住“hold”29.5s加入佇列,最後0.5s再檢測配置檔案無論有沒有更新都進行正常返回,但等待的29.5s期間有配置更新可以提前結束並返回,下面會在原始碼中講解具體怎麼處理的

nacos client處理

動態監聽的發起是在ConfigService的實現類NacosConfigService的構造方法中,它是對外nacos config api介面,在之前載入配置檔案和NacosContextRefresher構造方法中都會獲取或建立

 

 

 

 這裡都會先判斷是否已經建立了ConfigServer,沒有則例項化一個NacosConfigService,來看它的建構函式

/***************************************** NacosConfigService *****************************************/
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);
    // 用來向nacos server發起請求的代理,這裡用到了裝飾模式
    agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
    agent.start();
    // 客戶端的一個工作類,agent作為它的構造傳參 可猜想到裡面肯定會做一些遠端呼叫
    worker = new ClientWorker(agent, configFilterChainManager, properties);
}

/***************************************** ClientWorker *****************************************/
public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager, final Properties properties) {
    this.agent = agent;
    this.configFilterChainManager = configFilterChainManager;

    // Initialize the timeout parameter

    init(properties);
    // 這個執行緒池只有一個核心執行緒 用來執行checkConfigInfo()方法
    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;
        }
    });
    
    // 執行一個呼叫checkConfigInfo()方法的週期性任務,每10ms執行一次,首次執行延遲1ms後執行
    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);
}

NacosConfigService構造方法主要建立一個agent 它是用來向nacos server發出請求的,然後又建立了一個clientwoker,它的構造方法建立了兩個執行緒池,第一個執行緒池只有一個核心執行緒,它會執行一個週期性任務只用來呼叫checkconfiginfo()方法,第二個執行緒是後續由需要執行執行緒的地方都交給它來執行,下面重點來看checkconfiginfo()方法

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;
    }
}
AtomicReference<Map<String, CacheData>> cacheMap = new AtomicReference<Map<String, CacheData>>(
new HashMap<String, CacheData>());

 cacheMap:快取著需要重新整理的配置,它是在呼叫ConfigService 新增監聽器方式時會放入,可以自定義監聽配置重新整理

// 新增一個config監聽器,用來監聽dataId為ErrorCode,group為DEFAULT_GROUP的config
configService.addListener("ErrorCode","DEFAULT_GROUP",new Listener() {
    @Override
    public Executor getExecutor() {
        return null;
    }

    @Override
    public void receiveConfigInfo(String s) { //當配置更新時會呼叫監聽器該方法
        Map<String, Map<String, String>> map = JSON.parseObject(s, Map.class);
        // 根據自己的業務需要來處理
    }
});

這裡採用了一個策略:將cacheMap中的數量以3000分一個組,分別建立一個LongPollingRunnable用來監聽配置更新,這個LongPollingRunnable就是我們之前所說的長連線任務,來看這個長連線任務

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
            for (CacheData cacheData : cacheMap.get().values()) {
                if (cacheData.getTaskId() == taskId) {
                    cacheDatas.add(cacheData);
                    try {
                        // 1、檢查本地配置
                        checkLocalConfig(cacheData);
                        if (cacheData.isUseLocalConfigInfo()) {
                            cacheData.checkListenerMd5();
                        }
                    } catch (Exception e) {
                        LOGGER.error("get local config info error", e);
                    }
                }
            }

            // 2、向nacos server發出一個長連線 30s超時,返回nacos server有更新過的dataIds
            List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
            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 {
                    // 3、向nacos server請求獲取config最新內容
                    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]);
                    }
                } 
            }
            // 4、對有變化的config呼叫對應監聽器去處理
            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) {
            // 發生異常延遲執行
            executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS);
        }
    }
}

 這個長輪詢主要做了4個步驟

  1. 檢查本地配置,如果存在本地配置,並且與快取中的本地配置版本不一樣,把本地配置內容更新到快取,並觸發事件,這塊原始碼比較簡單,讀者跟到原始碼一讀編制
  2. 向nacos server發出一個長連線,30s超時,nacos server會返回有變化的dataIds
  3. 根據變化的dataId,從服務端拉取最新的配置內容然後更新到快取中
  4. 對有變化的配置 觸發事件監聽器來處理

講完了nacos client處理流程,再來看服務端這邊怎麼處理這個長連線的

nacos server處理

服務端長連線介面是/config/listener,對應原始碼包為config

/****************************************** ConfigController ******************************************/
@PostMapping("/listener")
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
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);
    // 需要檢查更新的config資訊
    Map<String, String> clientMd5Map;
    try {
        clientMd5Map = MD5Util.getClientMd5Map(probeModify);
    } catch (Throwable e) {
        throw new IllegalArgumentException("invalid probeModify");
    }
    
    // 長連線處理
    inner.doPollingConfig(request, response, clientMd5Map, probeModify.length());
}

/****************************************** ConfigServletInner ******************************************/
public String doPollingConfig(HttpServletRequest request, HttpServletResponse response,
            Map<String, String> clientMd5Map, int probeRequestSize) throws IOException {
    
    // 判斷是否支援長輪詢
    if (LongPollingService.isSupportLongPolling(request)) {
        // 長輪詢處理
        longPollingService.addLongPollingClient(request, response, clientMd5Map, probeRequestSize);
        return HttpServletResponse.SC_OK + "";
    }
    
    // 不支援長輪詢,直接與當前配置作比較,返回有變更的配置
    List<String> changedGroups = MD5Util.compareMd5(request, response, clientMd5Map);
    
    // Compatible with short polling result.
    String oldResult = MD5Util.compareMd5OldResult(changedGroups);
    String newResult = MD5Util.compareMd5ResultString(changedGroups);
    
    /*
    * 省略
    * 會響應變更的配置資訊
    */
    return HttpServletResponse.SC_OK + "";
}

/****************************************** LongPollingService ******************************************/
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");
    
    // 服務端這邊最多處理時長29.5s,需要留0.5s來返回,以免客戶端那邊超時
    int delayTime = SwitchService.getSwitchInteger(SwitchService.FIXED_DELAY_TIME, 500);
    // Add delay time for LoadBalance, and one response is returned 500 ms in advance to avoid client timeout.
    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();
        List<String> changedGroups = MD5Util.compareMd5(req, rsp, clientMd5Map);
        if (changedGroups.size() > 0) {
            generateResponse(req, rsp, changedGroups);
            // log....
            return;
        } else if (noHangUpFlag != null && noHangUpFlag.equalsIgnoreCase(TRUE_STR)) {
            // log....
            return;
        }
    }
    String ip = RequestUtil.getRemoteIp(req);
    
    // 將http響應交給非同步執行緒,返回一個非同步響應上下文, 當配置更新後可以主動呼叫及時返回,不用非等待29.5s
    final AsyncContext asyncContext = req.startAsync();
    
    // AsyncContext.setTimeout() is incorrect, Control by oneself
    asyncContext.setTimeout(0L);
    // 執行客戶端長連線任務,
    ConfigExecutor.executeLongPolling(
            new ClientLongPolling(asyncContext, clientMd5Map, ip, probeRequestSize, timeout, appName, tag));
}

/****************************************** ClientLongPolling ******************************************/
class ClientLongPolling implements Runnable {
        
    @Override
    public void run() {
        // 提交一個任務,延遲29.5s執行
        asyncTimeoutFuture = ConfigExecutor.scheduleLongPolling(new Runnable() {
            @Override
            public void run() {
                try {
                    getRetainIps().put(ClientLongPolling.this.ip, System.currentTimeMillis());
                    
                    // Delete subsciber's relations.
                    allSubs.remove(ClientLongPolling.this);
                    
                    if (isFixedPolling()) {
                        // 檢查變更配置 並相應
                        List<String> changedGroups = MD5Util
                                .compareMd5((HttpServletRequest) asyncContext.getRequest(),
                                        (HttpServletResponse) asyncContext.getResponse(), clientMd5Map);
                        if (changedGroups.size() > 0) {
                            sendResponse(changedGroups);
                        } else {
                            sendResponse(null);
                        }
                    } else {
                        sendResponse(null);
                    }
                } catch (Throwable t) {
                    LogUtil.DEFAULT_LOG.error("long polling error:" + t.getMessage(), t.getCause());
                }
                
            }
            
        }, timeoutTime, TimeUnit.MILLISECONDS);
        
        allSubs.add(this);
    }
}

final Queue<ClientLongPolling> allSubs

 

上面大部分地方都比較好懂,主要解釋下ClientLongPolling作用,它首先會提交一個任務,無論配置有沒有更新 最終都會進行響應,延遲29.5s執行,然後會把自己新增到一個佇列中,之前說過,服務端這邊配置有更新後 會找出正在等待配置更新的長連線任務,提前結束這個任務並返回,

來看這一步是怎麼處理的

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

class DataChangeTask implements Runnable {
        
    @Override
    public void run() {
        try {
            ConfigCacheService.getContentBetaMd5(groupKey);
            // 找出等在該配置的長連線,然後進行提前返回
            for (Iterator<ClientLongPolling> iter = allSubs.iterator(); iter.hasNext(); ) {
                ClientLongPolling clientSub = iter.next();
                if (clientSub.clientMd5Map.containsKey(groupKey)) {
                    // If published tag is not in the beta list, then it skipped.
                    if (isBeta && !CollectionUtils.contains(betaIps, clientSub.ip)) {
                        continue;
                    }
                    
                    // If published tag is not in the tag list, then it skipped.
                    if (StringUtils.isNotBlank(tag) && !tag.equals(clientSub.tag)) {
                        continue;
                    }
                    
                    getRetainIps().put(clientSub.ip, System.currentTimeMillis());
                    iter.remove(); // Delete subscribers' relationships.
                    clientSub.sendResponse(Arrays.asList(groupKey));
                }
            }
        } catch (Throwable t) {
            LogUtil.DEFAULT_LOG.error("data change error: {}", ExceptionUtil.getStackTrace(t));
        }
    }
}

 

LongPollingService建構函式中,會註冊一個訂閱,用來監聽LocalDataChangeEvent,當發生該事件時,會執行一個資料變更任務,這個任務就是找出等在配置的長連線,提前返回

我們在nacos控制檯修改一個配置檔案進行釋出,會呼叫ConfigController.publishConfig介面,但這個介面釋出的是ConfigDataChangeEvent事件,大意了。。。LocalDataChangeEvent事件釋出在ConfigCacheService,這裡怎麼呼叫的我就不深追,留給有興趣的讀者

至此nacos config動態監聽、重新整理就串聯起來了,nacos的相關原始碼都比較好理解,跟著原始碼追進去就一目瞭然了

相關文章