【深入理解SpringCloud微服务】深入理解nacos配置中心(二)——客户端启动源码分析

服务端启动原理回顾

在上一篇的《宏观理解nacos配置中心原理》的文章中,我们提到SpringBoot在启动之后会调用PropertySourceLocator的locate()方法获取配置信息。
在这里插入图片描述
PropertySourceLocator的locate()方法是用于获取配置信息,具体如何获取由实现这个接口的实现类去实现。可以通过RPC从远程获取,或者读取本地某个文件,也可以查询数据库,但是这个接口规定返回的是PropertySource对象。

于是nacos就实现了一个NacosPropertySourceLocator。

在这里插入图片描述

NacosPropertySourceLocator的locate()方法里面通过NacosConfigService向nacos服务端发起RPC远程调用获取配置信息,然后把获取到的配置信息放入CompositePropertySource对象返回。

源码解析

SpringBoot如何调用到PropertySourceLocator的locate()方法

PropertySourceLocator的locate()方法是从prepareContext()方法调用进去的,我们现在SpringApplication的run()方法中找到prepareContext()方法。

	public ConfigurableApplicationContext run(String... args) {
		...
		try {
			...
			context = createApplicationContext();
			...
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			...
		}
		catch (...) {...}
		...
		return context;
	}

prepareContext()就在refreshContext()方法前调用。

在这里插入图片描述

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		...
		applyInitializers(context);
		...
	}

prepareContext方法中会把Environment对象设置到Spring容器中,然后调用applyInitializers方法。

在这里插入图片描述

	protected void applyInitializers(ConfigurableApplicationContext context) {
		for (ApplicationContextInitializer initializer : getInitializers()) {
			...
			initializer.initialize(context);
		}
	}

applyInitializers方法会获取所有的ApplicationContextInitializer,这些ApplicationContextInitializer都是在SpringApplication初始化的时候加载的。

在这里插入图片描述

这里就会调用到PropertySourceBootstrapConfiguration#initialize()方法,里面就会调用到PropertySourceLocator的locate方法。

	public void initialize(ConfigurableApplicationContext applicationContext) {
		// 创建一个CompositePropertySource对象
		CompositePropertySource composite = new CompositePropertySource(
				BOOTSTRAP_PROPERTY_SOURCE_NAME);
		...
		// 从Spring容器中获取environment对象
		ConfigurableEnvironment environment = applicationContext.getEnvironment();
		// 遍历所有的PropertySourceLocator
		for (PropertySourceLocator locator : this.propertySourceLocators) {
			PropertySource<?> source = null;
			// 调用PropertySourceLocator的locate方法获取配置信息
			// 返回PropertySource对象
			source = locator.locate(environment);
			...
			// PropertySource放入到CompositePropertySource中
			composite.addPropertySource(source);
			empty = false;
		}
		if (!empty) {
			// 获取到environment对象中的MutablePropertySources对象
			MutablePropertySources propertySources = environment.getPropertySources();
			...
			// 把CompositePropertySource放入MutablePropertySources对象中
			insertPropertySources(propertySources, composite);
			...
		}
	}

首先创建一个CompositePropertySource对象,用于存放每个PropertySourceLocator的locate方法返回的PropertySource对象。

然后循环遍历每一个PropertySourceLocator,调用locate方法,返回的PropertySource对象放入CompositePropertySource中。

最后从environment对象中取出MutablePropertySources对象,把CompositePropertySource放入MutablePropertySources中。

在这里插入图片描述

NacosPropertySourceLocator#locate()

nacos在spring-cloud-alibaba-nacos-config中的spring.factories文件中指定了自己的配置类NacosConfigBootstrapConfiguration,NacosConfigBootstrapConfiguration中通过@Bean注解配置了NacosPropertySourceLocator。

@Configuration
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigBootstrapConfiguration {

	...

	@Bean
	public NacosPropertySourceLocator nacosPropertySourceLocator(
			NacosConfigProperties nacosConfigProperties) {
		return new NacosPropertySourceLocator(nacosConfigProperties);
	}

}

在这里插入图片描述

由于nacos向Spring注册了自己的PropertySourceLocator——NacosPropertySourceLocator,那么自然就会调用到NacosPropertySourceLocator的locate方法。

	@Override
	public PropertySource<?> locate(Environment env) {

		...

		// 创建一个CompositePropertySource对象
		CompositePropertySource composite = new CompositePropertySource(
				NACOS_PROPERTY_SOURCE_NAME);
		// 加载共享配置
		loadSharedConfiguration(composite);
		// 加载扩展配置
		loadExtConfiguration(composite);
		// 加载应用程序配置
		loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
		
		// 返回CompositePropertySource对象
		return composite;
	}

我们知道nacos是有共享配置、扩展配置和应用程序配置之分的,这里就是分别加载共享配置、扩展配置和应用程序配置,越往后配置的优先级越高。

在这里插入图片描述

