Paxos 存在的问题

Paxos 算法的描述偏学术化,缺失了很多细节,无法直接应用于工程领域。实际工程应用中的分布式算法大多是 Paxos 的变种,验证这些算法的正确性也成为了一个难题。

举个例子:上一篇文章的 最后 介绍了一个应用 Paxos 算法的工程模型,这个模型存在明显的写性能瓶颈:

  • 使用多主架构,写入冲突的概率高
  • 每次更新操作都需要至少 2 轮以上的网络通信,通信开销大

如果要提高该模型的性能,仍需要在很多细节上做进一步调整,最终实现出来的算法已经和原始的版本的 Paxos 相去甚远。

为了解决以上问题,另一个高性能且易于理解的一致性算法横空出世:Raft

基本概念

Raft 算法基于 复制状态机Replicated State Machine模型,本质上就是一个管理 日志复制 的算法。

Raft 集群采用 Single Leader 架构,集群中有唯一的 Leader 进程负责管理日志复制,其职责有:

  • 接受 Client 发送的请求
  • 将日志记录同步到其他进程
  • 告知其他进程的何时能够提交日志

复制状态机

复制状态机的本质就是:Paxos + WAL

每个进程维护一个状态机State Machine,并且使用一个日志存储其所要执行指令。
如果两个状态机执行按照相同的顺序,执行相同的指令,则这两个进程最终能够收敛到同个状态。如果能保证所有进程的日志一致,则每个进程的状态必定也是一致的。

任期

为了减少不必要的网络通信,日志追加顺序由集群唯一的 Leader 决定,无须与其他节点协商。通信开销从最低 2 次降为固定的 1 次,从而大幅提高了算法的性能。

出于可用性考虑,当前 Leader 下线后,集群需要从存活的节点中挑选一个新的 Leader,这个过程被称为选举 election

每次选举都会产生一个新的任期号 term(单调递增),如果选举中产生了一个新的 Leader,那么这个任期号会伴随这个 Leader 直到其下线。

每个 参与者 进程都会维护一个 current_term 用于表示已知的最新任期,进程之间通过彼此交换该值来感知 Leader 变化。

/**
* 基础信息
*/
public abstract class RaftMember implements RaftParticipant { // 响应 RPC 前需要持久化以下两个属性
protected final long currentTerm; // 已知的最新的 term(初始为 0)
protected final ID lastCandidate; // 最近一次赞成投票的 candidate protected RaftMember(long term, ID candidate) {
this.currentTerm = term;
this.lastCandidate = candidate;
stableStorage().persist(currentTerm, lastCandidate);
} /**
* @see RaftParticipant#currentTerm()
*/
@Override
public long currentTerm() {
return currentTerm;
} /**
* @see RaftParticipant#votedFor()
*/
@Override
public ID votedFor() {
return lastCandidate;
} }

日志

日志是 Raft 的核心概念。Raft 保证日志是连续且一致的,并且最终能够被所有进程按照日志索引的顺序提交。

每条日志记录包含:

  • 任期term:生成该条记录的 Leader 对应的任期
  • 索引index:其在日志中的顺序
  • 命令command:可执行的状态机指令

一旦某条日志中的命令被状态机执行了,那么我们称这条记录为已提交committed,Raft 保证已提交的记录不会丢失。

角色

Raft集群中每个进程只能担任其中一个角色:

  • Leader:发送心跳、管理日志复制与提交
  • Follower:被动响应其他节点发送过来的请求
  • Candidate:主动发起并参与选举

Raft 进程间使用 RPC 的方式进行通信,实现最基础的共识算法只需 两种RPC

  • RequestVote:用于选举产生 Leader
  • AppendEntries:复制日志与发送心跳
/**
* RPC 接口
* */
public interface RaftService { /**
* 复制日志+发送心跳(由 leader 调用)
* @param term leader 任期
* @param leaderId leader 在集群中的唯一标识
* @param prevLogIndex 紧接在新的之前的日志条目索引
* @param prevLogTerm prevLogIndex 对应的任期
* @param entries 日志条目(发送心跳时为空)
* @param leaderCommit leader 已经提交的日志条目索引
* @return 当 follower 中的日志包含 prevLogIndex 与 prevLogTerm 匹配的日志条目返回 true
* */
Async<RaftResponse> appendEntries(
long term, ID leaderId,
long prevLogIndex, long prevLogTerm,
Entry[] entries, long leaderCommit) throws Exception; /**
* 选主(由 candidate 调用)
* @param term candidate 任期
* @param candidateId candidate 在集群中的唯一标识
* @param lastLogIndex candidate 最后一条日志条目的索引
* @param lastLogTerm candidate 最后一条日志条目的任期
* @return 当收到赞成票时返回 true
* */
Async<RaftResponse> requestVote(
long term, ID candidateId,
long lastLogIndex, long lastLogTerm) throws Exception; }

