翻牌源码之Nacos--配置中心实现 一

Nacos是什么,在nacos.io网址下解释的很清楚,这里大概做个简单的介绍。

  • 服务(Service)是Nacos世界的一等公民。
  • Nacos提供了对服务的实时健康检查,阻止向不健康的主机或服务实例发送请求。Nacos 提供了 agent 上报模式和服务端主动检测2种健康检查模式。
  • Nacos提供的服务
    • 动态服务配置
    • 服务注册与发现并管理
    • 动态DNS服务
    • 服务及其元数据管理

上边简单的介绍了一下Nacos,他的使用与配置在官网说的非常清楚,这里我们就不再赘述了。这里,就Nacos配置中心的实现,主攻一波源码实现,分析一下再说。

首先,我们先看一下Nacos官网给出的JAVA SDK 客户端部分的程序结构

我们就从NacosFactory开始,一步一步把Nacos作为配置中心的功能梳理一遍。

获取配置

首先看一下Nacos获取配置信息部分的代码:

Properties properties = new Properties();
//指定组名
String group = "DEFAULT_GROUP";
//指定dataId
String dataId = "webflux-test.yml";
//如果有命名空间,则需要指定命名空间
String namespace = "62f571c7-7593-4513-8de9-538426175db4";
properties.put("serverAddr", "81.70.10.96:8848");
properties.put("namespace", namespace);
//获取ConfigService
ConfigService configService = NacosFactory.createConfigService(properties);
String config = configService.getConfig(dataId, group, 5000);
System.out.println(config);

经过这个一行Java SDK的调用,我们就可以获取配置信息了。实现总体来说也很简单,我们来跟一下代码部分,客户端脉络就是通过NacosFactory来获取ConfigService。创建代码如下:

可以看到是使用反射的方式进行创建的。用反射的方式进行创建,我的理解是如果后续实现功能的类如果需要改变的话,可以直接修改配置文件就行了,而不用进入类中修改相应的实现代码。

下边主要看一下ConfigService的构造函数,也就是他的实现类,NacosConfigService的构造函数。

public NacosConfigService(Properties properties) throws NacosException {
    //校验配置信息
    ValidatorUtils.checkInitParam(properties);
    String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE);
    if (StringUtils.isBlank(encodeTmp)) {
        this.encode = Constants.ENCODE;
    } else {
        this.encode = encodeTmp.trim();
    }
    initNamespace(properties);
    //这部分使用的代理模式,其中执行了login操作,
    //这个agent也作为load blance请求服务端使用
    //这里先不说
    this.agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
    this.agent.start();
    //获取配置主要是这一部分,ClientWorker是客户端主要工作的地方
    this.worker = new ClientWorker(this.agent, this.configFilterChainManager, properties);
}

这里先不深入CilentWorker代码,现在我们拿到了ConfigService的实例对象,开始获取配置的操作了。如下图所示,获取配置的操作有两个,一个是单纯获取配置,另一个是获取配置并监听,我们先看单纯获取配置的部分。

com.alibaba.nacos.client.config.NacosConfigService#getConfigInner这个是最终调用的方法,方法比较长,一共分为3部分。首先,从本地配置文件中获取,获取到了,就直接返回,如下:

其次,从远程获取,获取成功则保存快照信息到本地文件中。最后,如果远程获取失败,则从本地快照获取,防止远程连接丢失造成的服务不可用。这就是获取配置信息在客户端部分的主要源码、如下:

我们继续追踪远程获取配置信息的部分,其实是发送了一个Http的请求来进行配置信息的获取的。

我们找到对应的地址,然后来到服务端,看看配置文件是从哪儿获取来的。经过追踪,发现地址是:/v1/cs/configs,GET

进过一番寻找,在服务端的config module下,找到ConfigController,获取配置调用的方法为:com.alibaba.nacos.config.server.controller.ConfigController#getConfig。

ConfigController中引用了ConfigServletInner类,调用了com.alibaba.nacos.config.server.controller.ConfigServletInner#doGetConfig方法。如下图所示:

tryConfigReadLock方法根据key值来判断所要读取的配置当前的锁状态,如果可读,就进入lockResult>0的判断分支,进行内容读取。主要逻辑就是这一部分。

