【微服务】Nacos 原理
在系统开发过程中通常会将⼀些需要变更的参数、变量等从代码中分离出来独立管理,以独立的配置文件的形式存在。如常见的 yml、properties。修改这些配置需要重启,当服务实例很多的时候重启将会是一种对稳定性的大考验。Nacos 的配置模型解决了这个问题。
1.配置管理模型
在系统开发过程中通常会将⼀些需要变更的参数、变量等从代码中分离出来独立管理,以独立的配置文件的形式存在。如常见的 yml、properties。修改这些配置需要重启,当服务实例很多的时候重启将会是一种对稳定性的大考验。Nacos 的配置模型解决了这个问题。
1.1.解决了什么问题
- 支持动态修改配置,不必重启服务
- 实时动态变更
- 管控控制实时动态变更带来的风险,如灰度、回滚等
- 敏感配置做到安全配置
1.2.模型
- Nacos 提供可视化的控制台,可以对配置进行发布、更新、删除、灰度、版本管理等功能。
- SDK 可以提供发布配置、更新配置、监听配置等功能。
- SDK 通过 GRPC 长连接监听配置变更,Server 端对比 Client 端配置的 MD5 和本地 MD5 是否相等,不相等推送配置变更。
- SDK 会保存配置的快照,当服务端出现问题的时候从本地获取。
对于多套配置引用了 Namespace。从单个租户的角度来看,我们要配置多套环境(开发、测试、线上)的配置,可以根据不同的环境来创建 Namespace。从多个租户的角度来看,每个租户都可以有自己的命名空间。我们可以为每个用户创建⼀个命名空间,并给用户分配对应的权限。
2.内核设计
2.1.强一致性
集群模式下,需要考虑如何保障各个节点之间的数据⼀致性以及数据同步,而要解决这个问题,就不得不引入共识算法,通过算法来保障各个节点之间的数据的⼀致性。这个一致性表现在两个方面:从服务注册发现来看,需要在任何场景下,尽最大可能保证服务注册发现能力可以对外提供服务,而心跳完成了服务数据的补偿;从配置管理来看,必须保证大部分的节点都保存了此配
置数据才能认为配置被成功保存,否则就会丢失配置的变更,这就要求集群中大部分的节点是强⼀致的,因此只能使用强⼀致性共识算法。共识算法通常基于状态复制机(Replicated State Machine)模型,也就是所有节点从同一个 state 出发,经过同样的操作 log,最终达到一致的 state。
2.1.1.Raft 协议
对于强⼀致性共识算法,当前工业生产中,最多使用的就是 Raft 协议。它是一个共识算法,所谓共识,就是多个节点对某个事情达成一致的看法,即使是在部分节点故障、网络延时、网络分割的情况之下。
2.1.1.1. leader 选举
Raft 会先选举出 leader,leader 完全负责 replicated log 的管理。leader 负责接收所有客户端更新请求,然后复制到 follower 节点,并在认为安全的时候执行这些请求。如果 leader 发生故障,followes 会重新选举出新的 leader。
节点有三种状态:leader、follower、candidate(候选人)。所有节点启动时都是 follower 状态;在一段时间内如果没有收到来自 leader 的心跳,就会从 follower 切换到 candidate 发起选举(election)。任期(term)以选举开始,然后是一段或长或短的稳定工作期(normal operation)。如果收到 majority 的投票(含自己的一票)则该节点切换到 leader 状态;如果发现其他节点比自己更加新,则主动切换到 follower。
下面是较为详细的选举过程:
(1) 如果follower在 election timeout 内没有收到来自 leader 的心跳,则会主动发起选举;
(2) 增加节点本地的 current term ,切换到 candidate 状态;
(3) 投自己一票;
(4) 并行给其他节点发送 Request Vote RPCs;
(5) 等待其他节点的回复,根据来自其他节点的消息,可能出现三种结果:收到 majority 的投票(含自己的一票),则赢得选举,成为 leader;被告知别人已当选,那么自行切换到 follower;一段时间内没有收到 majority 投票,则保持 candidate 状态,重新发出选举。
每一个任期有且仅有一个 leader 节点,这是由选举规则决定。follower 在等待 leader 心跳信息超时后,推荐自己为 candidate,会增加自己的任期号,但并不是每个任期都一定对应一个 leader,有时候某个任期内会由于选举超时导致选不出 leader,这时 candicate 会递增任期号并开始新一轮选举。当节点发现自己的任期编号比其他节点小时,会更新到较大的编号值。如果一个 candicate 人或者 leader,发现自己的任期编号比其他节点小,那么它会立即恢复成 follower 状态。
2.1.1.2.日志复制
Raft 赋予了 leader 节点更强的领导力,称之为 Strong Leader。leader 被选举出来后,主要工作就是接收写请求,将操作包装为日志同步给其它节点,在保证大部分节点都同步了本次操作后,就可以安全地给客户端响应。这一部分工作在 Raft 核心算法中叫日志复制。共识算法的实现一般是基于复制状态机(Replicated state machines)。
(1) leader 将客户端请求封装到一个个 log entry;
(2) 将这些 log entries 复制(replicate)到所有 follower 节点,向其它发起附加条目请求(AppendEntries RPC);
(3) follower 按相同顺序应用 log entry 中的请求,则状态肯定是一致的;下图中的 Consensus Module 即是一致性模块;
(4) 当 leader 得知某条日志被集群过半的节点复制成功时,就可以进行 commit,committed 日志一定最终会被状态机 apply。leader 会将该日志 apply 到它本地的状态机中,然后把操作成功的结果返回给客户端。
每条日志除了存储状态机的操作指令外,还会拥有一个唯一的整数索引值(log index)来表明它在日志集合中的位置。每条日志还会存储一个 term 号,该 term 表示 leader 收到这条指令时的当前任期,term 相同的 log 是由同一个 leader 在其任期内发送的。
如果不同的节点日志集合中的两个日志条目拥有相同的 term 和 index,那么它们一定存储了相同的指令。
对于日志不一致的问题,不管是 follower 比 leader 日志多还是 leader 比 follower 日志多,Raft 强制要求 follower 必须复制 leader 的日志集合来解决不一致问题。follower 上任何与 leader 不一致的日志,都会被 leader 上的日志所覆盖。
2.1.1.3.正确性
选举机制无法保证每个节点的状态机会严格按照相同的顺序 apply 日志。假如发生如下步骤:
(1) leader 将一些日志复制到了大多数节点上,进行 commit 后发生了宕机;
(2) 某个 follower 并没有被复制到这些日志,但它参与选举并当选了下一任 leader;
(3) 新的 leader 又同步并 commit 了一些日志,这些日志覆盖掉了其它节点上的上一任 committed 日志;
(4) 各个节点的状态机可能 apply 了不同的日志序列,出现了不一致的情况。
为了避免这种情况,每个 candidate 必须在 RequestVote RPC 中携带自己本地日志的最新 term 和 index,如果 follower 发现这个 candidate 的日志还没有自己的新,则拒绝投票给该 candidate。这里并不是已提交到状态机的日志 index,而是日志序列里面的 index。candidate 想要赢得选举成为 leader,必须得到集群大多数节点的投票,那么它的日志就一定至少不落后于大多数节点。又因为一条日志只有复制到了大多数节点才能被 commit,因此能赢得选举的 candidate 一定拥有所有 committed 日志,至于未提交的日志,稍后一定会变为提交状态,因为可以选出来的 leader 一定是日志最多,未提交的日志一定是复制超过半数。
另外,除了对选举增加一点限制外,我们还需对 commit 行为增加一点限制。主要原因是 leader 并不能在任何时候都随意 commit 旧任期留下的日志,假如发生如下步骤:
(1) 假设共4个节点ABCD,leader A 收到请求后将日志 (term1, index1) 只复制给了一个 follower B,尚未复制给其他 follower C、D;
(2) A 宕机,C 当选 term2 的 leader,收到请求后保存了日志 (term2, index1) ,尚未复制给任何节点;
(3) C 宕机,A 恢复,A 重新当选 term3 的 leader,继续将 (term1, index1) 复制给了 D,已经满足大多数节点,我们将其 commit;
(4) A 又宕机,C 恢复,C 重新当选 leader,将 (term2, index1) 复制给了所有节点并 commit。注意,此时发生了致命错误,已经 committed 的 (term1, index1) 被 (term2, index1) 覆盖了。
因此,需要限制 leader 只允许 commit 包含当前 term 的日志。
2.2.Distro 协议
Distro 协议是 Nacos 社区自研的一种 AP 分布式协议,是面向临时实例设计的一种分布式协议,其保证了在某些 Nacos 节点宕机后,整个临时实例处理系统依旧可以正常工作。作为⼀种有状态的中间件应用的内嵌协议,Distro 保证了各个 Nacos 节点对于海量注册请求的统⼀协调和存储。
2.2.1.数据初始化
新加入的 Distro 节点会进行全量数据拉取。具体操作是轮询所有的 Distro 节点,通过向其他的机器发送请求拉取全量数据。
2.2.2.数据校验
在 Distro 集群启动之后,各台机器之间会定期的发送心跳。心跳信息主要为各个机器上的所有数据的元信息。这种数据校验会以心跳的形式进行,即每台机器在固定时间间隔会向其他机器发起⼀次数据校验请求。一旦发现不⼀致,则会发起⼀次全量拉取请求,将数据补齐。
2.2.3.写操作
(1) 前置的 Filter 拦截请求,并根据请求中包含的 IP 和 port 信息计算其所属的 Distro 责任节点,并将该请求转发到所属的 Distro 责任节点上;
(2) 责任节点上的 Controller 将写请求进行解析;
(3) Distro 协议定期执行 Sync 任务,将本机所负责的所有的实例信息同步到其他节点上。
2.3.通信通道
2.3.1.配置对连接的诉求
- SDK 和 Server 之间
客户端 SDK 需要感知服务节点列表,并按照某种策略选择其中⼀个节点进行连接;底层连接断开时,需要进行切换 Server 进行重连;
客户端基于当前可用的长链接进行配置的查询,发布,删除,监听,取消监听等配置领域的 RPC 语意接口通信;
感知配置变更消息,需要将配置变更消息通知推送当前监听的客户端;网络不稳定时,客户端接收失败,需要支持重推,并告警;
感知客户端连接断开事件,将连接注销,并且清空连接对应的上下文,比如监听信息上下文清理。 - Server 之间
单个 Server 需要获取到集群的所有 Server 间的列表,并且为每⼀个 Server 创建独立的长连接;
连接断开时,需要进行重连,服务端列表发生变更时,需要创建新节点的长连接,销毁下线的节点长连接。
Server 间需要进行数据同步,包括配置变更信息同步,当前连接数信息,系统负载信息同步,负载调节信息同步等。
2.3.2.服务对连接的诉求
- SDK 和Server 之间
客户端 SDK 需要感知服务节点列表,并按照某种策略选择其中⼀个节点进行连接;底层连接断开时,需要切换 Server 进行重连;
客户端基于当前可用的长链接进行配置的查询,注册,注销,订阅,取消订阅等服务发现领域的 RPC 语意接口通信;
感知服务变更,有服务数据发生变更,服务端需要推送新数据到客户端;需要有推送 ack,方便服务端进行 metrics 和重推判定等;
感知客户端连接断开事件,将连接注销,并且清空连接对应的上下文,比如该客户端连接注册的服务和订阅的服务。 - Server 之间
服务端之间需要通过长连接感知对端存活状态,需要通过长连接汇报服务状态(同步 RPC 能力);
服务端之间进行 AP Distro 数据同步,需要异步 RPC 带 ack 能力。
2.3.3.连接生命周期
2.4.寻址机制
2.4.1.单机寻址
单机模式的寻址模式就是找到自己的 IP:PORT 组合信息,然后格式化为⼀个节点信息,调用 afterLookup 然后将信息存储到ServerMemberManager 中。
2.4.2.文件寻址
文件寻址模式就是每个 Nacos 节点需要维护⼀个叫做 cluster.conf 的文件。该文件默认只需要填写每个成员节点的 IP 信息即可。当 Nacos 节点启动时,会读取该文件的内容,然后将文件内的 IP 解析为节点列表,调用 afterLookup 存入 ServerMemberManager。
2.4.3.地址服务器寻址
地址服务器寻址模式是 Nacos 官方推荐的⼀种集群成员节点信息管理,该模式利用了⼀个简易的 web 服务器,用于管理 cluster.conf 文件的内容信息,这样,运维人员只需要管理这⼀份集群成员节点内容即可,而每个 Nacos 成员节点,只需要向这个 web 节点定时请求当前最新的集群成员节点列表信息即可。
3.注册中心原理
3.1.数据模型
Nacos 在经过内部多年生产经验后提炼出的数据模型,是⼀种服务-集群-实例的三层模型。
服务包括健康检查开关、元数据、路由机制、保护阈值;集群包括健康检查模式、元数据、同步机制;实例包括IP、端口、权重、健康状态、在离线状态、元数据、响应时间。
3.2.服务注册
(1) provider 在服务端本地通过轮询配置的注册中心集群地址,通过 Open API 向该地址进行服务注册;
(2) provider 提供可供监测健康状态的接口,注册中心向 provider 定时发送健康检查,请求服务端提供的监测接口;
(3) consumer 定时从服务注册中心拉取最新的 provider 实例列表,订阅某些 provider;
(4) 当注册中心发现 provider 处于不健康状态时,向订阅了这些 provider 的 consumer 发送推送;
(5) 在实际调用时,consumer 同 provider 建立RPC长连接通信。
3.3.健康检查机制
Nacos 支持客户端健康检查和服务端健康检查。客户端健康检查主要关注客户端上报心跳的方式、服务端摘除不健康客户端的机制。而服务端健康检查,则关注探测客户端的方式、灵敏度及设置客户端健康状态的机制。同⼀个服务可以切换健康检查模式。
客户端健康检查支持 TTL 机制,即如果客户端在⼀定时间内没有向注册中心发送心跳,则会将这个客户端摘除。
Nacos 目前支持临时实例使用心跳上报方式维持活性,发送心跳的周期默认是 5 秒,Nacos 服务端会在 15 秒没收到心跳后将实例设置为不健康,在 30 秒没收到心跳时将这个临时实例摘除。
有⼀些服务无法上报心跳,但是可以提供⼀个检测接口,由外部去探测。服务端健康检查最常见的方式是 TCP 端口探测和 HTTP 接口返回码探测,这两种探测方式因为其协议的通用性可以支持绝大多数的健康检查场景。
4.参考
《Nacos架构&原理》
https://www.cnblogs.com/xybaby/p/10124083.html
https://blog.csdn.net/qq_24768941/article/details/123682714
更多推荐
所有评论(0)