算法流程

基于 Single Leader 模型,Raft 将一致性问题分解为 3 个独立的子问题:

  • Leader 选举Election:Leader 进程失效后能够自动选举出一个新的 Leader
  • 日志复制Replication:Leader 保证其他节点的日志与其保持一致
  • 状态安全 Safety:Leader 保证状态机执行指令的顺序与内容完全一致

为了方便理解,下面结合 动画 进行介绍。

选举

使用 心跳超时heartbeat timeout机制来触发 Leader 选举:

  • 节点启动时默认处于 Follower 状态,如果 Follower 超时未收到 Leader 心跳信息,会转换为 Candidate 并向其他节点发起 RequestVote 请求。
  • 当 Candidate 收到半数以上的选票之后成为 Leader,开始定时向其他节点发起 AppendEntries 请求以维持其 Leader 的地位。
  • Leader 失效之后停止发送心跳,Follower 的心跳超时机制又会被触发,开始新一轮的选举。

复制

未提交日志
已提交日志

集群中只有 Leader 对外提供服务:

客户端与 Leader 进行通信时,每个请求包含一条可以被状态机执行的命令。

当 Leader 在接收到命令之后,首先会将命令转换为一条对应的 日志记录log entry,并追加到本地的日志中。然后调用 AppendEntries 将这条日志复制到其他节点的日志中。

当日志被复制到过半数节点上时,Leader 会将这条日志中包含的命令 提交commit状态机执行,最后将执行结果告知客户端。

网络分区

使用 过半数majority机制来处理脑裂:

发生网络分区后,集群中可能同时出现多个 Leader,复制机制保证了最多只有一个 Leader 能够正常对外提供服务。

如果日志无法复制到多数节点,Leader 会拒绝提交这些日志,当网络分区消失后,集群会自动恢复到一致的状态。

安全性保证

选举时…

保证新的 Leader 拥有所有已经提交的日志

  • 每个 Follower 节点在投票时会检查 Candidate 的日志索引,并拒绝为日志不完整的 Candidate 投赞成票
  • 半数以上的 Follower 节点都投了赞成票,意味着 Candidate 中包含了所有可能已经被提交的日志

提交日志时…

Leader 只主动提交自己任期内产生的日志

  • 如果记录是当前 Leader 所创建的,那么当这条记录被复制到大多数节点上时,Leader 就可以提交这条记录以及之前的记录
  • 如果记录是之前 Leader 所创建的,则只有当前 Leader 创建的记录被提交后,才能提交这些由之前 Leader 创建的日志

总结

一致性算法的本质:一致性与可用性之间的权衡

Raft 的优点:Single Leader 的架构简化日志管理

所有日志都由 Leader 流向其他节点,无需与其他节点进行协商。其他节点只需要记录并应用 Leader 发送过来日志内容即可,将原来的两阶段请求优化为一次 RPC 调用。

Raft 的缺点:对日志的连续性有较高要求

为了简化日志管理,Raft 的日志不允许存在空隙,限制了并发性。某些应用场景下,需要通过Multi-Raft的模式对无关的业务进行解耦,从而提高系统的并发度。

限于篇幅限制,本文只展示了 Raft 算法大致的轮廓。

此前在学习算法的过程中,使用 Java 实现了一个简化版的 Raft 协议:rafting

代码忠实于论文原文,包含了其中的众多算法细节,希望对各位学习 Raft 的朋友有所帮助。

