浅谈 Raft 分布式一致性协议|图解 Raft
前言
大家好,这里是白泽。本文是一年多前参加字节训练营针对 Raft 自我整理的笔记。
本篇文章将模拟一个KV数据读写服务,从提供单一节点读写服务,到结合分布式一致性协议(Raft)后,逐步扩展为一个分布式的,满足一致性读写需求的读写服务的过程。
其中将配合引入Raft协议的种种概念:选主、一致性、共识、安全等,通篇阅读之后,将帮助你深刻理解什么是分布式一致性协议。
一、 单机KV数据读写服务
DB Engine这里可以简单看成对数据的状态进行存储(比如B+树型的组织形式),负责存储KV的内容 ,并假设这个KV服务将提供如下接口:
- Get(key) —> value
- Put([key, value])
思考此时KV服务的可靠性:
- 容错:单个数据存储节点,不具备容错能力。
- 高可用:服务部署在单节点上,节点宕机将无法提供服务。
思考此时KV服务的正确性:
- 单进程,所有操作顺序执行,可以保证已经存储的数据是正确的
数据规模不断增加,我们需要扩大这个KV服务的规模,将其构建成一个分布式的系统
二、一致性与共识算法
2.1 从复制开始
既然一个节点会挂,那么我们就多准备一个节点!
我们这里只考虑主副本负责接收数据,而从副本只负责同步接收主副本数据的模式,如果主从都开放数据接收能力,将要考虑更多高可用和数据一致性的问题。
2.2 如何复制
- 主副本定期拷贝全量数据到从副本(代价太高)
- 主副本拷贝操作日志到从副本:如果你了解MySQL的主从同步,你就会想起它也是通过从副本监听主副本当中的
bin log
操作日志变化,进行集群数据同步的,因此这种方式也是更主流的选择。
2.3 具体的写流程
-
主副本把所有的操作打包成
Log
- 所有的
Log
写入都是持久化的,保存在磁盘上
- 所有的
-
应用包装成状态机(也就是DB Engine部分),只接收
Log
作为Input
-
主副本确认
Log
已经成功写入到从副本机器上,当状态机apply
后,返回客户端(关于写入之后,请求返回客户端的时机,是可以由应用控制的,可以是Log
写入从副本之后,就从主副本机器返回,也可以等Log
完成落盘之后,再返回)
2.4 具体的读流程
- 方案一:直接读状态机(这里指的是DB),要求上一步写操作进入状态机后再返回client(数据已落盘)
- 方案二:写操作复制
Log
完成后直接返回,读操作Block
等待所有pending log
进入状态机
- 如果不遵循上述两种方案:可能存在刚刚写入的值读不到的情况(在Log中)
2.5 什么是一致性
对于我们的KV服务,要像操作一台机器一样,对用户来说在写入成功后,就应该能读到最近写入的值,而不关心具体底层是如何分布式实现。
一致性是一种模型(或语义),约定一个分布式系统如何向外界提供服务,KV服务中常见的一致性模型有以下两种:
- 最终一致性:读取可能暂时读不到但是总会读到
- 线性一致性:最严格,线性时间执行(写完KV确保就能读到),是最理想中的状态
2.6 复制协议-当失效发生
上述用到的添加了一个从副本节点的方式,我们暂且将其称为山寨版分布式一致性协议——复制协议(因为它依赖于主从副本间的复制操作)
那么当主副本失效时,以这个复制协议为基础的KV服务的运行情况如何呢:
- 容错能力:没有容错能力,因为主副本停了,KV服务就停了
- 高可用:或许有,取决于我们发现主副本宕机后多快将从副本切换为主副本(手动切换)
- 正确性:正确,因为操作只从一台机器发起,可以控制所有操作返回前都已经复制到另一台机器了
衍生出的一些的问题:
- 如何保证主副本是真的失效了,切换的过程中如果主副本又开始接收
client
请求,则会出现Log覆盖写的情况 - 如果增加到3个乃至更多的节点,每次PUT数据的请求都等待其他节点操作落盘性能较差
- 能否允许少数节点挂了的情况下,仍然可以保持服务能工作(具备容错能力)
2.7 共识算法
什么是共识:一个值一旦确定,所有人都认同
共识协议不等于一致性:
-
应用层面不同的一致性,都可以用共识算法来实现
- 比如可以故意返回旧的值(共识算法只是一个彼此的约定,只要数据存储与获取符合需求,达成共识即可)
-
简单的复制协议也可以提供线性一致性(虽然不容错)
一般讨论共识协议时提到的一致性,都指线性一致性
- 因为弱一致性往往可以使用相对简单的复制算法实现
三、一致性协议案例:Raft
3.1 Ratf概述
-
2014年发表
-
易于理解作为算法设计的目标
- 使用了RSM、Log、RPC的概念
- 直接使用RPC对算法进行了描述
- Strong Leader-based(所有操作都是Leader发起的)
- 使用了随机的方法减少约束(比如选主时Follower谁先发现Leader失联就发起选主)
3.2 复制状态机(RSM)
-
RSM(replicated state machine)
- Raft中所有的共识机制(consensus)都是直接使用Log作为载体:简单说就是client将Log提供给Raft(也就是上图的Consensus Module),Raft确定这个Log已经Commit之后(写入Log文件),将Log提供给用户的状态机
- 注意此时状态机是一个内存中的存储概念,当然后续每个节点数据还涉及到落盘持久化,这是具体应用层面的问题了,这里讨论的更多是Raft协议的原理概念,使用状态机对此进行抽象
-
Commited Index(这里先有个概念,用于标识Log中哪些数据可以提交给状态机)
- 一旦Raft更新Commited Index,意味着这个Index前所有的Log都可以提交给状态机了
- Commited Index是不持久化的,状态机也是volatile的(断电清空),重启后从第一条Log开始恢复状态机,Raft协议工作过程中每个节点的Commited Index可能是不一致的,但是Ratf可以保证最终所有节点都是一致的
3.3 Raft角色
- Leader是所有操作的发起者,会把Log同步给Follower,确定哪个Log已经Commit了
- 接受Leader的请求,并且在发现Leader消失,则转换为Candidate,向其他Follower申请投票,如果获得票数过半,则晋升为Leader
3.4 Raft日志复制
接下来讲解一下Raft协议中Log是如何在节点间同步的:
- 第一张图s2是当前Leader,紫色的小箭头指向每个节点已经确认commit的Log位置
- 第二张图s2又append了一条新的Log,用K标识
- 第三张图表示,s2将自己收到的K以及自己的Commited Index发送给所有Follower节点。Follower都同步Leader的Log以及Commited Index,并且Follower的状态机检测到自己的Commited Index又向前推进了,说明有新的Log值达成了共识,就会把Commited Index没读取的Log值应用给状态机
- 第四张图,当Follower完成步骤三的操作之后,会告诉Leader自己已经拿到这个Log值了,Leader在收到多数派的确认消息后,就可以确定这个K已经永远的持久化,也就是已经Commit了,因此将Commited Index向后推一位
这里有一个细节就是,虽然Raft协议使节点间的数据最终达成了一致,但是节点Log数据落盘时间是有先后的(因为Commited Index在各节点之间存在差异,所以Log与状态机是有时差的)
3.5 Raft从节点失效
- 图一假设s3失效了很久,Log有很多数据没有同步
- 图二表示此时的Leader是s2,将自己Log中的K和自己的Commited Index复制给s1
- 图三中s2的K完成了commit(流程参考Raft日志复制),因为s1、s2共两个节点已经形成了多数派(此时已经有容错能力了)
- 图四中,s3尝试重新同步数据,在Raft协议中,s3会向s2逆向迭代的去获取Log数据(K、QK、TQK、XTQK),直到与s3当前Log相对齐则完成数据同步(当然Raft具体实现中应用对此过程进行了优化)
3.6 Raft Term
Term(周期的概念),在Raft中相当于一个逻辑的时钟,下面介绍:
- 每个Leader服务于一个term:一旦发现更高的term,那么表示我已经不是Leader了
- 每个term至多只有一个leader:选出新的Leader,会使得term增加
- 每个节点存储当前的term
- 每个节点term从一开始,只增不减
- 所有的rpc的request response 都携带term
- 只commit本term内的log
3.7 Raft主节点失效
-
Leader定期发送AppendEntries RPCs 给其余所有的节点
-
如果Follower 有一段时间没有收到Leader的AppendEntries,则转换身份为Candidate
-
Candidate自增自己的term,同时使用RequestVote PRCs 向剩余节点请求投票
- raft在检查是否可以投票时,会检查log是否outdated,至少不比本身旧才会投票给对应的Candidate
-
如果多数派投给它,则成为该term的leader
3.8 Raft Leader failure
接下来模拟一下主节点失效后的流程,并且注意此时图一当中,s1的Log内容比较特殊,为XW,这个场景是可以实现的,比如一开始有s1、s2、s3,其中s1是Leader,首先将X同步给了s2和s3,并且接受到了新的W,但是突然s1节点卡死了,s2和s3重新开始选举Leader为s2,最终s2和s3的Log变成了XTQ(这只是一种情况,一切皆有可能),然后s1又恢复了。
关于为什么s3落后s2两条Commited Index,有可能是s2一次同步了两条Log给s3,而s3的状态机还没来得及同步数据,但是s3接收到在标识TQ的Log后,将其commit到自己的Log之中,就返回确认响应给了s2,s2将自己的Commited Index向后推进到Q的位置。
- 从图二开始,s3发现s2挂了,没有Leader,则将自己的term+1变为3,并且向s1请求Vote
- 图三中,s1发现RequestVote是来自term等于3的,大于自己的term,因此将自己的term也更新为3,同时答应给s3投票,达成多数派条件,s3成为新的Leader
- 图四,s3开始同步自己的Log给s1,但是发现s1中的Log起初为XW,而s3中为XTQY,因此还是会按照从后往前迭代的方式将数据同步给s1(Y、QY、TQY、XTQY),最后s1虽然与同步状态发生了冲突,但是由于Raft是
Strong Leader-based
的,会完全按照Leader的内容覆盖自己的Log
3.9 Raft 安全性 - 同 Term
-
对于Term内的安全性
- 目标:对于所有已经commited的<term, index>位置上至多只有一条log
-
由于Raft的多数派选举,我们可以保证在一个term中只有一个leader
- 我们可以证明一条更严格的声明:在任何<term, index>位置上,至多只有一条log
3.10 Raft 安全性 - 跨 Term
-
对于跨Term的安全性
- 目标:如果一个log被标记commited,那这个log一定会在未来所有的leader中出现
-
可以证明这个property:
- Raft选举时会检查Log是否outdated,只有最新的才能当选Leader
- 选举需要多数派投票,而commited log也已经在多数派当中(必有overlap)
- 新Leader一定持有commited log,且Leader永远不会overwrite log
四、回到KV
4.1 重新打造KV服务
结合Raft算法,更新一下我们的KV
回顾一下一致性读写的定义:
-
方案一:
- 写log被commit了,返回客户端成功
- 读操作也写入一条log(因为读的数据可能还没从Log写入状态机,无法读取状态机数据),状态机apply log后返回client
- 增加Log量
-
方案二:
- 写log被commit了,返回客户端成功
- 读操作先等待所有commited log apply,再读取状态机
- 优化写时延
-
方案三:
- 写log被状态机apply,返回给client
- 读操作直接读状态机
- 优化读时延
多个副本只有单个副本可以提供服务(一个Leader万一忙不过来怎么办)
-
服务无法水平扩展
-
增加更多Raft组(不多展开)
- 如果操作跨Raft组(对key进行分组,每一个Raft负责读写一个range的key)
4.2 回到共识算法
-
Raft:关于log
- 论文中就给出的方案,当过多的Log产生后,启动snapshot,替换掉Log
- 如果对于持久化的状态机,如何快速的产生Snapshot(略)
- 多组Raft的应用中,Log如何合流(略)
-
关于configuration change(节点规模变化)
- 论文中就给出的joint-consensus以及单一节点变更两种方案(很复杂,略)
4.3 共识算法的未来
-
Raft Paxos相互移植
-
Raft有很多成熟的实现
-
研究主要关注在Paxos上
-
如何关联两种算法:
- On the Parallels between Paxos and Raft, and how to Port Optimizations[1]
- Paxos vs Raft: Have we reached consensus on distributed consensus?[2]
-
感兴趣可以关注公众号 「白泽talk」,白泽目前也打算打造一个氛围良好的行业交流群,文章的更新也会提前预告,欢迎加入:622383022。
相关文章:

