计算机系统应用教程网站

网站首页 > 技术文章 正文

Spring Cloud(六):注册中心nacos-服务端视角

btikc 2024-09-20 14:50:55 技术文章 25 ℃ 0 评论

大家好,我是杰哥


上周,我们通过文章Spring Cloud(五):注册中心-nacos篇,对nacos作为注册中心,有了一个基本的了解。那么按照惯例,今天就要进行真正有料的环节-源码探究


本篇,将站在服务端的视角,对于nacos作为服务注册中心的选举心跳、注册以及服务同步几个重要动作的流程解析,一步步深入了解nacos注册中心的真正原理


为了方便跟踪源码,我们先来一起搭建一个nacos的集群环境


一 环境

集群搭建


01.nacos集群环境架构图


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


02.集群部署步骤


整体部署过程与单点部署过程基本一致,只是集群模式是多节点的模式而已

可以参照上一篇文章中的单点部署过程


1)安装包地址


https://github.com/alibaba/nacos/releases


2)解压


解压压缩包为nacos,并复制为nacos-1nacos-2


3)配置application.properties文件


分别进入nacos/confnacos-1/confnacos-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



那么,到目前为止,我们的集群就搭建起来啦~其实跟zookeeper的集群部署过程很类似啦


或者说在本质上,集群环境的部署,都需要多注意这么几点就可以了:


  • 配置不同的服务端口
  • 配置声明集群的所有节点
  • 满足一定的节点个数


二 原理

源码探究


nacos,经常是以集群形式作为日常的开发或生产环境中的注册中心(当然也经常以配置中心的角色出现)


首先作为一个集群环境,nacos的各个节点的服务是如何保持同步的呢?


作为一个注册中心,它的主要作用实际上就是对服务的管理。那么,客户端各个服务的状态发生变化,注册中心又是如何进行感知并做处理的呢?


我们将分别从以下几个方面进行一步步探究


1) 选举过程

2)心跳过程(集群节点之间)

3)实例注册(服务的变化)

4)实例同步


01.选举过程


1.1 理论分析


nacos-server的节点分为三类:leaderfollower以及candidate


Leader:负责Client交互和log复制,同一时刻系统中最多存在1个

Follower:被动响应请求RPC,从不主动发起请求RPC。接收到请求,会转发给leader处理

Candidate:一种临时的角色,只存在于leader的选举阶段

某个节点想要变成leader,那么就发起投票请求,同时自己变成candidate,如果选举成功,则变为candidate,否则退回为follower

具体流程如下图所示:

a 集群启动后,初始节点状态都是 Follower 状态

b 某个follower想要被选举成为leader,于是变成candidate(候选人)状态,向自己投票,并向其他follower发起投票请求

c 收到其他节点的投票响应以后,若超过半数的follower都投了自己,则投票成功,自己的状态变为leader

d 如果并没有收到大多数的选票,则进行新一轮的投票


1.2 源码


nacos主要是以raft算法来实现服务端的一系列功能的,因此我们主要来看看RaftCore


1)init()方法



我们看到,init()方法分别完成了以下动作:


a 启动Notifier通知器

b 从磁盘加载Datanum和term数据,进行当前节点的数据恢复

c 分别注册两个定时器:选举任务心跳任务


其中,最主要的的是进行选举和心跳两个任务的注册过程


nacos通过定时500毫秒执行一次选举,即执行MasterElection

下面来看一下 MasterElection 的具体执行逻辑


2)进入投票


a 若leader的任期时间大于0,则直接返回,不用进行投票


local.leaderDueMs的值一开始是随机生成的,范围是[0, 15000)(单位是毫秒)此后按照500ms的梯度进行递减,减少到不大于0后,就会触发选举操作


b 当leader的任期时间local.leaderDueMs<=0时,进入选举


首先,分别重置超时时间和心跳时间


resetLeaderDue()方法是把leaderDueMs变量重新赋值,在初始值15000毫秒的基础上加上了一个随机值,其随机值的范围为[0, 5000)毫秒



resetHeartbeatDue()方法则是,重置心跳间隔时间heartbeatDueMs
为5s
最后,调用sendVote()方法进行真正的选举操作
3)进入sendVote()方法
a 组合url,发送投票调用投票的接口:/v1/ns/raft/vote
b 获取请求结果
c 调用decideLeader()方法,确定leader
4)进入/v1/ns/raft/vote接口
a 接收并处理投票请求

b 进入receivedVote()方法

该方法进行如下处理:
判断所有的集群节点中是否包含这个请求的远程节点,若不包含则抛出异常判断请求服务的任期是否大于自己,若不大于,则投票给自己重置leader的任期时间
当请求服务的任期大于自己,则投票给这个服务,并将自己的状态置为follower
5)进入decideLeader()方法



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()方法



a 与选举定时任务的处理类似,进入心跳检测前,会先按照500ms的梯度进行递减,减少到不大于0后,触发心跳


b 执行方法sendBeat(),发送心跳包


2)进入sendBeat()方法


