nacos配置中心源码分析——拉取配置信息

目录

版本说明

nacos官方示例

集成nacos客户端源码分析

nacos服务端源码分析

总结


版本说明

个人认为分析配置中心的源码要比注册中心更需要spring boot源码的理解,主要体现在更新或者刷新容器内部配置文件内容。

主要分为两部分讲解:1、spring boot启动集成nacos及拉取远端配置信息;2、nacos动态更新配置,客户端如何感知并在未重启应用的情况下如何刷新配置。

spring-boot版本:1.5.21.RELEASE

spring-cloud-alibaba版本:1.5.1.RELEASE

nacos版本:1.x

nacos官方示例

在分析源码之前我们先来看一个nacos官方example示例:

public class ConfigExample {

    public static void main(String[] args) throws NacosException, InterruptedException {
        String serverAddr = "127.0.0.1";
        String dataId = "test";
        String group = "DEFAULT_GROUP";
        Properties properties = new Properties();
        properties.put("serverAddr", serverAddr);
        ConfigService configService = NacosFactory.createConfigService(properties);
        //根据dataId获取配置信息
        String content = configService.getConfig(dataId, group, 5000);
        System.out.println(content);
        //添加配置信息变更的监听器
        configService.addListener(dataId, group, new Listener() {
            @Override
            public void receiveConfigInfo(String configInfo) {
                System.out.println("receive:" + configInfo);
            }

            @Override
            public Executor getExecutor() {
                return null;
            }
        });
        //发布变更配置信息
        boolean isPublishOk = configService.publishConfig(dataId, group, "content");
        System.out.println(isPublishOk);

        Thread.sleep(3000);
        content = configService.getConfig(dataId, group, 5000);
        System.out.println(content);

        //根据dataId删除配置信息
        boolean isRemoveOk = configService.removeConfig(dataId, group);
        System.out.println(isRemoveOk);
        Thread.sleep(3000);

        content = configService.getConfig(dataId, group, 5000);
        System.out.println(content);
        Thread.sleep(300000);

    }
}

 执行结果:

nacos配置中心源码分析——拉取配置信息_第1张图片

根据示例结果我们先来认识一下nacos核心组件ConfigService,通过这个configService可以向nacos服务端发起获取或发布配置的请求,还可注册监听器,监听发生变化的配置,而nacos发布配置动态更新就是通过监听器实现。注意是一个dataId对应一个监听器。

集成nacos客户端源码分析

spring boot集成spring cloud alibaba后,SpringApplication.run()会被执行两次,这是spring cloud利用BootstrapApplicationListener事件监听器另起炉灶,通过再次调用SpringApplication.run()重新创建了一个spring容器。关于这块内容另出一期博客,下面代码注释可以看出spring cloud创建的spring容器是spring boot创建的spring容器的父容器。

spring boot应用在启动过程中,在加载创建spring容器后和refresh()之间,会有调用prepareContext()方法,准备刷新容器之前需要初始化的方法,这其中就包含容器初始化器接口的ApplicationContextInitializer.initialize()方法,在spring boot启动流程分析的博客中提了一嘴,默认会去spring boot中spring.factories文件中读取6个初始化器的实现类,如图。

nacos配置中心源码分析——拉取配置信息_第2张图片

而在引入spring-cloud-alibaba-nacos后,还会加载其他的初始化器,其中就包含加载nacos配置文件的PropertySourceBootstrapConfiguration,由于第一次创建的是bootstrap容器,所以执行PropertySourceBootstrapConfiguration#initialize()方法是在第二次创建spring boot容器时执行的,这里还有其他的一些初始化器,我们忽略掉。

nacos配置中心源码分析——拉取配置信息_第3张图片

nacos配置中心源码分析——拉取配置信息_第4张图片

在spring boot自动装配的过程中,会加载NacosConfigBootstrapConfiguration,它创建了NacosPropertySourceLocator的bean,在执行上面的locator.locate(),就调用到NacosPropertySourceLocator.locate()方法,nacos配置中心源码分析——拉取配置信息_第5张图片

在分析NacosPropertySourceLocator.locate()之前我们来看一个示例,就明白他们是干什么的了,在nacos UI中分别创建如下配置:

nacos配置中心源码分析——拉取配置信息_第6张图片

share.properties: 

user.age=20
user.height=180

ext.properties:

user.name=james
user.age=21

nacos-config.properties:

user.name=rick
user.weight=50

 springboot应用的bootstrap配置nacos配置中心源码分析——拉取配置信息_第7张图片

启动这个应用后,调用接口获取配置信息:

实验结构得知:加载配置文件的顺序是application覆盖ext,ext覆盖share。

再回过头来看NacosPropertySourceLocator.locate(),它里面有三个核心方法,以及创建configService对象用来与nacos服务端进行交互。然后创建CompositePropertySource对象,他有一个属性很重要,propertySources set集合,用来缓存所有从nacos获取的配置源。

nacos配置中心源码分析——拉取配置信息_第8张图片

nacos配置中心源码分析——拉取配置信息_第9张图片nacos配置中心源码分析——拉取配置信息_第10张图片

他们依次从nacos服务中拉取bootstrap配置文件中配置的share.properties,ext.properties,nacos-config.properties。只分析loadSharedConfiguration(),其他两个方法的调用流程是相同的。nacos配置中心源码分析——拉取配置信息_第11张图片

