gateway nacos注册服务_Spring Cloud(六):注册中心nacos服务端视角

点击蓝字关注我们

264db2256ebbb3b5c0cf22f99da49489.gif 264db2256ebbb3b5c0cf22f99da49489.gif 大家好,我是杰哥 上周,我们通过文章 Spring Cloud(五):注册中心-nacos篇 ,对nacos作为注册中心,有了一个基本的了解。那么按照惯例,今天就要进行真正有料的环节-源码探究 本篇,将 站在服务端的视角 ,对于nacos作为服务注册中心的 选举 、 心跳、注册 以及 服务同步 几个重要动作的流程解析,一步步深入了解nacos注册中心的真正原理 为了方便跟踪源码,我们先来一起搭建一个nacos的集群环境

一 环境

集群搭建

01.nacos集群环境架构图

nacos的集群环境架构,建议:域名 + VIP模式,这种可读性好,而且换ip方便,是官网的推荐模式 2ef9cb98413c01cf87ce066a1e94c583.png

02.集群部署步骤

整体部署过程与单点部署过程基本一致,只是集群模式是多节点的模式而已 可以参照上一篇文章中的单点部署过程 1)安装包地址 https://github.com/alibaba/nacos/releases 2)解压 解压压缩包为nacos,并复制为nacos-1,nacos-2 3)配置application.properties文件 分别进入nacos/conf,nacos-1/conf,nacos-2/conf目录,进行application.properties文件的配置 a 服务端口配置 三个服务端口分别配置为: server.port=8848 server.port=8847 server.port=8846 b 数据库配置 此步骤为可选步骤。nacos默认使用的是内嵌数据库,我们可以通过配置数据库信息,将数据信息存储在我们的mysql数据库中,可以通过在三个服务的配置文件中均添加以下mysql配置来实现 # db mysql spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:mysql://localhost:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true db.user=root db.password=123456 而这个nacos_config数据库的建表脚本在该目录下的nacos-mysql.sql文件中 4)配置cluster.conf文件 同样在conf目录下,分别将原有的cluster.conf.example文件重命名为cluster.conf,并分别添加如下配置 #it is ip #example 192.168.31.95:8848 192.168.31.95:8847 192.168.31.95:8846 5)分别启动服务 linux和mac环境中默认启动是以集群方式启动的,因此直接执行启动命令即可。因此分别进入三个服务的bin目录,执行 ./startup.sh 分别启动服务 6)访问页面,查看集群状态 此时访问http://192.168.31.95:8848/nacos,我们可以看到目前的集群列表有三个节点,一个LEADER和两个FOLLOWER 81f657b3952ed94afbcbc286b900d165.png 那么,到目前为止,我们的集群就搭建起来啦~其实跟zookeeper的集群部署过程很类似啦 或者说在本质上,集群环境的部署,都需要多注意这么几点就可以了:
  • 配置不同的服务端口
  • 配置声明集群的所有节点
  • 满足一定的节点个数

二 原理

源码探究

nacos,经常是以集群形式作为日常的开发或生产环境中的注册中心(当然也经常以配置中心的角色出现) 首先作为一个集群环境,nacos的各个节点的服务是如何保持同步的呢? 作为一个注册中心,它的主要作用实际上就是对服务的管理。那么,客户端各个服务的状态发生变化,注册中心又是如何进行感知并做处理的呢? 我们将分别从以下几个方面进行一步步探究 1)  选举过程 2)心跳过程(集群节点之间) 3)实例注册(服务的变化) 4)实例同步

01.选举过程