由于方法比较长,我们分为两部分进行解读


第一部分 基本判断



a 首先,判断该节点是否是leader,若不是,则不需要发送心跳


b 若是leader,则重置leader的任期时间leaderDueMs,顺便说一句,这个时间间隔,就是如果收不到leader的心跳,该节点就会进行重新选举的时间间隔


第二部分 心跳包数据组装



分为两种情况进行发送数据的处理


a 只需要发送心跳包,不用做任何处理


b 除了发送心跳包,还要发送数据,则需要将数据都添加到packet中(这些数据包含注册的实例的更新情况)


c 最后,将所有数据最终都放入params中,转换成json,并使用gzip压缩


第三部分 心跳发送



a 构造url

b 分别向所有follower节点发送心跳包


3)接收到心跳包


调用/beat接口,进行心跳的发送,同时返回接收结果



4)进入RaftCore#receivedBeat()方法


分为两个部分来看


第一部分 逻辑判断,排除数据过期等场景



a 分别构造当前节点信息local和发送心跳的节点信息remote


b 若发送节点不是leader,则不符合逻辑(只能由leader发起心跳),抛出异常

c 如果local的任期大于remote,说明信息已过期,抛出异常

d 若本地节点不是follower状态,不合逻辑,将本地的节点状态更新为follower


第二部分 收集数据,实现服务的更新



a 遍历请求参数中的datums,如果Follwoer不存在这个datumKey或者时间戳比较旧,则收集这个datumKey


b 当datnum的数量小于50时,继续进行收集


c 当数量大于等于50时,进行异步发送,获取对应的50个最新的Datnum对象


第三部分 将数据添加到缓存中,并更新参数



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


b 将Datum存放到RaftCore的datums集合


c 调用

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

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


d 重置leaderDue时间


e 更新本地节点的任期term


小结 1


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

03.实例注册过程


当新服务启动,需要注册在nacos-server上,那么此时就需要进行进行服务的注册,进入ServiceManager#registerInstance()方法


1)进入ServiceManager#registerInstance()方法



a 创建空service


b 调用addInstance()方法添加新的实例


2)进入addInstance()方法



该方法主要添加 instance 到缓存中,并且持久化


3)进入addIpAddresss()方法



该方法通过调用updateIpAddresss()方法进行具体处理


4)进入updateIpAddresss()方法



我们看到该方法实际上是通过调用setValid()方法,将旧实例列表与新实例列表进行合并的



5)实例信息持久化consistencyService.put(key, instances)


再来回到实例信息持久化的方法,该方法通过调用RaftCore#signalPublish()方法,进行具体的实例信息持久化



6)实现实例信息持久化,分为两个部分


第一部分 基本判断


a 若节点不为leader状态,则转发请求给leader

b 若节点为leader状态,则将包发送给所有follower


第二部分 发送消息,同步给大多数节点


发送消息,同步等待,接受到大多数节点的响应之后,返回成功



小结 2


实例注册过程的源码处理流程如下图

04.实例同步过程


Nacos通过Raft发布内容,内容只是存在了Leader节点上,而要实现服务信息的同步,则采用Raft心跳机制实现

在说心跳过程的时候,我们提到了,leader发送心跳请求时,会分为两种情况有数据无数据,其中有数据情况下的数据就是我们的需要同步的服务数据

我们来理一下,在注册服务的时候,addInstance() 方法将 instance 添加到了本地缓存

然后,raft实现数据从leader到follower的数据同步。follower 接收到包之后,通过 onPublish() 方法进行了持久化,但没有将信息更新到本地缓存,更新到本地缓存,这一动作,实际上是通过一个监听器来实现:

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

即:将本次的变动,添加到通知任务中,然后进行后续处理

我们来继续看看,通知任务将如何被处理

1)添加服务变更到tasks队列


2)删除任务列表中的Key,根据变更类型,调用对应的方法进行缓存更新



于是,服务便得到了真正的同步

小结3

实例同步过程,源码流程如下图


四 总结

总而言之


好了,今天的推送到这里就结束啦,肝源码,真的是一件枯燥的,艰难的,费时的事情。本篇文章,不夸张的说,杰哥我花了一周多的下班时间准备,本来想着最多两天搞定的。。。

不过还好,完成之后,感觉收获也很大,相信这篇文章,也会带给你们更大的收获

进行分条总结一番~

1 搭建nacos集群环境

2 源码探究 - 选举

3 源码探究 - 心跳

4 源码探究 - 注册

5 源码探究 - 服务同步

本篇文章,通过搭建集群环境,让大家对于nacos的集群环境有了初步的印象。然后通过跟踪源码,针对nacos服务端的选举心跳服务注册以及服务同步,分别进行梳理以及步骤说明,并且每个环节结束,都以一张总体源码流程图进行总结。

相信大家对于nacos作为注册中心的基本机制有了一定的了解了!


嗯,就这样。每天学习一点,时间会见证你的强大~


下期预告:


Spring Cloud(六):注册中心nacos-站在客户端角度


本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表