2021-06-01

二、Raft算法

 

在paxos算法笔记中说过,Multi-Paxos 算法是一个统称,它是指基于 Multi-Paxos 思想,通过多个 Basic Paxos 实例实现一系列值的共识的算法。Raft算法就是其中的一种。

 

Raft 算法是现在分布式系统开发首选的共识算法。全新的系统大多选择了 Raft 算法(比如 Etcd、Consul、CockroachDB——《分布式数据库一》的4.1.3节却说由于节点数较多,CockroachDB采用了Gossip协议)。

 

从本质上说,Raft 算法是通过一切以领导者为准的方式,实现一系列值的共识和各节点日志的一致。这句话比较抽象,我来做个比喻,领导者就是 Raft 算法中的霸道总裁,通过霸道的“一切以我为准”的方式,决定了日志中命令的值,也实现了各节点日志的一致。

 

Raft算法本质上处理的是主从结构,相当于每个节点上只有一个副本,节点和副本必须一一对应。这样的结果,就是数据不再有分片。

 

1、选主过程

 

集群中的节点有三种可以互相转换的状态:领导者(Leader)、跟随者(Follower)和候选人(Candidate)。其中候选人状态只有集群处于选举状态时才会有这个状态,一旦选举完成,就只有领导者和跟随者两种状态的节点了。

 

和政治选举不同,Raft算法中尽量减少多个节点同时进行入候选人状态。因为每个节点的超时时间是随机的,超时时间短的节点首先进入候选人状态,先投自己一票,然后发送RPC消息,让其他节点也投自己一票,其他节点不会进行辩论,直接就投票了。不论是谁投票,投票的时候,都会把任期编号加1。当选领导者后,会向其他节点发送心跳,阻止有人政变。

 

如果多个节点同时发起选举导致无人获得大多数投票,那么选举的超时时间过了以后,就会重新选举。

 

 

 

 

 

 

1.1、领导者节点的任期

任期代表了Raft世界中的朝代,每选举一次,就会更新一次朝代。各个节点也比较讲道理:

  • 跟随者主动更新自己的编号到收到的RPC消息中携带的更大的任期编号。
  • 如果领导者发现自己的任期编号比其他的小(可能网络分区后又恢复了),就会立即变为跟随者状态。
  • 大家一起抵制任期标号比自己的任期标号小的请求。(上面三步避免了paxos算法中确定提案编号的准备阶段)
  • 投票的时候,谁先发起投票就选谁。不会吵架。
  • 只要领导者的心跳消息在,就不会有人政变。
  • 大家终归是全心全意为数据服务的,所以日志完整性高的跟随者(也就是最后一条日志项对应的任期编号值更大,索引号更大),拒绝投票给日志完整性低的候选人。
  • 选举是为数据服务的过程,没啥不好意思的,所以发起投票是先给自己投一票。
  • 追随者收到领导者的数据更新指令后,会老老实实的执行。

 

如果节点不像上面说的都是“老实人”,那么就不能构成Raft算法了,需要防止有人作恶,就需要PBFT等算法,参见第五章。

 

2、日志的一致性如何保证

 

所谓日志就是raft集群中的数据,或者说副本数据是以日志的形式存在的,日志是由日志项组成。

 

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

 

 

日志复制的过程可以理解成一个优化后的二阶段提交(将二阶段优化成了一阶段),减少了一半的往返消息,也就是降低了一半的消息延迟。这里采用的数据复制技术是半同步复制技术,因为领导者收到大多数节点的回复就可以往下进行了。

 

  • 领导者节点收到客户的请求后,进入第一阶段,通过日志复制(AppendEntries)RPC 消息,将日志项复制到集群其他节点上。

 

  • 如果领导者接收到大多数的“复制成功”响应后,它将日志项应用到它的状态机,并返回成功给客户端。如果领导者没有接收到大多数的“复制成功”响应,那么就返回错误给客户端。这里与完整的二阶段提交不同,领导者收到响应后,不会再进入第二阶段——通知所有人提交或回滚事务,而是自己直接提交然后应答客户端。

 

那跟随者何时提交或回滚呢?领导者的日志复制 RPC 消息或心跳消息,包含了当前最大的,将会被提交(Commit)的日志项索引值。所以通过日志复制 RPC 消息或心跳消息,跟随者就可以知道领导者的日志提交位置信息。

 

 

还有另外一个类似的描述:

 

  1. Leader 收到客户端的请求。
  2. Leader 将请求内容(即 Log Entry)追加(Append)到本地的 Log。
  3. Leader 将 Log Entry 发送给其他的 Follower。
  4. Leader 等待 Follower 的结果,如果大多数节点提交了这个 Log,那么这个 Log Entry 就是 Committed Entry,Leader 就可以将它应用(Apply)到本地的状态机。
  5. Leader 返回客户端提交成功。
  6. Leader 继续处理下一次请求。

 

