天天看點

微服務架構 | *2.4 Nacos 擷取配置與事件訂閱機制的源碼分析

目錄

  • 前言
  • 1. 用戶端擷取 Nacos 伺服器裡的配置
    • 1.1 定位 Nacos 配置源 NacosPropertySourceLocator.locate()
  • 2. Nacos 配置的事件訂閱機制
    • 2.1 監聽 ApplicationReadyEvent 事件,注冊監聽器 NacosContextRefresher.onApplicationEvent()
    • 2.2 注冊 Nacos 監聽器,監聽配置變更 NacosContextRefresher.registerNacosListener()
    • 2.3 監聽配置變更,實施變更 RefreshEventListener.handle()
  • 3. 源碼結構圖小結
    • 3.1 用戶端擷取 Nacos 伺服器上的配置源碼結構圖
    • 3.2 Nacos 配置的事件訂閱機制
  • 最後

參考資料:

《Spring Microservices in Action》

《Spring Cloud Alibaba 微服務原理與實戰》

《B站 尚矽谷 SpringCloud 架構開發教程 周陽》

為友善了解與表達,這裡把 Nacos 控制台和 Nacos 注冊中心稱為 Nacos 伺服器(就是 web 界面那個),我們編寫的業務服務稱為 Nacso 用戶端;

由于篇幅有限,這裡将源碼分析分為上下兩篇,其中上篇講擷取配置與事件訂閱機制,下篇講長輪詢定時機制;

上篇《微服務架構 | *2.3 Spring Cloud 啟動及加載配置檔案源碼分析(以 Nacos 為例)》中提到,讀取 Nacos 伺服器裡的配置依靠的是

NacosPropertySourceLocator.locate()

方法,我們這次的源碼之旅将從這個方法開始;

  • 該方法的主要作用是:
    • 初始化 ConfigService 對象,這是 Nacos 用戶端提供的用于通路實作配置中心基本操作的類;
    • 按照順序分别加載共享配置、擴充配置、應用名稱對應的配置;
  • 方法源碼如下:
@Override
public PropertySource<?> locate(Environment env) {
    //【斷點步入 長輪詢定時機制】擷取配置伺服器執行個體,這是 Nacos 用戶端提供的用于通路實作配置中心基本操作的類
	ConfigService configService = nacosConfigProperties.configServiceInstance();
	if (null == configService) {
		log.warn("no instance of config service found, can't load config from nacos");
		return null;
	}
	long timeout = nacosConfigProperties.getTimeout();
	//Nacos 屬性源生成器
	nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
	String name = nacosConfigProperties.getName();

    //DataId 字首(這裡是 nacos-config-client)
	String dataIdPrefix = nacosConfigProperties.getPrefix();
	if (StringUtils.isEmpty(dataIdPrefix)) {
		dataIdPrefix = name;
	}
    //沒有配置 DataId 字首則用 spring.application.name 屬性的值
	if (StringUtils.isEmpty(dataIdPrefix)) {
		dataIdPrefix = env.getProperty("spring.application.name");
	}
    //建立複合屬性源
	CompositePropertySource composite = new CompositePropertySource(NACOS_PROPERTY_SOURCE_NAME);

    //加載共享配置
	loadSharedConfiguration(composite);
	//加載外部配置
	loadExtConfiguration(composite);
	//【斷點步入】加載 Nacos 伺服器上應用程式名對應的的配置
	loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);

	return composite;
}
           
  • 進入

    NacosPropertySourceLocator.loadApplicationConfiguration()

    方法,根據 Data ID 加載配置;
private void loadApplicationConfiguration(CompositePropertySource compositePropertySource, String dataIdPrefix, NacosConfigProperties properties, Environment environment) {
    //擷取配置格式(這裡是 yaml)
	String fileExtension = properties.getFileExtension();
	//擷取nacosGroup(這裡是 DEFAULT_GROUP)
	String nacosGroup = properties.getGroup();
    //如果我們配置了字首,則按字首擷取配置檔案(由于我們沒配置字首,這裡是 nacos-config-client.yaml 擷取不到)
	loadNacosDataIfPresent(compositePropertySource, dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
	for (String profile : environment.getActiveProfiles()) {
	    //這裡是 nacos-config-client-dev.yaml 可以擷取
		String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
		//【斷點步入】加載配置
		loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup, fileExtension, true);
	}
}
           
  • NacosPropertySourceLocator.loadNacosDataIfPresent()

    方法,判斷是否更新情況;
private void loadNacosDataIfPresent(final CompositePropertySource composite, final String dataId, final String group, String fileExtension, boolean isRefreshable) {
    //擷取更新的配置,需要注意,NacosContextRefresher 類與事件訂閱機制相關,本篇第2點将重點讨論
	if (NacosContextRefresher.getRefreshCount() != 0) {
		NacosPropertySource ps;
		if (!isRefreshable) {
			ps = NacosPropertySourceRepository.getNacosPropertySource(dataId);
		}
		else {
			ps = nacosPropertySourceBuilder.build(dataId, group, fileExtension, true);
		}

		composite.addFirstPropertySource(ps);
	}
	else {
	    //【斷點步入】如果我們沒有更新配置,則走下面代碼
		NacosPropertySource ps = nacosPropertySourceBuilder.build(dataId, group,fileExtension, isRefreshable);
		composite.addFirstPropertySource(ps);
	}
}
           
  • NacosPropertySourceBuilder.build()

    方法,加載并封裝配置;
