Microservice architecture * 2.4 source code analysis of Nacos acquisition configuration and event subscription mechanism

preface

reference material:
<Spring Microservices in Action>
Principle and practice of Spring Cloud Alibaba microservice
"Spring cloud framework development tutorial in Silicon Valley of station B" Zhou Yang

In order to facilitate understanding and expression, the Nacos console and the Nacos registry are called the Nacos server (that is, the web interface), and the business service we write is called the Nacso client;

Due to the limited space, the source code analysis is divided into two parts. The first part focuses on the acquisition configuration and event subscription mechanism, and the second part focuses on the long polling timing mechanism;

Part I Microservice architecture * 2.3 Spring Cloud startup and loading configuration file source code analysis (taking Nacos as an example) It is mentioned in that reading the configuration in the Nacos server depends on the nacospropertysourcelocator Locate () method, our source code journey will start from this method;

1. The client obtains the configuration in the Nacos server

1.1 locate the Nacos configuration source nacospropertysourcelocator locate()

  • The main functions of this method are:
    • Initialize the ConfigService object, which is a class provided by the Nacos client to access and implement the basic operations of the configuration center;
    • Load the configuration corresponding to the shared configuration, extended configuration and application name in order;
  • The source code of the method is as follows:
@Override
public PropertySource<?> locate(Environment env) {
    //[long polling timing mechanism] obtain the configuration server instance, which is a class provided by the Nacos client to access and implement the basic operations of the configuration center
	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 attribute source generator
	nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
	String name = nacosConfigProperties.getName();

    //DataId prefix (here is Nacos config client)
	String dataIdPrefix = nacosConfigProperties.getPrefix();
	if (StringUtils.isEmpty(dataIdPrefix)) {
		dataIdPrefix = name;
	}
    //If the DataId prefix is not configured, use spring application. Value of the name attribute
	if (StringUtils.isEmpty(dataIdPrefix)) {
		dataIdPrefix = env.getProperty("spring.application.name");
	}
    //Create composite attribute source
	CompositePropertySource composite = new CompositePropertySource(NACOS_PROPERTY_SOURCE_NAME);

    //Load shared configuration
	loadSharedConfiguration(composite);
	//Load external configuration
	loadExtConfiguration(composite);
	//[breakpoint] load the configuration corresponding to the application name on the Nacos server
	loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);

	return composite;
}
  • Enter nacospropertysourcelocator Loadapplicationconfiguration() method, load the configuration according to the Data ID;
private void loadApplicationConfiguration(CompositePropertySource compositePropertySource, String dataIdPrefix, NacosConfigProperties properties, Environment environment) {
    //Get configuration format (yaml here)
	String fileExtension = properties.getFileExtension();
	//Get the nacosGroup (DEFAULT_GROUP here)
	String nacosGroup = properties.getGroup();
    //If we configure the prefix, we will obtain the configuration file according to the prefix (because we do not configure the prefix, here is nacos-config-client.yaml, which cannot be obtained)
	loadNacosDataIfPresent(compositePropertySource, dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
	for (String profile : environment.getActiveProfiles()) {
	    //Here is nacos-config-client-dev.yaml, which can be obtained
		String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
		//[breakpoint] load configuration
		loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup, fileExtension, true);
	}
}
  • Enter nacospropertysourcelocator Loadnacosdataifpresent() method to judge whether to update;
private void loadNacosDataIfPresent(final CompositePropertySource composite, final String dataId, final String group, String fileExtension, boolean isRefreshable) {
    //To obtain the updated configuration, it should be noted that the NacosContextRefresher class is related to the event subscription mechanism, which will be discussed in point 2 of this article
	if (NacosContextRefresher.getRefreshCount() != 0) {
		NacosPropertySource ps;
		if (!isRefreshable) {
			ps = NacosPropertySourceRepository.getNacosPropertySource(dataId);
		}
		else {
			ps = nacosPropertySourceBuilder.build(dataId, group, fileExtension, true);
		}

		composite.addFirstPropertySource(ps);
	}
	else {
	    //[breakpoint entry] if we don't update the configuration, follow the code below
		NacosPropertySource ps = nacosPropertySourceBuilder.build(dataId, group,fileExtension, isRefreshable);
		composite.addFirstPropertySource(ps);
	}
}
  • Enter nacospropertysourcebuilder Build () method to load and encapsulate the configuration;