浅谈 Raft 分布式一致性协议|图解 Raft
前言 大家好,这里是白泽。本文是一年多前参加字节训练营针对 Raft 自我整理的笔记。 本篇文章将模拟一个KV数据读写服务,从提供单一节点读写服务,到结合分布式一致性协议(Raft)后,逐步扩展为一个分布式的…...

4_【Linux版】重装数据库问题处理记录
1、卸载已安装的oracle数据库。 2、知识点补充: 3、调整/dev/shm/的大小 【linux下修改/dev/shm tmpfs文件系统大小 - saratearing - 博客园 (cnblogs.com)】 mount -o remount,size100g /dev/shm 4、重装oracle后没有orainstRoot.sh 【重装oracle后没有orains…...

隧道应用2-netsh端口转发监听Meterpreter
流程介绍: 跳板机 A 和目标靶机 B 是可以互相访问到的,在服务器 A 上可以通过配置 netsh 端口映射访问 B 服务器。如果要拿 B 服务器的权限通常是生成正向后门,使用 kali 的 msf 正向连接B服务器,进而得到 Meterpreter,…...

《Spring》--使用application.yml特性提供多环境开发解决方案/开发/测试/线上--方案1
阿丹有话说: 有不少同志有疑问说我正常开发的时候,需要自己搭建项目的时候。总是出现配置文件环境切换出现问题。多环境系列会出两个文章解决给搭建重点解决一下这个问题。给与两种解决的方案。正确让大家只需要按照步骤操作就可以完成。 原理…...
统计项目5000+,出具报表5分钟......捷顺科技数据中台怎么做?
捷顺创立于1992年,以智慧车行、人行出入口软硬件产品为依托,致力于智慧停车生态建设和运营,是出入口智能管理和智慧生态环境建设的开创者和引领者。 历经近三十年的发展,已经成为国内智慧停车领域的领军企业。公司集研、产、销一…...

