MIT6.824
  • 简介
  • Lecture 01 - Introduction
    • 1.1 分布式系统的驱动力和挑战(Drivens and Challenges)
    • 1.2 课程结构(Course Structure)
    • 1.3 分布式系统的抽象和实现工具(Abstraction and Implementation)
    • 1.4 可扩展性(Scalability)
    • 1.5 可用性(Availability)
    • 1.6 一致性(Consistency)
    • 1.7 MapReduce基本工作方式
    • 1.8 Map函数和Reduce函数
  • Lecture 03 - GFS
    • 3.1分布式存储系统的难点(Why Hard)
    • 3.2 错误的设计(Bad Design)
    • 3.3 GFS的设计目标
    • 3.4 GFS Master 节点
    • 3.5 GFS读文件(Read File)
    • 3.6 GFS写文件(Write File)(1)
    • 3.7 GFS写文件(Write File)(2)
    • 3.8 GFS的一致性
  • Lecture 04 - VMware FT
    • 4.1 复制(Replication)
    • 4.2 状态转移和复制状态机(State Transfer and Replicated State Machine)
    • 4.3 VMware FT 工作原理
    • 4.4 非确定性事件(Non-Deterministic Events)
    • 4.5 输出控制(Output Rule)
    • 4.6 重复输出(Duplicated Output)
    • 4.7 Test-and-Set 服务
  • Lecture 06 - Raft1
    • 6.1 脑裂(Split Brain)
    • 6.2 过半票决(Majority Vote)
    • 6.3 Raft 初探
    • 6.4 Log 同步时序
    • 6.5 日志(Raft Log)
    • 6.6 应用层接口
    • 6.7 Leader选举(Leader Election)
    • 6.8 选举定时器(Election Timer)
    • 6.9 可能的异常情况
  • Lecture 07 - Raft2
    • 7.1 日志恢复(Log Backup)
    • 7.2 选举约束(Election Restriction)
    • 7.3 快速恢复(Fast Backup)
    • 7.4 持久化(Persistence)
    • 7.5 日志快照(Log Snapshot)
    • 7.6 线性一致(Linearizability)
  • Lecture 08 - Zookeeper
    • 8.1 线性一致(Linearizability)(1)
    • 8.2 线性一致(Linearizability)(2)
    • 8.3 线性一致(Linearizability)(3)
    • 8.4 Zookeeper
    • 8.5 一致保证(Consistency Guarantees)
    • 8.6 同步操作(sync)
    • 8.7 就绪文件(Ready file/znode)
  • Lecture 09 - More Replication, CRAQ
    • 9.1 Zookeeper API
    • 9.2 使用Zookeeper实现计数器
    • 9.3 使用Zookeeper实现非扩展锁
    • 9.4 使用Zookeeper实现可扩展锁
    • 9.5 链复制(Chain Replication)
    • 9.6 链复制的故障恢复(Fail Recover)
    • 9.7 链复制的配置管理器(Configuration Manager)
  • Lecture 10 - Cloud Replicated DB, Aurora
    • 10.1 Aurora 背景历史
    • 10.2 故障可恢复事务(Crash Recoverable Transaction)
    • 10.3 关系型数据库(Amazon RDS)
    • 10.4 Aurora 初探
    • 10.5 Aurora存储服务器的容错目标(Fault-Tolerant Goals)
    • 10.6 Quorum 复制机制(Quorum Replication)
    • 10.7 Aurora读写存储服务器
    • 10.8 数据分片(Protection Group)
    • 10.9 只读数据库(Read-only Database)
  • Lecture 11 - Cache Consistency: Frangipani
    • 11.1 Frangipani 初探
    • 11.2 Frangipani的挑战(Challenges)
    • 11.3 Frangipani的锁服务(Lock Server)
    • 11.4 缓存一致性(Cache Coherence)
    • 11.5 原子性(Atomicity)
    • 11.6 Frangipani Log
    • 11.7 故障恢复(Crash Recovery)
    • 11.8 Frangipani总结
  • Lecture 12 - Distributed Transaction
    • 12.1 分布式事务初探(Distributed Transaction)
    • 12.2 并发控制(Concurrency Control)
    • 12.3 两阶段提交(Two-Phase Commit)
    • 12.4 故障恢复(Crash Recovery)
    • 12.5 总结
