Raft 解决的问题
提供一种共识算法(分布式一致性算法)。 Paxos是早先的一个分布式共识算法,Paxos 逻辑复杂而难以理解和实现。相比早先的 Paxos, Raft 提供一个容易理解和实现的共识算法,在很多的系统比如 etcd, ozone,tikv,RethinkDB 等项目中大量使用。
分布式共识协议, 源于我们对系统可用性,容错性的追求。分布式系统中,我们把数据复制到多个节点,为了解决多份数据的一致性问题, 常常将其简化为多份状态机的一致性问题。
对于客户端发来的指令, 只要能确保指令以唯一确定的顺序发送给所有结点, 那么只要各个状态机的初始状态相同, 执行完所有指令后, 也会到达相同的一致状态。 在多状态机一致性问题中, 共识算法( Consensus Algorithm) 的作用就是确保多份 log 中包含顺序和内容完全一致的指令,Raft 算法也即为了达到这个目的。
什么是共识? distributed consensus
共识是多个服务器在某一个值上达成一致。 这个值可以是商品的库存,可以是最大的Id值,可以是代表集群中主节点(Master 节点)的id,可以是账号里的余额数据,可以是任何的需要共识的数据,比如一份合同,钱包属于谁的,等等。
分布式共识的不同算法
Paxos (1990) – 可以工作,但少有人能够理解,难以正确实现
Raft (2014) – “In Search of an Understandable Consensus Algorithm” , Diego Ongaro and John Ousterhout – USENIX ATC’14, https://raft.github.io
Raft 算法的工作过程
多结点数据一致性的问题可以被抽象简化为多状态机副本一致性问题。
共识算法(一致性算法)是从复制状态机的背景下提出的。在这种方法中,一组服务器上的状态机产生相同状态的副本,并且在一些机器宕掉的情况下也可以继续运行。复制状态机在分布式系统中被用于解决很多容错的问题。例如,大规模的系统中通常都有一个集群领导人,像 GFS、HDFS 和 RAMCloud,典型应用就是一个独立的复制状态机去管理领导选举和存储配置信息并且在领导人宕机的情况下也要存活下来。比如 Chubby 和 ZooKeeper。
让整个集群拥有一个主结点, 客户端只向集群中的主结点发送指令, 如果客户端将指令发送给了集群中的非主结点, 非主结点都会直接返回主结点的地址, 让客户端与主结点建立连接,由主结点进行所有指令的接收和分发, 这样非常简单地就可以确保集群中的每个状态机都收到一份相同的指令,每一条指令即是一条日志。如果主节点出现故障,其他节点发起选举选出新的节点。
日志复制结构(图)
Raft 通过选举一个主节点,然后给予他全部的管理复制日志的责任来实现一致性。主节点从客户端接收日志条目(log entries),把日志条目复制到其他服务器上,并告诉其他的服务器什么时候可以安全地将日志条目应用到他们的状态机中。拥有一个主节点大大简化了对复制日志的管理。
Raft 将整个复杂的问题分解为三个相对独立的子问题逐个解决。
1.主结点选举问题
如何在一个主结点宕机后, 重新为集群选举出新的主结点。
2.指令集分发(日志分发)问题
主结点必须能够接收客户端发来的指令,并将指令 log 分发复制给集群中的其他结点, 并强迫其他状态机所持有且执行的指令 log 与自己的指令 log 达成一致。
3.安全性问题
Raft 算法对于安全性的定义有多条始终成立的性质共同构成
Raft 算法中的几个主要概念
概念 | 英文 | 概念定义 |
主节点 | Leader | Raft 集群中的一台主服务器,负责接收、处理、响应所有来自客户端的请求 |
跟随节点 | Follower | 跟随节点自己不会发送任何请求, 只会响应来自 leader 和 candidate 的请求, 不会响应其他 follower 发来的请求, 如果 follower 收到了来自客户端的请求, follower 会将主节点的信息返回客户端, 让客户端去联系 |
候选人节点 | Candidate | candidate 是选举过程中的候选节点角色, 当 leader 失效后, 会有follower 转变为 candidate , 下一个 leader 从candidate 中产生 |
任期 | Term |
在 Raft 协议中,时间被划分成一个个的任期,每个任期始于一次选举。在选举成功后,主节点会管理整个集群直到任期结束。有时候选举会失败,那么这个任期就会没有主节点而结束。任期之间的切换可以在不同的时间不同的服务器上观察到。 一个任期内最多只有一个主节点(也可能没有主节点); 任期 term 初始值为0,经过初次选举后变成1,单调递增; |
日志 | Log | 日志包含了用于状态机的命令,以及 Leader接收到该条目时的任期(任期从1开始) |
日志索引 | Log Index | 每一条日志条目都有一个整数索引值来表明它在日志中的位置 |
已提交日志 | Committed Log | Raft 算法保证所有已提交的日志条目都是持久化的并且最终会被所有可用的状态机执行。在领导人将创建的日志条目复制到大多数的服务器上的时候,日志条目就会被提交,提交之后变成已提交 |
最后应用的日志索引 | lastApplied Index | 已经被应用到状态机的最大的日志条目的索引(初始值为0,单调递增) |
任期 (图)
一个任期中可能有操作,也可能没有操作,一个任期中最多有一个 leader,也可能没有 leader。 图中有4个任期,t1, t2, t3, t4。
日志序列/日志索引(图)
图中日志的索引从1到8,每条日志里面有一个任期,以及一条赋值指令(如 x <- 3)。图中 leader 节点经历了3个任期,1, 2, 3。
几个主要问题是如何解决的?
1.如何选举出主节点
Raft 使用一种心跳机制来触发领导人选举。当服务器程序启动时,他们都是跟随者身份(Follower)。服务器节点只要从领导人或者候选人处接收到有效的 RPCs,就继续保持着跟随者状态。 Leader 节点周期性的向所有跟随者发送心跳包(即不包含日志项内容的附加条目(AppendEntries)的 RPCs)来维持自己的权威。
如果一个跟随者在一段时间里没有接收到任何消息,也就是选举超时,那么他就会认为系统中没有可用的领导人,此时跟随者变成候选人,并且发起选举以选出新的领导人。
开始一次选举的过程:跟随者先要增加自己的当前任期号并且转换到候选人状态。然后他会并行地向集群中的其他服务器节点发送请求投票的 RPCs 来给自己投票。候选人会继续保持着当前状态直到以下三件事情之一发生:
(a) 他自己赢得了这次的选举,
(b) 其他的服务器成为领导人,
(c) 一段时间之后没有任何一个获胜的人。
当一个候选人从整个集群的大多数服务器节点获得了针对同一个任期号的选票,那么他就赢得了这次选举并成为领导人。每一个服务器最多会对一个任期号投出一张选票,按照先来先服务的原则。要求大多数选票的规则确保了最多只会有一个候选人赢得此次选举。
一旦候选人赢得选举,他就立即成为领导人。
然后他会向其他的服务器发送心跳消息来建立自己的权威并且阻止发起新的选举。
在等待投票的时候,候选人可能会从其他的服务器接收到声明它是领导人的附加条目(AppendEntries)RPC。
如果这个领导人的任期号(包含在此次的 RPC中)不小于候选人当前的任期号,那么候选人会承认领导人合法并回到跟随者状态。 如果此次 RPC 中的任期号比自己小,那么候选人就会拒绝这次的 RPC 并且继续保持候选人状态。
第三种可能的结果是候选人既没有赢得选举也没有输:如果有多个跟随者同时成为候选人,那么选票可能会被瓜分以至于没有候选人可以赢得大多数人的支持。当这种情况发生的时候,每一个候选人都会超时,然后通过增加当前任期号来开始一轮新的选举。
Raft 算法使用随机选举超时时间的方法来确保很少会发生选票瓜分的情况,就算发生也能很快的解决。为了阻止选票起初就被瓜分,选举超时时间是从一个固定的区间(例如 150-300 毫秒)随机选择。这样可以把服务器都分散开以至于在大多数情况下只有一个服务器会选举超时;然后他赢得选举并在其他服务器超时之前发送心跳包。同样的机制被用在选票瓜分的情况下。每一个候选人在开始一次选举的时候会重置一个随机的选举超时时间,然后在超时时间内等待投票的结果;这样减少了在新的选举中另外的选票瓜分的可能性。
其他更多逻辑参考如下参考文章。
Raft 的教程,视频课程,以及不同语言中的实现
参考文章
https://ramcloud.atlassian.net/wiki/download/attachments/6586375/raft.pdf
https://www.usenix.org/system/files/hotcloud19-paper-ahn.pdf
Apache Kudu – Introducing Apache Kudu
https://home.apache.org/~szetszwo/presentations/20171115brown_bag.pdf