apollo源码分析 感知_Nacos获取服务端配置和动态更新的源码分析(上)

背景

前面两个spring boot2.x整合nacos系列的教程里面,我们使用docker初步搭建了nacos单机版的服务端,并且介绍了我们在平时开发的过程中,如何正确高效的使用nacos,让nacos无限的减少我们本地的配置,本小节主要介绍我们在本地如何能够拉取到nacos服务端的配置的原理,和源码基本讲解,让大家知道其中的基本流程,并且能够掌握其中的源码技巧,希望大家在有的工作中,能够有机会去使用这些优秀源码的技巧,让我们平时的业务代码写的更加精致

tips:本小节大概阅读的时间在20分钟,分上下两小节,如果您能静下心来安心地读完,相信你会觉得受益匪浅,最后希望大家一起进步~本文为原创,如果您希望,帮忙关注一下,您的支持是我写作最大的动力~

阅读源码的入口

首先,在阅读源码之前,我们先找到阅读源码的入口,在nacos的源代码中,子项目example中有一个示例类ConfigExample,我们先运行一下,看下效果

com.alibaba.nacos.example.ConfigExample

源代码示例如下

628755b0ef77f097636532c54cc1b0c2.png

最基本的main函数nacos客户端获取服务端数据方法示例

运行main函数,我们可以成功的看到控制台打印输出如下

f0b45a951cfaf0b1199880002b711090.png

运行结果

我们粗略地看下ConfigExample的main函数实现,我们指定了nacos服务端的地址IP,dataId,groupId,namespace 然后我们就可以获取到服务端返回给我们的数据,从这一点可以明确一个远程的properties的唯一标识是如下几个因素可以确认

  1. groupId
  2. dataId
  3. namespace

我们回到刚才的源代码,我们还配置了一个listener,nacos最大的一个特性就是支持热更新,当nacos服务端的properties更新的时候,所有的客户端(具体说来,是订阅过对应dataId,groupId的客户端)都能"立即"(具体说应该是准实时)感知到服务端的数据变更,我们看下当注册一个如下的listener的时候,我们修改一下配置中心的属性,看看该listener能不能感知到变化

dfbdd7dd9bc35c5dee2da2331af43a86.png

我们在nacos的服务端的namespace为dev,dataId为oms,groupId为product_center下修改数据,点击发布

00d47ef5d581b086dbd7e3e7065cc128.png

我们能发现控制台出现了我们意料之中的变化,nacos的客户端感知到了服务端的数据变化

3ff5373ad79ad4f0ae0c4eec33402899.png

本章节,我们就介绍一下如下2个内容

  1. 客户端如何拉取服务端的配置信息
  2. 客户端如何感知到服务端的配置变化(重点)

阅读源码前的知识准备

1.Servlet3.0新特性,异步处理的支持

servlet3.0开始开始支持异步处理,所谓的异步处理就是,当servlet接受到请求的时候,可能需要一段耗时很长的业务处理时间,这个时候业务线程就是tomcat或者其他web容器的servlet线程,这个线程就会一直消耗在处理业务线程上来,无法得到释放,如果大量客户端请求到来的时候,web容器就没有剩余线程处理业务逻辑,这就会影响性能,所以servlet3.0开始有了一个新的特性:servlet的线程接受到请求之后,把逻辑复杂耗时时间长的操作交给异步线程,自己则回归到web容器线程池中,这样能够快速地释放servlet线程,得到资源的最大利用,异步线程处理结束之后,可以返回web服务器端的响应数据(以后有机会做一个spring boot 整个异步处理新特性的文章)

2.ScheduledExecutorService处理类的scheduledWithFixedDelay方法的含义,四个参数

  1. 第一个Runnable,表示要执行的任务函数体
  2. 第二个参数initialDelay:表示第一个任务开始执行的延迟执行时间
  3. 第三个参数delay:表示上一个任务执行结束之后,下一个任务执行的时间,这边必须要注意的是上一个任务执行完之后开始计时的延迟时间
  4. 第四个参数Timeout,时间单位

3.ScheduledExecutorService处理类的schedule方法,这个方法表示延迟执行,且只调度一次,这个方法返回一个future对象,可以通过future.get,cancel等方法进行一些操作,来达到我们要的效果