NacosPropertySource build(String dataId, String group, String fileExtension, boolean isRefreshable) {
    //【斷點步入】加載 Nacos 
	Properties p = loadNacosData(dataId, group, fileExtension);
	//将擷取到的配置封裝到 NacosPropertySource
	NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId, propertiesToMap(p), new Date(), isRefreshable);	
	NacosPropertySourceRepository.collectNacosPropertySources(nacosPropertySource);
	return nacosPropertySource;
}
           
  • NacosPropertySourceBuilder.loadNacosData()

    方法,
private Properties loadNacosData(String dataId, String group, String fileExtension) {
	
    //省略其他代碼

	try {
	    //【斷點步入】根據 dataId、group 等資訊擷取配置
		data = configService.getConfig(dataId, group, timeout);
		
	}
	catch (NacosException e) {
		log.error("get data from Nacos error,dataId:{}, ", dataId, e);
	}
	return EMPTY_PROPERTIES;
}
           
  • 一直追下去發現在

    NacosConfigService.getConfigInner()

    方法裡成功擷取到配置;
微服務架構 | *2.4 Nacos 擷取配置與事件訂閱機制的源碼分析

  • 上下文準備完畢後,程式運作,EventPublishingRunListener 釋出 ApplicationReadyEvent 事件,詳情請見《微服務架構 | *2.3 Spring Cloud 啟動及加載配置檔案源碼分析(以 Nacos 為例)》中的《4. 程式運作事件》;
  • 上面 1.1 提到,事件訂閱機制與 NacosContextRefresher(Nacos上下文更新器) 相關,這是因為裡面有個

    NacosContextRefresher.onApplicationEvent()

    方法實作了對事件 ApplicationReadyEvent(上下文準備完畢事件) 的監聽,源碼如下:
//監聽 ApplicationReadyEvent 事件
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
	if (this.ready.compareAndSet(false, true)) {
	    //【斷點步入】為應用程式注冊 Nacos 監聽器
		this.registerNacosListenersForApplications();
	}
}
           

  • 當監聽到 ApplicationReadyEvent 事件後,最終會調用

    NacosContextRefresher.registerNacosListener()

    方法來實作 Nacos 監聽器的注冊,源碼如下:
private void registerNacosListener(final String group, final String dataId) {

	Listener listener = listenerMap.computeIfAbsent(dataId, i -> new Listener() {
	    //接受配置變更的回調
		@Override
		public void receiveConfigInfo(String configInfo) {
			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 (NoSuchAlgorithmException | UnsupportedEncodingException e) {
					log.warn("[Nacos] unable to get md5 for dataId: " + dataId, e);
				}
			}
			refreshHistory.add(dataId, md5);
			//釋出 RefreshEvent 配置變更事件
			applicationContext.publishEvent(new RefreshEvent(this, null, "Refresh Nacos config"));
			if (log.isDebugEnabled()) {
				log.debug("Refresh Nacos config group " + group + ",dataId" + dataId);
			}
		}

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

	try {
	    //監聽配置
		configService.addListener(dataId, group, listener);
	}
	catch (NacosException e) {
		e.printStackTrace();
	}
}
           
  • 當收到配置變更的回調時,會通過

    applicationContext.publishEvent()

    釋出一個 RefreshEvent 事件;
  • 該事件又會被 RefreshEventListener(事件更新監聽器) 監聽,源碼如下:
public void onApplicationEvent(ApplicationEvent event){
    if (event instanceof ApplicationReadyEvent) {
        //監聽 ApplicationReadyEvent 事件
        this.handle((ApplicationReadyEvent)event);
    } else if (event instanceof RefreshEvent) {
        //【斷點步入 2.3】監聽 RefreshEvent 事件
        this.handle((RefreshEvent)event);
    }
}
           

  • RefreshEventListener(事件更新監聽器) 類使用

    RefreshEventListener.handle()

    方法變更配置,源碼如下:
public void handle(RefreshEvent event) {
    if (this.ready.get()) {
        log.debug("Event received " + event.getEventDesc());
        Set<String> keys = this.refresh.refresh();
        log.info("Refresh keys changed: " + keys);
    }
}
           

  • NacosPropertySourceLocator.locate():初始化 ConfigService 對象,定位配置;
    • NacosPropertySourceLocator.loadApplicationConfiguration():根據 Data ID 加載配置;
      • NacosPropertySourceLocator.loadNacosDataIfPresent():判斷是否更新配置;
        • NacosPropertySourceBuilder.build():加載并封裝配置;
          • NacosPropertySourceBuilder.loadNacosData():加載配置;
            • NacosConfigService.getConfig():使用配置服務擷取配置;
              • NacosConfigService.getConfigInner():最終在這裡擷取到配置;

  • 上下文準備完畢,程式運作,EventPublishingRunListener 釋出

    ApplicationReadyEvent

    事件;
  • NacosContextRefresher.onApplicationEvent():監聽 ApplicationReadyEvent 事件;
    • NacosContextRefresher.registerNacosListener():注冊 Nacos 監聽器,監聽配置變更;
  • 變更發生時,NacosContextRefresher 釋出一個

    RefreshEvent

  • RefreshEventListener.onApplicationEvent():同時監聽 ApplicationReadyEvent 和 RefreshEvent 事件;
    • RefreshEventListener.handle():實施變更方法;

新人制作,如有錯誤,歡迎指出,感激不盡!

歡迎關注公衆号,會分享一些更日常的東西!

如需轉載,請标注出處!

繼續閱讀