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()
- After the context is prepared, the program runs, and the eventpublishingrunnlistener publishes the ApplicationReadyEvent event. For details, see Microservice architecture * 2.3 Spring Cloud startup and loading configuration file source code analysis (taking Nacos as an example) 4. Program operation events in;
- As mentioned in 1.1 above, the event subscription mechanism is related to the Nacos context refresher (Nacos context updater) because there is a Nacos context refresher Onapplicationevent() method realizes listening to the event ApplicationReadyEvent (context ready event). The source code is as follows:
//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;
- NacosConfigService.getConfig(): use the configuration service to obtain the configuration;
- NacosPropertySourceBuilder.loadNacosData(): load configuration;
- NacosPropertySourceBuilder.build(): load and encapsulate the configuration;
- NacosPropertySourceLocator.loadNacosDataIfPresent(): judge whether to update the configuration;
- NacosPropertySourceLocator.loadApplicationConfiguration(): load the configuration according to the Data ID;
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;