Nacos源码系列——第一章(Nacos核心源码主线剖析上)
Posted 风清扬逍遥子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Nacos源码系列——第一章(Nacos核心源码主线剖析上)相关的知识,希望对你有一定的参考价值。
在讲具体的源码之前,我有几点想说明下,很多开发可能觉得源码不重要,甚至觉得互联网
的知识,目前够用就可以,也不需要多么精通。的确,在大多数的公司中,你能用你的知识
解决问题就可以,不一定非要涉及到源码,但是你们应该知道如果想进大厂的话,对读源码
的能力是非常高的,甚至像阿里,字节这样的厂,面试经常会问到,尤其是做中间件组件,
对开源框架的源码阅读能力,是有一定的要求的,那么想熟悉源码的过程和思想,可以看看
这篇,会让你受益匪浅!!
1、为什么要看源码
我总结有以下几点,个人觉得非常重要的!
- 提升技术功底
可以学习源码里的优秀设计思想,比如一些疑难问题的解决思路,一些优秀的设计模式,可
整体提升自己的技术功底。
- 深度掌握技术框架
源码看多了,对于一个新技术或者新框架的掌握速度就会有大幅度的提升,其实市面上的框
架源码本身原理大差不差,在一些细节上有所差异,看下demo就可以大致知道底层的实现。
框架更新再快也不用怕了。
- 快速定位线上的问题
遇到线上问题,特别是框架里的bug,能够快速定位,相比别人没看过源码,你具有非常大
的优势。
- 对面试大有好处
面试一线互联网公司对于技术框架,都会问到源码底层的实现。
- 知其然知其所以然
对技术有追求的人必须做的事情,使用一个好的框架,很想知道底层怎么实现的。
- 拥抱开源社区
参与到开源项目的开发,结识更多的大牛,积累更多优质人脉。
2、怎么阅读源码
- 先学会使用
先看官方文档快速掌握框架的基本使用,要多去用,争取自己可以熟悉他的大多数功能,不
要只用一遍,很快就会忘记,先会用,会用!!
- 抓主线
框架源码下载下来,自己写一个demo,顺腾摸瓜快速的静态看源码,也就是不debug看,就
像读英文那样,边边角角先不管,看主线,画出源码主流程图,切勿一开始就陷入源码的细
枝末节,否则很快把自己就绕晕了;有能力的凭经验猜。
- 画图做笔记
总结框架的一些核心功能点,从这些功能点入手深入到源码的细节,边看源码边画源码走向
图,并对关键源码的理解做笔记,把源码里的闪光点记录下来,后续借鉴到工作项目中去,
理解能力强的可以直接看静态的源码,也可以边看源码边debug执行,一定要记录关键变量
的值,和你的方法,不然你还是出不来。
- 整合总结
所有的功能点和源码都分析完了后,回到主流程,重新梳理一遍,争取多来几遍,在自己的
脑瓜子里做个整合。
3、Nacos核心功能点有哪些
- 服务注册
Nacos Client端会通过发送Rest请求的方式,向Nacos Server端注册自己的服务,提供自身
的元数据,比如ip地址,端口等信息,Nacos Server收到注册信息后,就会把这些元数据信
息存储到一个双层的内存Map中。
- 服务心跳
在服务注册后,Nacos Client会维护一个定时心跳来持续通知Nacos Server,说明服务一直
处于可用状态,防止被剔除,默认5s发送一次心跳。
- 服务健康检查
Nacos Server会开启一个定时任务,用来检查注册服务实例的健康情况,对于超过15s没有
收到客户端心跳的实例,会将它的healthy属性置位false(客户端服务发现的时候不会发
现),如果某个实例超过30s没有收到心跳,直接剔除该实例,被踢出的实例如果恢复的话
发送心跳重新注册。
- 服务发现
Nacos Client端在调用服务提供者提供的服务时,会发送一个Rest请求给Nacos Server,获
取上面注册的服务清单,并且缓存Nacos Client本地,同时会在Nacos Client本地开启一个定
时任务,定时拉取服务端最新的注册表信息,更新到本地缓存。
- 服务同步
Nacos Server集群之间会互相同步实例,用来保证服务信息的唯一性。
4、开启Nacos阅读源码之路
还是一样的,在读源码之前,一定要先了解Nacos是做什么用的,自己用demo跑一个实例,
不然上来就看源码,根本不行,如果有需要,可以看我前几篇的Nacos整合博客:
废话不多说,我来带你们看下源码的步骤和过程;
Nacos版本是1.4.2,官网自己下载下来,怎么跑我前面也有博客写
当你构建好了之后,第一个问题来了,这么多的结构,你怎么样可以发现源码的主启动类在
哪里?
教大家一个方法怎么入手源码:
从启动脚本入手!
看下Nacos Server的启动脚本
这有个xxx.jar,一看就知道要启动这个jar,进target下看看,原来是nacos-server.jar;
那么学过maven的就一定知道,这个名字一定是打包的时候手动加的,因为上面的Nacos的
源码项目里没有叫做nacos-server的jar包,于是我们就可以全局搜下:
发现是在console这个项目里,点开看下pom文件
那这我就不多说了,直接找到Nacos,启动的时候记得添加参数-Dnacos.standalone=true
我自己手写了两个demo,一个是consumer,一个是provider;
依赖关系:(这三个必须要有)
于是我启动我的服务,发现已经注册上去了:
源码部分开始,首先我们引入的一个依赖,是spring-cloud-starter-nacos-discovery,我们看
下里面:知道Spring的应该知道这个spring.factory的意思,自动装配机制了解吧。
这里面有个
NacosDiscoveryAutoConfiguration
这个类,为什么要看他,一般情况下找这个类的时候,找和pom依赖很类似的
pom依赖是spring-cloud-starter-nacos-discovery,这个类看样子很像。一般比较厉害的程序
员写开源项目还是很厉害的,命名也很规范。
点进去看看,里面有三个Bean
这三个bean,优先看带Auto的,Springboot项目叫自动装配,这是个小技巧;那有的程序员
命名很不规范,那没办法了,经验猜不出来,一个一个看吧。。。
而且这类的入参具有上面两个bean对象,所以看样子一定没谁了,是个核心类;
我看到了ApplicationListener,就知道Spring容器在启动的时候,要做些事情。
看到if 判断,先跳过,这不是我们关注的主线。
看start方法,里面有个register(),看起来像是注册的意思
往下走,到实现类里,又有一个register();
还有register;
其他的边边角角我不会带你过,我会直接让你看主线,服务启动的第一件事情,应该是要注
册,所以我看到了有注册的方法就沿着一直走下去。
再进去看看,记住一定要抓主线!!
发现这里有点像一个http请求,而且这个请求是post,前面都是一些Map去封装的参数,于是
我们在这里debug看下:
你发现有个请求,/nacos/v1/ns/instance,去nacos官网搜下这个api
发现真的是服务注册的api,好了到这里,客户端怎么注册到server端的过程,我就先走到这
那么服务端收到这个请求,干了啥呢?怎么处理的呢?
刚刚说客户端/nacos/v1/ns/instance这个请求,一定是往服务端去的,搜下服务端的代码;
里面有Post方法,很明显这方法就是服务端处理客户端传来的实例Instance进行注册的;
看这个方法registerInstance
createEmptyService看样子像是创建一个空的Service,很明显判断是不是为空,不为空就初始化一个Service
进入putServiceAndInit放进一个Map中,并初始化,这里用到了DCL
实际上我可以提前剧透下这个serviceMap就是我们说的注册表,并且是个双层Map
Map<String, Map<String, Service>> serviceMap;---注册表结构 ---结构是 Map<public, Map<DEFAULT_GROUP@@provider, Map<String, Set<Instance>>>>
provider是我的服务名字!
再回到putServiceAndInit方法看init
里面有个参数clientBeatCheckTask
见明知意,客户端发起心跳检查的任务,很这是不是我们说的每隔5s发起一个心跳
检查任务给服务端呢?带着疑惑去看下(这里先留下困惑,这个5s是什么)
回到这个类ClientBeatCheckTast看到run方法
这个getInstanceHeartBeatTimeOut方法,点进去看,超过15s后健康状态置false
找到这里,发现有点偏离主线了,我们要找注册的逻辑,继续回到主线,注册
进入addInstance方法
里面的key = com.alibaba.nacos.naming.iplist.ephemeral.public##DEFAULT_GROUP@@provider
这个key后面要放进一个consistancyService这个map中
命名可以知道,这是持久化表,而ephemeral英文是临时的;
我们点下put方法,发现有很多实现类,这个是读源码的一个不好的场景,我不知道应该进哪个,教大家一个技巧,当你不知道的时候,凭经验猜
猜不出来,就在这个点打个断点
如果猜的话,看下这个源头来自哪里
这里有个Delegate,我凭经验猜就是实现类DelegateConsistencyServiceImpl,事实证明我是对的,这个key实际上就是我上面发的,判断这个key是不是包含临时标志ephemeral
ephemeralConsistencyService:临时实例的注册 persistentConsistencyService:持久实例的注册
这两个再剧透下,区分Ap和Cp架构的
继续看DelegateConsistencyServiceImpl.put
又是一堆实现类,还是技巧,要么经验,要么断点,这再教一个
mapConsistencyService(key)这个返回的是什么类
这里判断一定是第一个
进入ephemeralConsistencyService的实现类,只有一个
现在知道这个put应该进入哪里了吧,进去看看进到onput
这个dataMap先不管,混个眼熟,后面看多了自然穿起来
进到addTask,分支代码先暂时忽略
看下这个offer是什么,很明显是阻塞队列,一个内存队列,把客户端传来的参数,封装了后
传到了这个阻塞队列里去,Ok,注册逻辑到此结束!!
什么?这么快结束了?
刚刚不是说有个注册表是个双层Map吗,客户端信息没有写到双层Map里去啊?
知道阻塞队列,应该知道既然往里塞,就一定会异步去取出来,你往下稍微走走看
看到了take吧?
再看看handle方法,分支不看,大概过一遍,这里很有可能是拿到队列里的注册信息去做事情,
看下有三个实现类
我这里其实都看了下,发现是第一个,为啥,你会发现其他两个里面都是consistency持久化,我们一直在看的是ephemeral,而且断点进去的,和我猜想的结果是一样的
边边角角的逻辑先过掉,真正的注册的逻辑在这里
这么多代码大致看了下,我找了半天,没什么技巧了,是在这个方法里做了注册
进去看下,传入参数ephemeral是true还是false,默认是true,而到现在这个值一直没变过
当然的确论证了观点
中间一大段逻辑,先不管,最终会写到这里来,这里就是注册表的最核心的地方
哎,这个和我上面说的好像不一样啊
我们看下Service里面是什么,有个Map对象
里面有个Cluster,现在知道这个双层Map有多复杂了吧
Map我们可以再细化下
Map<String, Map<String, Service>> serviceMap; -- 注册表结构 现在细化下 Service: Map<String, Cluster> clusterMap; Cluster: Set<Instance> ----所以最后核心注册表结构是: Map<public, Map<DEFAULT_GROUP@@provider, Map<DEFAULT, Set<Instance>>>>
看下clusterMap
注册我就讲到这里
总结下:
客户端启动的时候,发起http请求,发送一些注册参数,服务端会开启一个线程把这些参数
放在一个阻塞队列里,并异步的消费去把这些放在一个双层Map中的Set集合,实现注册的逻
辑;
那你怎么知道哪里去启动这个线程呢?
回到刚刚的逻辑,很明显这里被Spring初始化的时候调用,里面就是线程池的逻辑我就不看了。
而本节留的几个问题
- 客户端怎么定时发心跳的?多久发一次?
后面一节我会重点剖析!
- 为什么要用异步去注册,而不用同步?
设想下如果这个中间件,采用同步注册,如果运维启动一批服务注册上去,先注册,再消费队列,每个服务的启动时间是不是很久,如果我的一个项目中引入很多很多中间件,每个中间件都要同步去做这些事情,那整个系统启动非常慢导致不可用。思想我觉得应该都理解
- 阿里的开发人员为什么要这么设计?我们有没有什么值得学习的地方?
整体收篇单独总结!
- 注册表的设计如何防止多节点读写并发冲突?
重点来了,为什么要这么设计双层Map呢?
1、考虑到目前开发的环境,和市面上公司的情况,有的公司钱不多,不能支撑每个环境都做一套注册中心;Nacos支持你部署一套环境,支持你所有的开发环境,区分namespace和group。
2、高可扩展,大型互联网公司,一定是多机房部署,比如深圳机房,华南机房,不可能我只有一个机房在北京,内蒙古那边访问个淘宝要等很久才出来?所以双层Map中会有Cluster,通过Cluster区分哪个集群属于哪个机房部署;这种商用中间件一定是这样多扩展的。
3、这个注册表会不停的修改,那么其他服务拉取这个注册表的时候,保证数据怎么正确?
采用CopyOnWrite思想,我们知道注册的逻辑比较复杂,很多步骤,每一步都可能会改这个注册表的结构和逻辑,我们不可能加锁,性能效率会非常低而且并发很低,读写冲突问题,我们采用写时复制思想;阿里这样的中间件不会随随便便加把锁,所以在写的时候,修改的是一份副本,然后在替换注册表,读的时候是读真正替换后的注册表!!
等于是读写分离,但是有个弊端,你写你的,我读我的,有可能会导致数据不一致,只有当替换回来的时候,我才能读到新的数据。
虽然写时复制提高了我们的并发了,但是对数据的实时性就不能很好的保证,那么阿里怎么处理这个呢?但是这个影响大吗,其实并不大,无非就是生产者启动慢点罢了,延迟一点的感知其实对整个系统的影响并不大,Eureka都延迟几十秒,Nacos这个延迟并不大,后面我会说到客户端也会定时拉取服务端最新的注册信息,以及剔除下线的服务,目前大大的提升了并发,总不能又要高并发,又要实时感知及时。
当然也不存在每个服务都复制一份去写,因为Server后台就一个线程去取队列注册,不存在多个线程去对不同的服务进行写时复制。
这块代码在这里CopyOnWrite
总结的思维脑图:https://www.processon.com/view/link/60d87a95637689326ce6a928
好了,目前注册就讲到这里,总结的思维脑图在下一章节会发出来,欢迎指正!!
以上是关于Nacos源码系列——第一章(Nacos核心源码主线剖析上)的主要内容,如果未能解决你的问题,请参考以下文章
Nacos源码系列——第三章(全网最经典的Nacos集群源码主线剖析)