力扣(105. 从前序与中序遍历序列构造二叉树,106. 从中序与后序遍历序列构造二叉树)
题目1链接 题目1: 思路:使用前序确定根,使用中序分左右子树,分治法。 难点:如何控制递归确定左右子树。 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* T…...

网络安全技术新手入门:在docker上安装dvwa靶场
前言 准备工作:1.已经安装好kali linux 步骤总览:1.安装好docker 2.拖取镜像,安装dvwa 一、安装docker 输入命令:sudo su 输入命令:curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key …...

Docker 介绍 及 支持的操作系统
Docker组成: Docker主机(Host): 一个物理机或虚拟机, 用于运行Docker服务进程和容器, 也成为宿主机, node节点。 Docker服务器端(Server): Docker守护进程, 运行Docker容器。 Docker客户端(Client): 客户端使用docker命令或其他工…...

大模型实战营Day5 LMDeploy大模型量化部署实践
模型部署 定义 产品形态 计算设备 大模型特点 内存开销大 动态shape 结构简单 部署挑战 设备存储 推理速度 服务质量 部署方案:技术点 (模型并行 transformer计算和访存优化 低比特量化 Continuous Batch Page Attention)方案(…...

py连接sqlserver数据库报错问题处理。20009
报错 pymssql模块连接sqlserver出现如下错误: pymssql._pymssql.OperationalError) (20009, bDB-Lib error message 20009, severity 9:\nUnable to connect: Adaptive Server is unavailable or does not exist (passwordlocalhost)\n) 解决办法: 打…...