如图,如果配置文件中使用直接读取,则读取数据库数据或者使用raft+derby读取leader的数据。如果不是,则直接从文件中读取内容。最终,数据写入response中,配置内容请求成功。

关于使用raft协议来实现获取配置信息,以保证配置信息的一致性的内容,后续对JRaft进行专门的研究之后,再回过头来进行分析。 

监听配置

我们开发系统的时候,有些配置项是需要可以实时进行变化的,Nacos作为配置中心,也是支持对配置改变的动态感知的,那么,Nacos这块儿是怎么实现的呢?

经过对源码的阅读,大概画了一下监听配置改变的实现方式:

在上边我们已经分析出了,获取配置的方法有两个。第一个直接获取配置,第二个就是获取配置然后对相应的配置进行监听,实际调用代码如下:

        configService.getConfigAndSignListener(dataId, group, 5000, new com.alibaba.nacos.api.config.listener.Listener() {
            @Override
            public Executor getExecutor() {
                return t -> {
                    System.out.println("nihao");
                    t.run();
                };
            }

            @Override
            public void receiveConfigInfo(String configInfo) {
                System.out.println(configInfo);
            }
        });

最后一个参数,是实现com.alibaba.nacos.api.config.listener.Listener接口的对象,有两个方法需要重写。这里就倒着来分析一波,看看在哪儿执行的这两个方法。我使用的是IDEA,按住Ctrl和鼠标左键就可以回溯到调用链的上一步,我们点击一下上述代码的getExecutor()方法,调用方法的地方是com.alibaba.nacos.client.config.impl.CacheData#safeNotifyListener方法,可以看见这里首先定义了一个Rannable的job,然后之下如下方法:先判断重写的getExecutor()方法返回值是否不为空,不为空则使用自定义的执行器执行job,为空则直接执行job.run()。

最后执行了Listener的receiveConfigInfo()方法并更新了本地缓存中的内容。

接着向上走,如下图所示,是checkListenerMd5方法,如果md5与缓存中的lastCallMd5不一致,就进行方法调用。

继续往上跟,来到com.alibaba.nacos.client.config.impl.ClientWorker.LongPollingRunnable内部类。这是个实现了Rannale接口的实现,刚刚的方法调用在run方法的执行中进行。这里的run方法要做的工作是:首先根据taskId匹配得到需要处理的缓存任务,然后先判断是否使用本地配置,如果使用本地配置则不再进行远程请求,直接对比本地配置文件的md5是否相同。如果不使用本地配置并且有需要监听同步的缓存数据,则进行远程调用,进而获取到变动的key值,然手根据key得到本地缓存,进而查询配置信息并更新缓存。

方法最后,执行一个调用当前执行器的方法,进而达到循环拉取调用的目的。也就是说,这个方法是一直在运行的,那这个方法是从哪儿调用过来的呢,继续向前,是com.alibaba.nacos.client.config.impl.ClientWorker#checkConfigInfo方法。从方法中可以看出,会启用一个LongPollingRunnable,并给定一个i作为taskId,持续不断的执行任务。那cacheMap的数据是怎么添加的呢?稍后再看,继续向上跟。

最终发现,com.alibaba.nacos.client.config.impl.ClientWorker#ClientWorker的构造方法中调用了该方法。这就是上文在初始化ConfigService时初始化的CilentWorker。好了,到这里都已经明了了。

在生成ConfigService时new了CilentWorker,CilentWorker中启动了一个定时任务线程池,持续不断的进行监控,实现cacheData中监听配置信息的目的。那个cacheData到底是怎么来的呢?

回过头来看获取配置并添加Listener的方法,调用的方法如下:

在接着往里跟,看见了关键的地方,这里就是添加cache的地方,

在往里跟,终于看到了cache添加的地方。

经过这一路下来,客户端怎么请求并监听配置变化已经一目了然了。再次回到远程请求服务中配置信息的地方,我发现,每次远程请求会等待很长一段时间,响应才会返回,而如果修改了配置文件内容,就会立马返回配置有更新,并触发监听器的方法执行,那服务器端到底是怎么实现方法的呢?后续再说。

以上内容是由源码一点一点梳理的,如有错误,烦请交流指正。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值