Raft 协议的更多相关文章

  1. Raft协议实战之Redis Sentinel的选举Leader源码解析

    这可能是我看过的写的最详细的关于redis 选举的文章了, 原文链接 Raft协议是用来解决分布式系统一致性问题的协议,在很长一段时间,Paxos被认为是解决分布式系统一致性的代名词.但是Paxos难 ...

  2. MIT-6.824 Raft协议

    摘要 raft是一种比paxos容易理解的一致性算法,实现起来比paxos简单许多.本文前部分描述算法的细节,后部分尝试探讨下该算法的原理. 算法描述 raft算法之所以简单的原因之一是它将问题分解成 ...

  3. Raft协议学习笔记

    目录 目录 1 1. 前言 1 2. 名词 1 3. 什么是分布式一致性? 3 4. Raft选举 3 4.1. 什么是Leader选举? 3 4.2. 选举的实现 4 4.3. Term和Lease ...

  4. [搜狐科技]由浅入深理解Raft协议

    由浅入深理解Raft协议 2017-10-16 12:12操作系统/设计 0 - Raft协议和Paxos的因缘 读过Raft论文<In Search of an Understandable ...

  5. Paxos、ZAB、RAFT协议

    这三个都是分布式一致性协议,ZAB基于Paxos修改后用于ZOOKEEPER协议,RAFT协议出现在ZAB协议之后,与ZAB差不多,也有很大区别. 1. Paxos 分布式节点分为3种角色, Prop ...

  6. Paxos算法与Zookeeper分析,zab (zk)raft协议(etcd) 8. 与Galera及MySQL Group replication的比较

    mit 分布式论文集 https://github.com/feixiao/Distributed-Systems wiki上描述的几种都明白了就出师了 raft 和 zab 是类似的,都是1.先选举 ...

  7. RocketMQ 多副本前置篇:初探raft协议

    目录 1.Leader选举 1.1 一轮投票中,只有一个节点发起投票的情况 1.2 一轮投票中,超过一个节点发起投票的情况 1.3 思考如何实现Raft选主 2.日志复制 Raft协议是分布式领域解决 ...

  8. 基于 raft 协议的 RocketMQ DLedger 多副本日志复制设计原理

    目录 1.RocketMQ DLedger 多副本日志复制流程图 1.1 RocketMQ DLedger 日志转发(append) 请求流程图 1.2 RocketMQ DLedger 日志仲裁流程 ...

  9. raft协议-分布式环境下的数据一致性问题

    阅读了一个有意思的ppt,是Standford大学发表的raft协议 网址:http://thesecretlivesofdata.com/raft/ 下面自己总结下咯: 1.raft是一个实现了解决 ...

  10. DLedger —基于 raft 协议的 commitlog 存储库

    “点击获取上云帮助文档” 尊敬的阿里云用户: 您好!为方便您试用开源 RocketMQ 客户端访问阿里云MQ,我们申请了专门的优惠券,优惠券可以直接抵扣金额.请填写下您公司账号信息,点击上图,了解更多 ...

随机推荐

  1. PyCharm 中文教程 01:运行 Python 的四种方式

    <PyCharm 中文指南>在线阅读: http://pycharm.iswbm.com/ Github 项目主页: https://github.com/iswbm/pych... 1. ...

  2. 【转】对初学LoadRunner朋友的建议

    对初学LoadRunner朋友的建议 作者:wind摘要:随着Internet的普及与迅速发展,企业业务量的迅速加大,数据大集中成为一种趋势,IT系统承载的负荷越来越重,系统性能的好坏严重的影响了企业 ...

  3. okhttp3 示例

    1.GET请求 private fun httpGetDemo() { //1.请求参数 val url = httpHost + "/api/test?arg1=xxx" //2 ...

  4. App性能测试前需要了解的内存原理

    这两天在研究性能中内存方面的一块,网上也零散看了挺多文章,写得很细但是感觉不够整体,所以这篇算是总结一下吧,当个复习资料. 那么这里个人分为两个大部分,第一部分应用内的内存管理,主要是oom的理解,G ...

  5. Ignatius and the Princess IV (水题)

    "OK, you are not too bad, em... But you can never pass the next test." feng5166 says.  &qu ...

  6. Codeforces 1337C Linova and Kingdom

    题意 给你一颗有根树,你要选择\(k\)个点,最大化\(\sum_{i \in S} val_i\),其中\(S\)是被选点的集合,\(val_i\)等于节点\(i\)到根的路径上未被选择点的个数. ...

  7. HDOJ 1003

    动态规划一直AC不了竟然是因为一厢情愿的多加了一个#! printf("Case #%d:\n",count); --------------------------------- ...

  8. Java获取Linux上指定文件夹下所有第一级子文件夹

    说明:需要只获得第一级文件夹目录 package com.sunsheen.jfids.studio.monitor.utils; import java.io.BufferedReader; imp ...

  9. 预处器的对比——Sass、LESS和Stylus

    http://www.w3cplus.com/css/sass-vs-less-vs-stylus-a-preprocessor-shootout.html

  10. PHP生成二维码 endroid/qr-code 扩展包

    目录 1. 引入 endroid/qr-code 扩展包 2. 使用示例 3. 二维码中放入logo,无法识别二维码的问题 1. 引入 endroid/qr-code 扩展包 https://pack ...