NacosPropertySource build(String dataId, String group, String fileExtension, boolean isRefreshable) {
    //[breakpoint] load Nacos 
	Properties p = loadNacosData(dataId, group, fileExtension);
	//Encapsulate the obtained configuration into the NacosPropertySource
	NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId, propertiesToMap(p), new Date(), isRefreshable);	
	NacosPropertySourceRepository.collectNacosPropertySources(nacosPropertySource);
	return nacosPropertySource;
}
  • Enter nacospropertysourcebuilder Loadnacosdata() method,
private Properties loadNacosData(String dataId, String group, String fileExtension) {
	
    //Omit other codes

	try {
	    //[breakpoint entry] obtain the configuration according to dataId, group and other information
		data = configService.getConfig(dataId, group, timeout);
		
	}
	catch (NacosException e) {
		log.error("get data from Nacos error,dataId:{}, ", dataId, e);
	}
	return EMPTY_PROPERTIES;
}
  • Keep chasing and find it in nacosconfigservice The configuration is successfully obtained in the getconfiginner () method;


2. Event subscription mechanism configured by Nacos

2.1 listen to the ApplicationReadyEvent event and register the listener nacoscontextrefresh onApplicationEvent()

//Listen for ApplicationReadyEvent events
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
	if (this.ready.compareAndSet(false, true)) {
	    //[breakpoint] register the Nacos listener for the application
		this.registerNacosListenersForApplications();
	}
}

2.2 register the Nacos listener and listen for configuration changes registerNacosListener()

  • When the supervisor hears the ApplicationReadyEvent event, it will eventually call nacoscontextrefresh Registernacoslistener() method to register the Nacos listener. The source code is as follows:
private void registerNacosListener(final String group, final String dataId) {

	Listener listener = listenerMap.computeIfAbsent(dataId, i -> new Listener() {
	    //Accept callback of configuration change
		@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);
			//Publish RefreshEvent configuration change event
			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 {
	    //Listening configuration
		configService.addListener(dataId, group, listener);
	}
	catch (NacosException e) {
		e.printStackTrace();
	}
}
  • When receiving the callback of configuration change, it will pass ApplicationContext Publishevent() publishes a RefreshEvent event;
  • This event will be monitored by RefreshEventListener. The source code is as follows:
public void onApplicationEvent(ApplicationEvent event){
    if (event instanceof ApplicationReadyEvent) {
        //Listen for ApplicationReadyEvent events
        this.handle((ApplicationReadyEvent)event);
    } else if (event instanceof RefreshEvent) {
        //[2.3] listen for RefreshEvent events
        this.handle((RefreshEvent)event);
    }
}

2.3 monitor the configuration change and implement the change refresheventlistener handle()

  • The RefreshEventListener class uses RefreshEventListener The handle () method changes the configuration. The source code is as follows:
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);
    }
}

3. Summary of source code structure diagram

3.1 the client obtains the configuration source code structure diagram on the Nacos server

  • NacosPropertySourceLocator.locate(): initialize the ConfigService object and locate the configuration;
    • NacosPropertySourceLocator.loadApplicationConfiguration(): load the configuration according to the Data ID;
      • NacosPropertySourceLocator.loadNacosDataIfPresent(): judge whether to update the configuration;
        • NacosPropertySourceBuilder.build(): load and encapsulate the configuration;
          • NacosPropertySourceBuilder.loadNacosData(): load configuration;
            • NacosConfigService.getConfig(): use the configuration service to obtain the configuration;
              • NacosConfigService.getConfigInner(): finally get the configuration here;

3.2 event subscription mechanism configured by Nacos

  • After the context is prepared, the program runs, and the eventpublishingrunnlistener publishes the ApplicationReadyEvent event;
  • NacosContextRefresher.onApplicationEvent(): listen for ApplicationReadyEvent events;
    • NacosContextRefresher.registerNacosListener(): register a Nacos listener to listen for configuration changes;
  • When a change occurs, NacosContextRefresher publishes a RefreshEvent event;
  • RefreshEventListener.onApplicationEvent(): listen to ApplicationReadyEvent and RefreshEvent events at the same time;
    • RefreshEventListener.handle(): implement the change method;

last

Newcomer production, if there are mistakes, welcome to point out, thank you very much! Welcome to the official account and share some more everyday things. If you need to reprint, please mark the source!

Keywords: Distribution Spring Cloud Microservices config

Added by Daisy Cutter on Sun, 23 Jan 2022 15:10:22 +0200