这三个方法,都会调用到loadNacosDataIfPresent()方法去获取配置。

在这里插入图片描述

NacosPropertySourceLocator#loadNacosDataIfPresent()

	private void loadNacosDataIfPresent(final CompositePropertySource composite,
		...
		// 调用loadNacosPropertySource方法获取配置信息,返回NacosPropertySource
		NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
				fileExtension, isRefreshable);
		// NacosPropertySource放入CompositePropertySource
		this.addFirstPropertySource(composite, propertySource, false);
	}

NacosPropertySourceLocator的loadNacosDataIfPresent()调用loadNacosPropertySource方法获取配置信息,返回NacosPropertySource,然后把NacosPropertySource放入CompositePropertySource。

在这里插入图片描述

	private NacosPropertySource loadNacosPropertySource(final String dataId,
			final String group, String fileExtension, boolean isRefreshable) {
		...
		return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
				isRefreshable);
	}

loadNacosPropertySource方法调用NacosPropertySourceBuilder的build方法。

在这里插入图片描述

NacosPropertySourceBuilder#build()

	NacosPropertySource build(String dataId, String group, String fileExtension,
			boolean isRefreshable) {
		Map<String, Object> p = loadNacosData(dataId, group, fileExtension);
		NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId,
				p, ...);
		...
		return nacosPropertySource;
	}

NacosPropertySourceBuilder的build方法中调用loadNacosData方法获取配置信息,loadNacosData方法返回的是一个Map结构,然后把Map封装成NacosPropertySource返回。

在这里插入图片描述

	private Map<String, Object> loadNacosData(String dataId, String group,
			String fileExtension) {
		String data = null;
		try {
			// 调用NacosConfigService获取配置信息
			data = configService.getConfig(dataId, group, timeout);
			...
			// 获取到的配置信息转成Map
			Map<String, Object> dataMap = NacosDataParserHandler.getInstance()
					.parseNacosData(data, fileExtension);
			return dataMap == null ? EMPTY_MAP : dataMap;
		}
		...
	}

在NacosPropertySourceBuilder的loadNacosData方法获中,调用NacosConfigService的getConfig方法获取配置信息,然后把返回的配置信息转成Map结构,最后返回该Map。

在这里插入图片描述

NacosConfigService#getConfig()

    public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
        return getConfigInner(namespace, dataId, group, timeoutMs);
    }

NacosConfigService的getConfig()调用getConfigInner方法。

在这里插入图片描述

    private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
        ...

		// 优先从本地配置文件获取
        String content = LocalConfigInfoProcessor.getFailover(worker.getAgentName(), dataId, group, tenant);
        if (content != null) {
            ...
            return content;
        }
        
        try {
        	// 本地没有,再调用ClientWorker从远程服务器获取
            ConfigResponse response = worker.getServerConfig(dataId, group, tenant, timeoutMs, false);
            cr.setContent(response.getContent());
            ...
            content = cr.getContent();
            
            return content;
        } catch (...) {...}
        ...
    }

NacosConfigService的getConfigInner方法首先从本地配置文件获取,如果本地没有才调用ClientWorker的getServerConfig从远程服务器获取。

在这里插入图片描述

然后ClientWorker的getServerConfig方法会调用到ConfigRpcTransportClient#queryConfig()方法

        @Override
        public ConfigResponse queryConfig(String dataId, String group, String tenant, long readTimeouts, boolean notify)
                throws NacosException {
            // 创建ConfigQueryRequest类型的请求对象
            ConfigQueryRequest request = ConfigQueryRequest.build(dataId, group, tenant);
            ...
            // 创建RpcClient对象,用于发起远程调用
            RpcClient rpcClient = getOneRunningClient();
            ...
            // 发起GRPC远程调用
            ConfigQueryResponse response = (ConfigQueryResponse) requestProxy(rpcClient, request, readTimeouts);

            ...
            if (response.isSuccess()) {
            	// 保存到本地配置文件
                LocalConfigInfoProcessor.saveSnapshot(this.getName(), dataId, group, tenant, response.getContent());
                ...
                return configResponse;
            }
            ...
        }

ConfigRpcTransportClient的queryConfig方法发起GRPC远程调用请求远程服务器获取配置内容,然后把获取到的配置内容保存到本地配置文件,那么下次再次获取时,就不需要发起远程调用请求从远程服务器获取了,而是直接取本地配置文件。

在这里插入图片描述

        private Response requestProxy(RpcClient rpcClientInner, Request request, long timeoutMills)
                throws NacosException {
            ...
            // 调用RpcClient的request方法,发起GRPC远程调用
            return rpcClientInner.request(request, timeoutMills);
        }

requestProxy方法里面就是调用调用RpcClient的request方法,发起GRPC远程调用,里面依赖到了grpc的API,我们就不往下看grpc的逻辑了。

在这里插入图片描述

以上便是nacos配置中心客户端启动源码的整体流程,下面是源码分析的整体流程图:

在这里插入图片描述

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值