以上三个就是我们在本章需要拥有的基本知识点,在接下来的核心源码分析中,了解这些你就会有事半功倍的效果,这边先卖一个关子

阅读源码之前的思考

我个人觉得阅读源码之前,我们一定要想好为什么要阅读源码,要解决我们什么困惑,带着问题去看源码,这样我们就能在看源码的过程中,不迷失方向

1.我们之前说过nacos是典型的C/S架构,如果客户端想要获取服务端的properties配置信息,根据服务端的地址,dataId,groupId,namespace应该说发送一个http请求就可以成功获取到信息,如果这个代码给我们写是不是写一个httpClient请求就可以完成获取属性的操作了?感觉这样并不是很难

2.客户端是如何获取到nacos属性变更通知的,是服务端主动通知的,还是客户端有一个定时任务不断地去查询对应的dataId,group组下的properties是否有变更呢,其实这个问题就是我们经常说的推拉模型,按照目前所有主流框架的逻辑,应该不是推模式,因为如果使用服务端变更的时候,服务端主动通知客户端属性变更,服务端的性能会成很大的问题,首先你要保持很多的长连接,还有就是当客户端很多的时候,监听的节点数以万计的时候,服务端的性能应该就是很大的问题,所以初步定论,现代优秀的nacos框架,不应该会是推模型

至于为什么不是"推"模型,还有一个经验问题

1.zookeeper有一个watch的机制,现在zookeeper的性能一直是大家所诟病的,当zookeeper的被watch的节点很多的时候,或者watch的客户端很多的时候,因为zookeeper就是使用推模型,会导致zookeeper服务端性能急剧下降,推送的不及时

2.看过阿里巴巴另一个优秀的消息中间件RocketMQ的人应该都知道RocketMQ使用的就是拉模型,解决的就是服务端性能的问题,使用的是"长轮询"的方式,所以这边猜想,同样是阿里中间件团队,应该也会考虑到这个问题(RocketMQ的整合和源码阅读以后也会一起整理)

带着如上的2个问题就开始进行源代码的阅读,应该会很轻松

第一个问题:客户端如何获取到服务端的配置信息的?

回到我们刚才的测试代码ConfigExample的入口,可以看到源代码中使用NacosFactory的#createConfigService方法,返回了一个ConfigService

4fa49b78387130d5f6762f43a4d2c7b3.png

ConfigService明显就是一个核心接口,方法列表如下,根据方法名,有getConfig,addListener等我们需要的一些客户端常用的方法,所以我们这边应该深入这个方法的研究

f8e0a45460547ce19bcb56bc77f4819d.png

ConfigService的方法列表

我们可以看到这边跟我们平时想象的不一样,一般factory的静态方法都是返回的一个单例,而这边却是使用反射创建一个继承ConfigService的实现类的NacosConfigService

5f5f4f12227134b2a30cf0e6ea843bd4.png

反射创建NacosConfigService

我们可以重点关注一下NacosConfigService的构造函数,源代码如下,配置好编码格式和namespace之后,我们重点看下下面源码中,红框标注的2个部分,第一个部分是用装饰者模式创建的带有统计的httpclient,名字叫做ServerHttpAgent,跟我们设想的一样,也是使用http协议与nacos服务端进行交互的,那么获取服务端的配置信息,应该并不难,然后又创建了一个ClientWorker,顾名思义,感觉这个类就是nacos客户端的总代理一样,应该所有的核心逻辑操作就在这边了,接下来我们应该会跟这个ClientWorker多次打交道

2c7e78389b606e99d511c0688870109d.png

回到我们刚才的main函数,获取配置属性的地方,方法名叫做getConfig的地方

c646e888f5e37dc378b046415943ffd2.png

我们可以跟踪源码分析,很容易就看到具体在NacosConfigService中的实现方法getConfigInner,可以看到第一段逻辑代码就是优先获取本地的关于dataId,groupId,namespace的配置,如果能获取到则优先返回,为什么优先获取本地呢,其实nacos也是做出了性能考虑,否则每次都去查询服务器端的数据,这样会导致服务器端的压力负载过大,特别是成千上万个节点连接到服务器端的时候,所以优先读取本地配置