上面描述的是单个事务的运行情况,多事务并行操作情况如下:

 

 

设定这个 Raft 组由 5 个节点组成,T1 到 T5 是先后发生的 5 个事务操作,被发送到这个 Raft 组。

 

  • 事务 T1 的操作是将 X 置为 1,5 个节点都 Append 成功,Leader 节点 Apply 到本地状态机,并返回客户端提交成功。
  • 事务 T2 执行时,虽然有一个 Follower 没有响应,但仍然得到了大多数节点的成功响应,所以也返回客户端提交成功。
  • 轮到 T3 事务执行,没有得到超过半数的响应,这时 Leader 必须等待一个明确的失败信号,比如通讯超时,才能结束这次操作。因为有顺序投票的规则,T3 会阻塞后续事务的进行。
  • T4 事务被阻塞是合理的,因为它和 T3 操作的是同一个数据项,但是 T5 要操作的数据项与 T3 无关,也被阻塞,显然这不是最优的并发控制策略。
  • 同样的情况也会发生在 Follower 节点上,第一个 Follower 节点可能由于网络原因没有收到 T2 事务的日志,即使它先收到 T3 的日志,也不会执行 Append 操作,因为这样会使日志出现空洞。

 

Raft 的顺序投票是一种设计上的权衡,虽然性能有些影响,但是节点间日志比对(见下)会非常简单。在两个节点上,只要找到一条日志是一致的,那么在这条日志之前的所有日志就都是一致的。这使得选举出的 Leader Follower 同步数据非常便捷,开放 Follower 读操作也更加容易。

 

除了正常情况,还需要保证异常情况下数据还是一致的,在节点进程崩溃或重启以后,需要以领导者为准,更新自己的日志。

 

  • 首先,领导者通过日志复制 RPC 的一致性检查,找到跟随者节点上,与自己相同日志项的最大索引值。也就是说,这个索引值之前的日志,领导者和跟随者是一致的,之后的日志是不一致的了。
  • 然后,领导者强制跟随者更新覆盖的不一致日志项,实现日志的一致。

 

跟随者中的不一致日志项会被领导者的日志覆盖,而且领导者从来不会覆盖或者删除自己的日志。

 

分布式关系数据库TiDB的元数据复制策略中,也是在心跳消息中包含了这里所说的RAFT集群中的“日志”(其实就是数据)消息的:

 

在任何一个分布式存储系统中,收到客户端请求后,承担路由功能的节点首先要访问分片元数据(简称元数据),确定分片对应的节点,然后才能访问真正的数据。这里说的元数据,一般会包括分片的数据范围、数据量、读写流量和分片副本处于哪些物理节点,以及副本状态等信息。

 

从存储的角度看,元数据也是数据,但特别之处在于每一个请求都要访问它,所以元数据的存储很容易成为整个系统的性能瓶颈和高可靠性的短板。如果系统支持动态分片,那么分片要自动地分拆、合并,还会在节点间来回移动。这样,元数据就处在不断变化中,又带来了多副本一致性(Consensus)的问题。

 

对于动态分片,通常是不会在有工作负载的节点上存放元数据的。那要怎么设计呢?有一个凭直觉就能想到的答案,那就是专门给元数据搞一个小规模的集群,用 Paxos 协议复制数据。

 

TiDB 大致就是这个思路:

 

在 TiDB 架构中,TiKV 节点是实际存储分片数据的节点,而元数据则由 Placement Driver 节点管理。Placement Driver 这个名称来自 Spanner 中对应节点角色,简称为 PD。

 

在 PD 与 TiKV 的通讯过程中,PD 完全是被动的一方。TiKV 节点定期主动向 PD 报送心跳,分片的元数据信息也就随着心跳一起报送,而 PD 会将分片调度指令放在心跳的返回信息中。等到 TiKV 下次报送心跳时,PD 就能了解到调度的执行情况。

 

3、RAFT集群高可用与水平扩展

 

高可用要解决两个问题:节点故障时可以隔离,需要满足不同的压力时可以扩缩容。

 

集群节点数量改变时,最初实现成员变更的是联合共识(Joint Consensus),但这个方法实现起来难,后来 Raft 的作者就提出了一种改进后的方法,单节点变更(single-server changes)。

 

单节点变更,就是通过一次变更一个节点实现成员变更。如果需要变更多个节点,那你需要执行多次单节点变更。比如将 3 节点集群扩容为 5 节点集群,这时你需要执行 2 次单节点变更,先将 3 节点集群变更为 4 节点集群,然后再将 4 节点集群变更为 5 节点集群。

 

4、一致性级别

 

Raft 算法能实现强一致性,也就是线性一致性(Linearizability),但需要客户端协议的配合。在实际场景中,我们一般需要根据场景特点,在一致性强度和实现复杂度之间进行权衡。比如 Consul 实现了三种一致性模型。

 

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

 

4.1、一致性的种类

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值