raft浅析

前言

单机系统存在什么问题呢?

  • 容错能力差,单机如果故障,那么相关数据、服务均无保证,恢复困难
  • 性能受限,单机提供的吞吐量无法水平扩展,而垂直扩展存在瓶颈且代价巨大

因此,考虑将单机系统转换为集群系统,发挥团结的力量来提高容错、性能,达到高可用、高并发的要求。具体应用场景,比如新闻网站(新浪、qq…),购物网站(淘宝、京东…),12306等。

那么,问题来了,多机之间如何正确、高效的协同呢?如果没有一个协议能保证多个节点的数据一致性(先不考虑最终一致性、强一致性等),那么集群就没有了意义。
举个例子,试想你妈让你帮她买火车票,她在家里查看余票信息是有的,然后让你给她买票。结果你登录之后,发现并没有余票,但是你妈一直说有余票。分布式情况下,如果你和你妈的查询请求被转发到不同机器,而这两个机器的信息没有保持一致,上述场景是很有可能出现的。

为此,Lamport发表了著名的Paxos协议。Paxos解决了集群一致性的问题,并且理论证明了正确性。但是,存在协议复杂、难以工程化等缺点。后人也发表了相当多的解释Paxos的论文,也有一些系统声称是基于Paxos实现的(GFS、HDFS、Spanner…?)。有人这么讲,因为Paxos算法和实践的巨大鸿沟,最终实现的系统将基于未证明的协议。
总而言之,虽然Paxos理论上解决了多节点的共识问题,但工程上有巨大的改进空间。

为了解决难以理解+难于实现两大难题,诞生了Raft算法。


一、解决之道

1、分解

面对复杂的大问题,将其拆解为若干稍简单的稍小问题,从而逐个击破是一个好思路。Raft就将整个协议分解成了三大块:

  • leader选举
  • log复制
  • 安全性

2、降低状态空间大小

减少不确定性的程度,减少服务器间不一致程度。
比如,log不允许有洞(即不连续)

3、平衡

在有多种解决方案的情况下,倾向于选择易理解的方案,这可能在某些场景下牺牲性能。

与已有算法的区别

  • 强leader,只能主节点(leader)更新从节点log,从节点不能更新主节点
  • leader选举,随机定时器来选举leader
  • 成员更新

优点

  • 简单、易理解;
  • 易于工程化实现;
  • 存在多个开源实现并被多个公司使用;
  • 安全性被形式化证明;
  • 足够高效。

复制状态机(SMR)

复制状态机通常用复制log实现,当然可以用复制状态实现。复制log,要求每个节点的log以相同的顺序包括相同的命令。状态机是确定性的,那所有节点只要执行log,既可以获得相同状态。
共识算法的作用就是保持节点log的一致性。
共识算法的基本要求:

  • safety(安全性),任何非拜占庭情况(网络延迟、分区、丢包、重复发、乱序等)下,不会有错误发生
  • 可用性(活性?),只要大部分节点正常,服务就可用
  • 一致性不依赖时机,错误的时钟、极端的消息延迟最多带来可用性问题,其实还是安全性
  • 整体性能取决于大部分节点,少部分低性能节点不影响整体响应速度,还是可用性

基本概念

节点状态

  • leader(主节点),任一任期leader数不大于1,处理所有命令并负责更新所有从节点
  • follower(从节点),接收正确leader的命令,投票
  • candidate(候选节点),尝试成为新leader

term 任期
全部时间被分割为严格递增的任期,每个新任期都需要一个选举阶段,一个生效阶段。与生活中的选举不同,Raft的选举阶段其实是无政府的混乱状态,现实中的老政府还在工作。
每个任期最多一个leader,可能由于选票被均摊而无法选出leader。因为不同于美国的2选1,Raft是N选1,而且规定必须获得半数以上选票才能当选,不存在联合执政。
另外一个点,Raft的leader是终身制。
任期类似逻辑时钟,标记信息是否落后。
在这里插入图片描述

leader选举

  1. 启动即进入从节点角色
  2. 等待leader的心跳,如果收到并合法,不变;否则等待超时并进入候选状态
  3. 进入候选状态,给自己投票,并向其它节点发送拉票请求
  4. 其它节点收到拉票请求后,合法则投票
  5. 候选节点拉到半数选票后,进入leader状态,并向其它节点周期性广播心跳
  6. 其它候选节点收到新leader广播后,合法则进入从状态
  7. 如果任期超时后,还没有选出新leader,则进入下一任期选举

状态转换图
candidate合法的条件

  • 任期不落后本节点任期,其实这里要求大于也可以,因为等于的情况肯定不满足后面条件
    相等意味着两个节点都是候选节点?
  • 本节点在新任期未投票,
  • 候选节点log不落后于本地

log复制

leader负责将所有新log同步给所有从节点。如果当前term的log被大部分节点复制,则leader可以提交,并将消息更新给从节点,保证提交的log最终会被所有工作节点提交。
大致流程,leader维护一个数组nextIndex[],记录每次同步log的时候,每个从节点log的起始高度,初始为leader log最高高度。
理论上,只要match的高度与本地log最高高度不一致,leader就发送send[i]到本地最新高度的log。
如果[nextIndex[I], highest] 返回ok,那么nextIndex[I] = highest + 1;如果返回失败,则send[I]–,leader下次发送高度更低的log。

安全性(safety not security)

底线&红线:log只要在任意节点commit,就必须在所有节点正常之后commit!

选举限制

从节点收到拉票请求后,处理校验任期外,还必须校验log新鲜度?
新鲜度定义:

  • 最新log term更大的,更新鲜(term是逻辑时钟!!)
  • 最新log term相同的,log越长越新鲜

合法规则: 候选节点最新log term大于本地,或者term相同且log长度不短于本地,即本地不比候选节点更新鲜。当然,这个规则也可以变化!!

commit log的条件

commit的条件不是大部分节点都已经存储。关键原因是新鲜度的定义,如果新leader的term更高,即使log在大部分节点存储了,它还是可以覆盖的,只有他自身任期内的log,他自己不会覆盖。
反例:
假定3个节点,A,B,C,用<索引序列化-term>对代表一个log,比如0-0第一个0表示第一个log,第二0表示任期0。

  1. A是leader,log 0-0,term = 0
  2. A crash, B成为leader,log 0-1,term = 1
  3. B crash, A成为leader,C也有log 0-0了,此时,0-0已经在2个节点存储,term = 2
  4. A crash,B成为leader,因为term = 3,符合新鲜度规则,B的0-1将会覆盖A,C中0-0

当然,也可以改新鲜度规则,让B无法覆盖0-0,这个可以留作思考^^。

因此commit的条件是

  • log在大部分节点存储
  • log的term与当前leader的term相等

安全性证明

时机

整个协议保证,除非有拜占庭节点,否则任何情况下都满足安全性要求。
但是,系统的可用性依赖于时机。

broadcastTime ≪ electionTimeout ≪ MTBF

猜你喜欢

转载自blog.csdn.net/wenyuanhust/article/details/123545883