由 GitBook 提供支持
在本页

这有帮助吗?

  1. Lecture 06 - Raft1

6.7 Leader选举(Leader Election)

上一页6.6 应用层接口下一页6.8 选举定时器(Election Timer)

最后更新于4年前

这有帮助吗?

这一部分我们来看一下Leader选举。这里有个问题,为什么Raft系统会有个Leader,为什么我们需要一个Leader?

答案是,你可以不用Leader就构建一个类似的系统。实际上有可能不引入任何指定的Leader,通过一组服务器来共同认可Log的顺序,进而构建一个一致系统。实际上,Raft论文中引用的Paxos系统就没有Leader,所以这是有可能的。

有很多原因导致了Raft系统有一个Leader,其中一个最主要的是:通常情况下,如果服务器不出现故障,有一个Leader的存在,会使得整个系统更加高效。因为有了一个大家都知道的指定的Leader,对于一个请求,你可以只通过一轮消息就获得过半服务器的认可。对于一个无Leader的系统,通常需要一轮消息来确认一个临时的Leader,之后第二轮消息才能确认请求。所以,使用一个Leader可以提升系统性能至2倍。同时,有一个Leader可以更好的理解Raft系统是如何工作的。

Raft生命周期中可能会有不同的Leader,它使用任期号(term number)来区分不同的Leader。Followers(非Leader副本节点)不需要知道Leader的ID,它们只需要知道当前的任期号。每一个任期最多有一个Leader,这是一个很关键的特性。对于每个任期来说,或许没有Leader,或许有一个Leader,但是不可能有两个Leader出现在同一个任期中。每个任期必然最多只有一个Leader。

那Leader是如何创建出来的呢?每个Raft节点都有一个选举定时器(Election Timer),如果在这个定时器时间耗尽之前,当前节点没有收到任何当前Leader的消息,这个节点会认为Leader已经下线,并开始一次选举。所以我们这里有了这个选举定时器,当它的时间耗尽时,当前节点会开始一次选举。

开始一次选举的意思是,当前服务器会增加任期号(term number),因为它想成为一个新的Leader。而你知道的,一个任期内不能有超过一个Leader,所以为了成为一个新的Leader,这里需要开启一个新的任期。 之后,当前服务器会发出请求投票(RequestVote)RPC,这个消息会发给所有的Raft节点。其实只需要发送到N-1个节点,因为Raft规定了,Leader的候选人总是会在选举时投票给自己。

这里需要注意的一点是,并不是说如果Leader没有故障,就不会有选举。但是如果Leader的确出现了故障,那么一定会有新的选举。这个选举的前提是其他服务器还在运行,因为选举需要其他服务器的选举定时器超时了才会触发。另一方面,如果Leader没有故障,我们仍然有可能会有一次新的选举。比如,如果网络很慢,丢了几个心跳,或者其他原因,这时,尽管Leader还在健康运行,我们可能会有某个选举定时器超时了,进而开启一次新的选举。在考虑正确性的时候,我们需要记住这点。所以这意味着,如果有一场新的选举,有可能之前的Leader仍然在运行,并认为自己还是Leader。例如,当出现网络分区时,旧Leader始终在一个小的分区中运行,而较大的分区会进行新的选举,最终成功选出一个新的Leader。这一切,旧的Leader完全不知道。所以我们也需要关心,在不知道有新的选举时,旧的Leader会有什么样的行为?

(注:下面这一段实际在Lec 06的65-67分钟出现,与这一篇前后的内容在时间上不连续,但是因为内容相关就放到这里来了)