先读取所有需要拉取的共享配置,然后遍历所有的dataId,调用loadNacosDataIfPresent()nacos配置中心源码分析——拉取配置信息_第12张图片

这里主要是通过loadNacosPropertySource()加载创建NacosPropertySource,然后通过addFirstPropertySource()方法将它添加到set集合中进行缓存。

nacos配置中心源码分析——拉取配置信息_第13张图片

loadNacosPropertySource()主要是做判断,看是否可以直接从缓存中获取配置,如果需要刷新配置再执行build()方法

nacos配置中心源码分析——拉取配置信息_第14张图片

build()先执行loadNacosData()方法,返回的properties对象,构建NacosPropertySource对象,然会调用collectNacosPropertySources()进行缓存,缓存的是一个map集合,key:dataId,value:nacosPropertySource。

nacos配置中心源码分析——拉取配置信息_第15张图片

最后到loadNacosData(),通过configService.getConfig()从Nacos服务端拉取配置信息。到这里configService就是nacos客户端提供的api用来获取远端nacos服务的配置信息。继续深入configService.getConfig()源码进行分析

nacos配置中心源码分析——拉取配置信息_第16张图片

先调用getFailover()方法从本地获取配置信息,文件所在目录:用户空间\nacos\config\fixed-127.0.0.1_8848_nacos\snapshot\DEFAULT_GROUP\xxx.xxx。这样做主要有两个优点:1、避免每次从服务端拉取配置,减少网络开销;2、如果nacos服务宕机或者网络可不用了,服务还可以继续使用(容灾)。 

然后调用worker.getServerConfig(),使用agent.httpGet()实际底层调用HttpURLConnection发起http请求(请求路径为/v1/cs/configs),返回结果通过saveSnapshot()方法,写入磁盘,目录就是:用户空间\nacos\config\fixed-127.0.0.1_8848_nacos\snapshot\DEFAULT_GROUP\xxx.xxx

nacos配置中心源码分析——拉取配置信息_第17张图片

nacos服务端源码分析

请求来到nacos服务端的ConfigController.getConfig()

nacos配置中心源码分析——拉取配置信息_第18张图片

一通校验后,进入inner.doGetConfig(),该方法很长,我截取重要的代码来分析

nacos配置中心源码分析——拉取配置信息_第19张图片

如果没有配置则直接调用persistService.findConfigInfo(),它直接从java的内存数据Derby获取配置信息。nacos配置中心源码分析——拉取配置信息_第20张图片

如果在console子工程中resources/application.properties或者已经打好包的配置文件配置并使用mysql数据库,最终代码会执行DiskUtil.targetTagFile(),从磁盘中读取配置信息。问题来了配置信息明明存储在mysql数据库中,磁盘中的数据是从哪来的呢?

nacos配置中心源码分析——拉取配置信息_第21张图片

那就有请另一个重要的抽象类DumpService,他有两个实现类,分别是EmbeddedDumpService和ExternalDumpService,其中DumpService有一个抽象方法init(),它的两个子类都实现了init()。没错,init()方法会将数据库中的数据写到磁盘中,那么为什么要这么做呢?

先来看看ExternalDumpService.init(),@PostConstruct就不用解释了吧nacos配置中心源码分析——拉取配置信息_第22张图片

在dumpOperate()中会向任务管理器TaskManager的父类NacosDelayTaskExecuteEngine的ConcurrentHashMap中添加任务,稍微注意下这个DumpAllTask.TASK_ID,在dumpService的构造方法中会将这个taskId绑定DumpAllProcessor这个任务处理器

nacos配置中心源码分析——拉取配置信息_第23张图片

 在NacosDelayTaskExecuteEngine中定义定时任务线程池来处理ProcessRunnable,ProcessRunnable的run调用processTasks()处理任务

nacos配置中心源码分析——拉取配置信息_第24张图片

nacos配置中心源码分析——拉取配置信息_第25张图片

最终调用DumpAllProcessor.process()方法,使用循环分页的方式查询数据库中的数据,然后调用ConfigCacheService.dump()将数据写到对应的文件中,目录是:用户空间\nacos\data\config-data\DEFAULT_GROUP\xxx.xx,当然在dump()方法中会先通过md5的对比是否需要更新数据。

nacos配置中心源码分析——拉取配置信息_第26张图片

到这里客户端应用从nacos服务端的请求链路就打通了,还遗留一个问题,为什么要将数据从数据库中写到磁盘中,而不是直接读数据库?可能是为了避免每次都请求mysql的网络开销。

总结

1、nacos客户端主要加载共享配置,拓展配置,应用配置,这三个配置的优先级由低到高(先加载的会被后加载的覆盖掉)。应用配置支持配置应用名称/应用名称.后缀名/应用名称.profile.后缀名

2、nacos客户端从服务端拉取到配置后,会缓存到内存和写入磁盘,防止nacos服务宕机或者网络分区导致服务不可用

3、nocos服务端支持两种模式,分别是基于内嵌内存数据库Derby,请求直接查询数据库,另一种外部数据库(如mysql),DumpService会启动定时任务定期将数据库配置信息写到磁盘中,请求会从磁盘文件中读取数据。

最后画了一张流程图:

你可能感兴趣的:(nacos,java,spring,开发语言)