1.1 理论分析 nacos-server的节点分为三类:leader、follower以及candidate Leader:负责Client交互和log复制,同一时刻系统中最多存在1个 Follower:被动响应请求RPC,从不主动发起请求RPC。接收到请求,会转发给leader处理 Candidate:一种临时的角色,只存在于leader的选举阶段 某个节点想要变成leader,那么就发起投票请求,同时自己变成candidate,如果选举成功,则变为candidate,否则退回为follower 具体流程如下图所示: f581e9b25639a0a6c0ebd800bef0cb43.png a 集群启动后,初始节点状态都是 Follower 状态 b 某个follower想要被选举成为leader,于是变成 candidate (候选人)状态,向自己投票,并向其他follower发起投票请求 c 收到其他节点的投票响应以后,若 超过半数的follower 都投了自己,则投票成功,自己的状态变为 leader d 如果并没有收到大多数的选票,则进行新一轮的投票 1.2 源码 nacos主要是以raft算法来实现服务端的一系列功能的,因此我们主要来看看 RaftCore 类 1)init()方法 e29058e6b8a59a7c3bf5575639eaeacf.png 我们看到,init()方法分别完成了以下动作: a 启动Notifier通知器 b 从磁盘加载Datanum和term数据,进行当前节点的数据恢复 c 分别注册两个定时器:选举任务和心跳任务 其中,最主要的的是进行 选举和心跳 两个任务的注册过程 nacos通过定时500毫秒执行一次选举,即执行MasterElection  下面来看一下 MasterElection 的具体执行逻辑 71f8023c120e087b7c587deadc098050.png 2)进入投票 a 若leader的任期时间大于0,则直接返回,不用进行投票 local.leaderDueMs的值一开始是随机生成的,范围是[0, 15000)(单位是毫秒)此后按照500ms的梯度进行递减,减少到不大于0后,就会触发选举操作 b 当leader的任期时间local.leaderDueMs<=0时,进入选举 首先,分别 重置 超时时间和心跳时间 resetLeaderDue()方法是把 leaderDueMs 变量重新赋值,在初始值15000毫秒的基础上加上了一个随机值,其随机值的范围为[0, 5000)毫秒 2944a0ada7cf10015311f7a2aa1799c6.png
resetHeartbeatDue()方法则是,重置心跳间隔时间heartbeatDueMs
为5s
d868ca3a81caaa4ffc86b590da67b51d.png
最后,调用sendVote()方法进行真正的选举操作
3)进入sendVote()方法
739e2d40a0eb3cbd5e424bc4eddb80d3.png
a 组合url,发送投票调用投票的接口:/v1/ns/raft/vote
b 获取请求结果
c 调用decideLeader()方法,确定leader4)进入/v1/ns/raft/vote接口a 接收并处理投票请求b 进入receivedVote()方法该方法进行如下处理:
  • 判断所有的集群节点中是否包含这个请求的远程节点,若不包含则抛出异常

  • 判断请求服务的任期是否大于自己,若不大于,则投票给自己

  • 重置leader的任期时间

  • 当请求服务的任期大于自己,则投票给这个服务,并将自己的状态置为follower

5)进入decideLeader()方法
fbedf84d332c2dae4e5b253e7ec2329e.png a 比较收集到的最大支持者的投票数,是否大于等于大多数节点数量 b 若是,则获取获得最大投票数的节点,并将该获得最多票数的节点设置为leader c 向其他节点发送 通 知消息-该节点已经变成了leader

02.心跳过程

我们看到,在RaftCore.init()中,除了注册了选举的定时任务,还通过

GlobalExecutor.register(new HeartBeat())注册了心跳定时任务