假设网线故障了,旧的Leader在一个网络分区中,这个网络分区中有一些客户端和少数(未过半)的服务器。在网络的另一个分区中,有着过半的服务器,这些服务器选出了一个新的Leader。旧的Leader会怎样,或者说为什么旧的Leader不会执行错误的操作?这里看起来有两个潜在的问题。第一个问题是,如果一个Leader在一个网络分区中,并且这个网络分区没有过半的服务器。那么下次客户端发送请求时,这个在少数分区的Leader,它会发出AppendEntries消息。但是因为它在少数分区,即使包括它自己,它也凑不齐过半服务器,所以它永远不会commit这个客户端请求,它永远不会执行这个请求,它也永远不会响应客户端,并告诉客户端它已经执行了这个请求。所以,如果一个旧的Leader在一个不同的网络分区中,客户端或许会发送一个请求给这个旧的Leader,但是客户端永远也不能从这个Leader获得响应。所以没有客户端会认为这个旧的Leader执行了任何操作。另一个更奇怪的问题是,有可能Leader在向一部分Followers发完AppendEntries消息之后就故障了,所以这个Leader还没决定commit这个请求。这是一个非常有趣的问题,我将会再花45分钟(下一节课)来讲。

学生提问:有没有可能出现极端的情况,导致单向的网络出现故障,进而使得Raft系统不能工作?

Robert教授:我认为是有可能的。例如,如果当前Leader的网络单边出现故障,Leader可以发出心跳,但是又不能收到任何客户端请求。它发出的心跳被送达了,因为它的出方向网络是正常的,那么它的心跳会抑制其他服务器开始一次新的选举。但是它的入方向网络是故障的,这会阻止它接收或者执行任何客户端请求。这个场景是Raft并没有考虑的众多极端的网络故障场景之一。

我认为这个问题是可修复的。我们可以通过一个双向的心跳来解决这里的问题。在这个双向的心跳中,Leader发出心跳,但是这时Followers需要以某种形式响应这个心跳。如果Leader一段时间没有收到自己发出心跳的响应,Leader会决定卸任,这样我认为可以解决这个特定的问题和一些其他的问题。

你是对的,网络中可能发生非常奇怪的事情,而Raft协议没有考虑到这些场景。

所以,我们这里有Leader选举,我们需要确保每个任期最多只有一个Leader。Raft是如何做到这一点的呢?

为了能够当选,Raft要求一个候选人从过半服务器中获得认可投票。每个Raft节点,只会在一个任期内投出一个认可选票。这意味着,在任意一个任期内,每一个节点只会对一个候选人投一次票。这样,就不可能有两个候选人同时获得过半的选票,因为每个节点只会投票一次。所以这里是过半原则导致了最多只能有一个胜出的候选人,这样我们在每个任期会有最多一个选举出的候选人。

同时,也是非常重要的一点,过半原则意味着,即使一些节点已经故障了,你仍然可以赢得选举。如果少数服务器故障了或者出现了网络问题,我们仍然可以选举出Leader。如果超过一半的节点故障了,不可用了,或者在另一个网络分区,那么系统会不断地额尝试选举Leader,并永远也不能选出一个Leader,因为没有过半的服务器在运行。

如果一次选举成功了,整个集群的节点是如何知道的呢?当一个服务器赢得了一次选举,这个服务器会收到过半的认可投票,这个服务器会直接知道自己是新的Leader,因为它收到了过半的投票。但是其他的服务器并不能直接知道谁赢得了选举,其他服务器甚至都不知道是否有人赢得了选举。这时,(赢得了选举的)候选人,会通过心跳通知其他服务器。Raft论文的图2规定了,如果你赢得了选举,你需要立刻发送一条AppendEntries消息给其他所有的服务器。这条代表心跳的AppendEntries并不会直接说:我赢得了选举,我就是任期23的Leader。这里的表达会更隐晦一些。Raft规定,除非是当前任期的Leader,没人可以发出AppendEntries消息。所以假设我是一个服务器,我发现对于任期19有一次选举,过了一会我收到了一条AppendEntries消息,这个消息的任期号就是19。那么这条消息告诉我,我不知道的某个节点赢得了任期19的选举。所以,其他服务器通过接收特定任期号的AppendEntries来知道,选举成功了。