LTESniffer:一款功能强大的LTE上下行链路安全监控工具
关于LTESniffer LTESniffer是一款功能强大的LTE上下行链路安全监控工具,该工具是一款针对LTE的安全开源工具。 该工具首先可以解码物理下行控制信道(PDCCH)并获取所有活动用户的下行链路控制信息(DCI)和无线网络临时…...

SQL语句详解二-DDL(数据定义语言)
文章目录 操作数据库创建:Create查询:Retrieve修改:Update删除:Delete使用数据库 操作表常见的几种数据类型创建:Create复制表 查询:Retrieve修改:Update删除:Delete 操作数据库 创…...

web前端算法简介之链表
链表 链表 VS 数组链表类型链表基本操作 创建链表:插入操作:删除操作:查找操作:显示/打印链表:反转链表:合并两个有序链表:链表基本操作示例 JavaScript中,instanceof环形链表 判断…...
C++函数对象
任何定义了函数调用操作符的对象都是函数对象。C 支持创建、操作新的函数对象,同时也提供了许多内置的函数对象。 函数包装器 std::function 提供存储任意类型函数对象的支持。 function (C11) 包装具有指定函数调用签名的任意类型的可调用对象 (类模板) bad_funct…...

插件化简单介绍
关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。 未经允许不得转载 目录 一、导读二、概览三、常见的插件化方案…...