而需要知道的是,关于心跳检测,是由leader向各个follower节点发送,以表示自己依旧是健康状态,完全可以担任leader 每500ms检查是否还在时间间隔内(5s),若超过5s,某个follower还没有接收到leader的心跳的话,follower就会发起投票,选择自己为leader 1)进入HeartBeat#run()方法 779c6c17c3b500a9af6f45d0f845638b.png   a 与选举定时任务的处理类似,进入心跳检测前,会先按照500ms的梯度进行递减,减少到不大于0后,触发心跳 b 执行方法sendBeat(),发送心跳包 2)进入sendBeat()方法 由于方法比较长,我们分为两部分进行解读 第一部分 基本判断 476941ccefdceca959708d8ffd6fc037.png a 首先,判断该节点是否是leader,若不是,则不需要发送心跳 b 若是leader,则重置leader的任期时间leaderDueMs,顺便说一句,这个时间间隔,就是如果收不到leader的心跳,该节点就会进行重新选举的时间间隔 第二部分 心跳包数据组装 783c11d30f63648dbfd78f0a2b2be06f.png 分为两种情况进行发送数据的处理 a 只需要发送心跳包,不用做任何处理 b 除了发送心跳包,还要发送数据,则需要将数据都添加到packet中(这些数据包含注册的实例的更新情况) c 最后,将所有数据最终都放入params中,转换成json,并使用 gzip 压缩 第三部分 心跳发送 dc12e7b7ab43d71dd5238159750ad135.png a 构造url b 分别向所有follower节点发送心跳包 3)接收到心跳包 调用/beat接口,进行心跳的发送,同时返回接收结果 7aa3cb766d58e1f68676e20eb9ede277.png 4)进入RaftCore#receivedBeat()方法 分为两个部分来看 第一部分  逻辑判断,排除数据过期等场景 e426c449f49c106d8a196070cc4f170b.png a 分别构造当前节点信息local和发送心跳的节点信息remote b 若发送节点不是leader,则不符合逻辑(只能由leader发起心跳),抛出异常 c 如果local的任期大于remote,说明信息已过期,抛出异常 d 若本地节点不是follower状态,不合逻辑,将本地的节点状态更新为follower 第二部分 收集数据,实现服务的更新 4f82f7b8bdabbd46e130510038d52663.png a 遍历请求参数中的datums,如果Follwoer不存在这个 datumKey 或者时间戳比较旧,则收集这个datumKey b 当datnum的数量 小于50 时,继续进行收集 c 当数量 大于等于50 时,进行异步发送,获取对应的50个最新的Datnum对象 第三部分 将数据添加到缓存中,并更新参数

5ebd64e3f913a9f49b31fdfb32a281c7.png

a 调用RaftStore#write()方法,将Datum序列化为json写到本地缓存中

b 将Datum存放到RaftCore的datums集合中

c 调用

notifier.addTask(datum, Notifier.ApplyAction.CHANGE)

通知对应的RaftListener,删除key对应的旧的Datum

d 重置leaderDue时间

e 更新本地节点的任期term

小结 1

选举和心跳部分的源码处理流程,用一个图表示,你就更清晰啦

00d2c942588c2feeeade6d59caba5187.png

03.实例注册过程

当新服务启动,需要注册在nacos-server上,那么此时就需要进行进行服务的注册,进入ServiceManager#registerInstance()方法 1)进入ServiceManager#registerInstance()方法 b423e52ab7a30bfd2e1ebf48028eea3a.png a 创建空service b 调用addInstance()方法添加新的实例 2)进入addInstance()方法
2d7499270fc1bd714fd1a5d70d33da9c.png 该方法主要添加 instance 到缓存中,并且持久化
3)进入addIpAddresss()方法 b148d0a2209421da8481aba3102fb17c.png 该方法通过调用updateIpAddresss()方法进行具体处理 4)进入updateIpAddresss()方法 d4ffa5f22250f6755de219395e2ea133.png 我们看到该方法实际上是通过调用setValid()方法,将旧实例列表与新实例列表进行合并的 4fa588a1235fc60506b60e60fdbd8519.png 5)实例信息持久化consistencyService.put(key, instances) 再来回到实例信息持久化的方法,该方法通过调用RaftCore#signalPublish()方法,进行具体的实例信息持久化 77a46c2d7b68d5d56df35c89416f1fc4.png 6)实现实例信息持久化,分为两个部分 第一部分 基本判断 a 若节点不为leader状态,则转发请求给leader b 若节点为leader状态,则将包发送给所有follower d439b4b3595128d0104ca62177bfb8a1.png 第二部分 发送消息,同步给大多数节点 发送消息,同步等待,接受到大多数节点的响应之后,返回成功 65a3089fec16674ba908435e6bb46b77.png 小结 2 实例注册过程 的源码处理流程如下图

