前言

为了对分布式系统有更深的理解,也为了我的技术成长提供更多可能性,我花了部分时间对目前分布式系统设计中比较常用或者重要的算法进行了学习,最终整理出这篇文章。以前学习时,对于分布式,高可用,集群,leader选举等一系列核心问题都是一知半解,希望日后我也能时常勉励自己,有时间可以再回过头来看看这篇博客,做到见微知著,举一反三。

Paxos算法简介

  • 我看了很多文章,对于Paxos算法还是一知半解,所以这里只是对Paxos算法做简单的总结。

  • Paxos算法包含两个重要部分

    1. Basic-Paxos算法,描述的是多节点之间如何就某个提案达成共识
    2. Muti-Paxos算法,描述的是执行多个Basic-Paxos实例,就一系列提案达成共识。下面要说的的Raft算法就是基于Multi-Paxos思想的共识算法之一。

Raft算法

  • Raft算法是现在分布式系统开发首选的共识算法。
  • Raft算法是强领导者模型,集群中只能有一个Leader

成员身份

  1. Follower:接收和处理来自Leader的消息,跟Leader心跳超时时,就变为Candidate
  2. Candidate:候选人向其他节点发送RPC消息请求投票,通知它们投票,如果赢得了大多数选票就晋升为Leader
  3. Leader:处理写请求,管理日志复制和不断发送心跳消息,通知其他节点。

Leader选举

  • 初始状态时,集群中所有的节点都是跟随者的状态。Raft实现了随机超时时间的特性,每个节点等待领导者节点心跳信息的超时时间间隔是随机的。可以看到,下图中节点A会最先因为没有收到leader心跳信息发生超时。这个时候节点A就会增加自己的任期编号,并且推举自己为Candidate,并给自己投上一票,然后向其他节点发送RPC请求投票。

  • 其他节点收到候选者A的投票请求,在编号为1的这届任期没有进行过投票,那么它将把选票投给A,并增加自己的任期编号。

  • 如果候选人A在选举超时时间内赢得了大多数的选票,那么它就会成为本届任期内新的Leader

  • 节点A当选Leader后,他将周期性地发送心跳消息,通知其他服务器我是领导者,阻止跟随者发起新的选举

日志复制

日志的基本介绍

在 Raft 算法中,副本数据是以日志的形式存在的,领导者接收到来自客户端写请求后,处理写请求的过程就是一个复制和提交日志项的过程。

  • 日志由日志项组成。而日志项主要包含用户指定的数据,也就是指令(Command),还包含一些附加信息,比如索引值(Log index)、任期编号(Term)。

  • 需要注意的是,一届Leader任期内,往往有多条日志项,而且日志项的索引值是连续的。
日志复制的过程
  • 首先,领导者进入第一阶段,通过日志复制(AppendEntries) RPC 消息,将日志项复制到集群其他节点上。接着,如果领导者接收到大多数的”复制成功”响应后,它将日志项提交到它的状态机,并返回成功给客户端。如果领导者没有接收到大多数的”复制成功”响应,那么就返回错误给客户端。

  • 具体而言会分为以下几步:

    1. Leader基于客户端请求的指令,创建一个新的日志项,并附加到本地日志中。
    2. Leader通过日志复制RPC操作,将新的日志项复制到其他服务器。
    3. Leader将日志项,成功复制到大多数服务器上的时候,Leader会将这条日志项提交到它的状态机中。
    4. Leader将执行的结果返回给客户端。
    5. Leader接收到心跳信息,或者新的日志复制RPC消息后,如果Followers发现Leader已经提交了某条日志项,而它还没提交,那么它就将这条日志项提交到本机的状态机中。

成员变更

​ 日常工作中服务器故障的情况偶尔也会出现,这时就要替换故障的服务器。如果遇到需要改变数据副本(服务器)数的情况,则需要增加或移除集群中的服务器,总的来说,在生产环境中集群的服务器数量是会发生变化的。

  • 集群的配置:集群中各节点地址的集合。比如节点A、B、C组成的集群,那么集群的配置就是[A、B、C]集合。
成员变更的带来的问题
  • 在集群中进行成员变更的最大风险是,可能会同时出现 2 个领导者。比如在进行成员变更时,节点 A、B 和 C 之间发生了分区错误,节点 A、B 组成旧配置中的“大多数”,也就是变更前的 3 节点集群中的“大多数”,那么这时的领导者(节点 A)依旧是领导者。 另一方面,节点 C 和新节点 D、E 组成了新配置的“大多数”,也就是变更后的 5 节点集群中的“大多数”,它们可能会选举出新的领导者(比如节点 C)。那么这时,就出现了同时存在 2 个领导者的情况。

解决方案
  • 通过单节点变更解决成员变更出现的极端情况。顾名思义,单节点变更就是通过一次变更一个节点实现成员变更。如果需要变更多个节点,就就行多次单节点变更。

总结

Raft 不是一致性算法而是共识算法,是一个 Multi-Paxos 算法,实现的是如何就一系列值达成共识。并且,Raft 能容忍少数节点的故障。虽然 Raft 算法能实现强一致性,也就是线性一致性(Linearizability),但需要客户端协议的配合。在实际场景中,我们一般需要根据场景特点,在一致性强度和实现复杂度之间进行权衡。比如 Consul 实现了三种一致性模型。

  • default:客户端访问领导者节点执行读操作,领导者确认自己处于稳定状态时(在 leader leasing 时间内),返回本地数据给客户端,否则返回错误给客户端。在这种情况下,客户端是可能读到旧数据的,比如此时发生了网络分区错误,新领导者已经更新过数据,但因为网络故障,旧领导者未更新数据也未退位,仍处于稳定状态。

  • consistent:客户端访问领导者节点执行读操作,领导者在和大多数节点确认自己仍是领导者之后返回本地数据给客户端,否则返回错误给客户端。在这种情况下,客户端读到的都是最新数据。

  • stale:从任意节点读数据,不局限于领导者节点,客户端可能会读到旧数据。

    一般而言,在实际工程中,Consul 的 consistent 就够用了,可以不用线性一致性,只要能保证写操作完成后,每次读都能读到最新值就可以了。比如为了实现冥等操作,我们使用一个编号 (ID) 来唯一标记一个操作,并使用一个状态字段(nil/done)来标记操作是否已经执行,那么只要我们能保证设置了 ID 对应状态值为 done 后,能立即和一直读到最新状态值就可以了,也就通过防止操作的重复执行,实现了冥等性。

​ *总的来说,Raft 算法能很好地处理绝大部分场景的一致性问题,在设计分布式系统时,优先考虑 Raft 算法,当 Raft 算法不能满足现有场景需求时,再去调研其他共识算法。 *


分布式      分布式

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

分布式事务解决方案-Seata 上一篇
Keepalived软件架构 下一篇