[Beego]1.Beego简介以及beego环境搭建,bee脚手架的使用,创建,运行项目
一.Beego介绍 Beego是一个开源的基于Golang的MVC框架,主要用于Golang Web开发,Beego可以用来快速开发API、Web、后端服务等各种应用。 Golang 的Web开发框架有很多,从 github star 数量来看Gin>Beego>lris>Echo>Revel>Buffalo 目前国内用的比较多的就…...
Tomcat 静态资源访问与项目根路径设置(AI问答)
一个静态文件,放在Tomcat中,希望能够通过网络访问,应该放在哪里? 在Apache Tomcat中,如果想要部署静态文件(例如HTML、CSS、JavaScript、图片等)并能通过网络访问,通常需要将这些文…...

Docker实战09|使用AUFS包装busybox
前几篇文章中,重点讲解了如何实现构建容器,需要回顾的小伙伴可以看以下文章: 《Docker实战06|深入剖析Docker Run命令》《Docker实战07|Docker增加容器资源限制》《Docker实战08|Docker管道及环境变量识别…...
什么是uni.request()?如何使用它?
uni.request()是uni-app提供的一个用于发起网络请求的API。 使用uni.request()的步骤如下: 在需要发起网络请求的页面中引入uni.request()方法。 调用uni.request()方法,并传入相应的参数,包括请求地址、请求方法、请求头部和请求数据等。 …...

用React给XXL-JOB开发一个新皮肤(一):环境搭建和项目初始化
目录 一. 简述二. Fork 项目三. 搭建开发环境四. 初始化皮肤项目五. 添加相关依赖六. 预览 一. 简述 大名鼎鼎的 xxl-job 任务调度中心我们应该都使用过,项目地址:xxl-job。它是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...

现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...

算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...