4f4214cca39c3a2f2e85b12d1d89377f.png

04.实例同步过程

Nacos通过Raft发布内容,内容只是存在了Leader节点上,而要实现服务信息的同步,则采用Raft心跳机制实现 在说心跳过程的时候,我们提到了,leader发送心跳请求时,会分为两种情况,有数据和无数据,其中有数据情况下的数据就是我们的需要同步的服务数据 我们来理一下,在注册服务的时候,addInstance() 方法将 instance 添加到了本地缓存中 然后,raft实现数据从leader到follower的数据同步。follower 接收到包之后,通过 onPublish() 方法进行了持久化,但没有将信息更新到本地缓存,更新到本地缓存,这一动作,实际上是通过一个监听器来实现: notifier.addTask(datum.key, ApplyAction.CHANGE) 即:将本次的变动,添加到通知任务中,然后进行后续处理 我们来继续看看,通知任务将如何被处理 1)添加服务变更到tasks队列 aefc595f8516d69d2362a44d8d908ab7.png 2)删除任务列表中的Key,根据变更类型,调用对应的方法进行缓存更新 7613ed14b41ad673bdae66c016ee6161.png 于是,服务便得到了真正的同步 小结3 实例同步过程 ,源码流程如下图

6732d92788c0d1af35777b6118212078.png

四 总结

总而言之

好了,今天的推送到这里就结束啦,肝源码,真的是一件枯燥的,艰难的,费时的事情。本篇文章,不夸张的说,杰哥我花了一周多的下班时间准备,本来想着最多两天搞定的。。。 不过还好,完成之后,感觉收获也很大,相信这篇文章,也会带给你们更大的收获 进行分条总结一番~ 1 搭建nacos集群环境 2 源码探究 - 选举 3 源码探究 - 心跳 4 源码探究 - 注册 5 源码探究 - 服务同步 本篇文章,通过搭建集群环境,让大家对于nacos的集群环境有了初步的印象。然后通过跟踪源码,针对nacos服务端的 选举 、 心跳 、 服务注册 以及 服务同步 ,分别进行梳理以及步骤说明,并且每个环节结束,都以一张总体源码流程图进行总结。 相信大家对于 nacos作为注册中心的基本机制 有了一定的了解了! 嗯,就这样。每天学习一点,时间会见证你的强大~ 下期预告: Spring Cloud(六):注册中心nacos-站在客户端角度 178bd4b921014e41b76b5599b34ac421.png

往期精彩回顾

9c99d9c6762c48aaca60d2c640c3445f.png Spring Cloud(五):注册中心-nacos篇 Spring Cloud(四):公司内部,关于Eureka和zookeeper的一场辩论赛 Spring Cloud(三):注册中心zookeeper-站在客户端角度 Spring Cloud(二):在实战中深入zookeeper服务端机制 Spring Cloud(一):我与导师的对话:你真的了解zookeeper吗? Spring Boot(十):注册中心Eureka-客户端视角 Spring Boot(九):注册中心Eureka-服务端视角 Spring Boot(八):Spring Boot的监控法宝:Actuator Spring Boot(七):你不能不知道的Mybatis缓存机制! Spring Boot(六):那些好用的数据库连接池们 Spring Boot(五):春眠不觉晓,Mybatis知多少 Spring Boot(四):让人又爱又恨的JPA Spring Boot(三):操作数据库-Spring JDBC SpringBoot(二):第一个Spring Boot项目 SpringBoot(一):特性概览 最近新建了一个微信交流群,里面都是真正对技术感兴趣的小伙伴,欢迎添加讨论~若群名片过期了,可以在后台回复进群,拉你入群哦~

f240d4f4bebbc0f9909d5dc537274e65.png

也欢迎大家关注们的公众号,一起持续性学习吧~ b65a65962577c0fde93e7bf317cac201.gif                 原创不易,如果对你有了一点点帮助,点亮 在看 呗~
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值