717387a17dd45cb0a786574dbb1be71c.png

如果本地文件不存在对应的配置,相当于本地缓存没有,这种情况一般都是第一次请求的时候,本地没有缓存,这个时候就需要请求服务端数据,接着看源码,我们可以看到接下来就是使用worker去获取服务端的数据了,也就是前面构造函数初始化好的clientworker

1d19af5aa0009a40cd44b0794f792cd9.png

我们感觉快接近真相大白了,我们接着看clientworker的getServerConfig方法,我们之前说的使用httpClient获取数据,这边也是这么做的,具体的参数,我在下图的源码中都一一说明标注了一下,最后请求的地址应该是http://ip:8848/v1/cs/configs的Get请求,服务端获取配置信息的源码,我们下几个小节分析,在这边我们只关注获取数据的逻辑,最后客户端成功获取到了服务器端的数据,总而言之,还是比较简单的

2c218611b2b0ba86773a3311f9506c49.png

使用httpclient去获取服务端的配置信息

接下来就有一段基本的逻辑处理,大家也不要忘记,把从服务端获取到的数据持久化到本地磁盘,这样就可以与刚才优先获取本地的配置做到相呼应了,可以看到当与服务端交互,服务端返回成功或者没找到的时候,都可以将信息持久化到本地磁盘,这样就可以很方便地优先从本地获取了

c027c22c84cdbcd85673d03a2d408b39.png

持久化到本地磁盘

到此为止,客户端获取到服务端的配置信息,主体流程已经分析结束了,接下来就要分析客户端如何感知到服务端的配置变更了

小结

本小节内容相对比较简单,也是我们阅读源码的第一篇文章,主要目的就是希望大家在阅读源码之前,有一些自己的思考,带着问题,和带着自己的想法去阅读源码,想着如果是自己应该怎么实现,这些写中间件的大牛又是怎么实现的,这样当看到别人实现的亮点的时候,以后自己就可以去模仿,去"抄写",慢慢地就会变得更加强大一点,与大牛的差距就会慢慢地缩小一些

废话不多说,还是总结一下吧,nacos客户端获取服务端的配置信息,还是比较简单的,依靠clientworker去获取,这是一个核心类,优先获取本地的配置,如果本地没有缓存,就去获取nacos服务器端的数据

下一个小节,应该就是本小节的重点内容,如何客户端如何感知到服务器端的数据变化,客户端感知到变化之后,更新本地磁盘的配置信息,这样就可以永远保持本地磁盘是最新的配置信息了~

如果您看到这里,觉得我写的不错的话,希望您点一个关注,或者你现在没有时间看的话,点一个收藏,在您闲暇的时候,翻阅看看,相信我们可以一起学习进步,如果写的有不对的地方,欢迎批评指出

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Apollo Routing 是 Apollo Client 的一个功能模块,用于处理客户端的路由和导航。它提供了一组 API 和工具,使得在 GraphQL 客户端进行页面导航和路由管理变得更简单。 Apollo Routing 的源码主要包括以下几个部分: 1. `Router` 类:这是 Apollo Routing 的核心类,负责管理路由状态和导航。它使用浏览器的 `history` API 来监听 URL 变化,并根据变化更新路由状态。`Router` 类还提供了一组方法,用于注册和处理路由变化的回调函数。 2. `Route` 组件:这是一个 React 组件,用于声明式地定义路由与组件的映射关系。每个 `Route` 组件都包含一个 `path` 属性和一个 `component` 属性,用于指定 URL 匹配规则和对应的组件。 3. `Link` 组件:这是一个 React 组件,用于生成带有正确 URL 的链接。它会通过 `Router` 类提供的 API 来更新 URL,并触发路由变化。 4. `Switch` 组件:这是一个 React 组件,用于在多个 `Route` 组件之间选择匹配的路由。它会遍历所有子组件,并渲染第一个匹配成功的 `Route` 组件。 5. `useRouter` Hook:这是一个自定义的 React Hook,用于在函数组件获取 `Router` 实例。它会利用 React 的上下文(context)机制,从根组件向下传递 `Router` 实例。 以上是 Apollo Routing 的主要源码组成部分。通过分析这些源码,我们可以更深入地了解 Apollo Routing 的实现原理和使用方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值