热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

一致性_Raft一致性协议

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Raft一致性协议相关的知识,希望对你有一定的参考价值。分布式存储系统通常通过维护多个副本来进行fault-tolera

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Raft一致性协议相关的知识,希望对你有一定的参考价值。


分布式存储系统通常通过维护多个副本来进行fault-tolerance,提高系统的availability,带来的代价就是分布式存储系统的核心问题之一:维护多个副本的一致性。一致性协议就是用来干这事的,即使在部分副本宕机的情况下。Raft是一种较容易理解的一致性协议。一致性协议通常基于replicated state machines,即所有结点都从同一个state出发,都经过同样的一些操作序列,最后到达同样的state。

为了便于理解,Raft大概将整个过程分为三个阶段,leader election,log replication和commit(safety)。

每个server处于三个状态:leader,follower,candidate。正常情况下,所有server中只有一个是leader,其它的都是follower。server之间通过RPC消息通信。follower不会主动发起RPC消息。leader和candidate(选主的时候)会主动发起RPC消息。

Leader election

时间被分为很多连续的随机长度的term(一段时间),一个term由一个唯一的id标识。每个term一开始就进行leader election:

1. followers将自己维护的current_term_id加1。

2. 然后将自己的状态转成candidate。

3. 发送RequestVoteRPC消息(带上current_term_id) 给 其它所有server

这个过程会有三种结果:

1. 自己被选成了主。当收到了majority的投票后,状态切成leader,并且定期给其它的所有server发心跳消息(其实是不带log的AppendEntriesRPC)以告诉对方自己是current_term_id所标识的term的leader。每个term最多只有一个leader,term id作为logical clock,在每个RPC消息中都会带上,用于检测过期的消息,比如自己是一个过期的leader(term id更小的leader)。当一个server收到的RPC消息中的rpc_term_id比本地的current_term_id更大时,就更新current_term_id为rpc_term_id,并且如果当前state为leader或者candidate时,将自己的状态切成follower。如果rpc_term_id比本地的current_term_id更小,则拒绝这个RPC消息。

2. 别人成为了主。如1所述,当candidate在等待投票的过程中,收到了大于或者等于本地的current_term_id的声明对方是leader的AppendEntriesRPC时,则将自己的state切成follower,并且更新本地的current_term_id。

3. 没有选出主。当投票被瓜分,没有任何一个candidate收到了majority的vote时,没有leader被选出。这种情况下,每个candidate等待的投票的过程就超时了,接着candidates都会将本地的current_term_id再加1,发起RequestVoteRPC进行新一轮的leader election。

投票策略:

每个server只会给每个term投一票,具体的是否同意和后续的Safety有关。


当投票被瓜分后,所有的candidate同时超时,然后有可能进入新一轮的票数被瓜分,为了避免这个问题,Raft采用一种很简单的方法:每个candidate的election timeout从150ms-300ms之间随机取,那么第一个超时的candidate就可以发起新一轮的leader election,带着最大的term_id给其它所有server发送RequestVoteRPC消息,从而自己成为leader,然后给他们发送心跳消息以告诉他们自己是主。

Log Replication

当leader被选出来后,leader就可以接受客户端发来的请求了,每个请求包含一条需要被replicated state machines执行的命令。leader会把它作为一个log entry,append到它的日志中,然后给其它的server发AppendEntriesRPC。当leader确定一个log entry被safely replicated了,就apply这条log entry到状态机中然后返回结果给客户端。如果某个follower宕机了或者运行的很慢,或者网络丢包了,则会一直给这个follower发AppendEntriesRPC直到日志一致。

当一条日志是commited时,leader才能决定将它apply到状态机中。Raft保证一条commited的log entry已经持久化了并且会被所有的server执行。

当一个新的leader选出来的时候,它的日志和其它的follower的日志可能不一样,这个时候,就需要一个机制来保证日志是一致的。如下图所示,一个新leader产生时,集群状态可能如下:


最上面这个是新leader,a~f是follower,每个格子代表一条log entry,格子内的数字代表这个log entry是在哪个term上产生的。

新leader产生后,log就以leader上的log为准。其它的follower要么少了数据比如b,要么多了数据,比如f,要么既少了又多了数据,比如d。

需要有一种机制来让leader和follower对log达成一致,leader会为每个follower维护一个nextIndex,表示leader给各个follower发送的下一条log entry在log中的index,初始化为leader

的最后一条log entry的下一个位置。leader给follower发送AppendEntriesRPC消息,带着(term_id, (nextIndex-1)), term_id即(nextIndex-1)这个槽位的log entry的term_id,follower接收到AppendEntriesRPC后,会从自己的log中找是不是存在这样的log entry,如果不存在,就给leader回复拒绝消息,然后leader则将nextIndex减1,再重复,知道AppendEntriesRPC消息被接收。

以leader和b为例:

初始化,nextIndex为11,leader给b发送AppendEntriesRPC(6,10),b在自己log的10号槽位中没有找到term_id为6的log entry。则给leader回应一个拒绝消息。接着,leader将nextIndex减一,变成10,然后给b发送AppendEntriesRPC(6, 9),b在自己log的9号槽位中同样没有找到term_id为6的log entry。循环下去,直到leader发送了AppendEntriesRPC(4,4),b在自己log的槽位4中找到了term_id为4的log entry。接收了消息。随后,leader就可以从槽位5开始给b推送日志了。

Safety

1.哪些follower有资格成为leader?

Raft保证被选为新leader的server拥有所有的已经committed的log entry,这与ViewStamped Replication不同,后者不需要这个保证,而是通过其他机制从follower拉取自己没有的commited的log entry。

这个保证是在RequestVoteRPC阶段做的,candidate在发送RequestVoteRPC时,会带上自己的最后一条log entry的term_id和index,server在接收到RequestVoteRPC消息时,如果发现自己的日志比RPC中的更新,就拒绝投票。日志比较的原则是,如果本地的最后一条log entry的term id更大,则更新,如果term id一样大,则日志更多的更大(index更大)。

2. 哪些log entry被认为是commited?

Raft一致性协议

两种情况:

1. leader正在replicate当前term即term2的log entry给其它follower,一旦leader确认了这条log entry被majority写盘了,这条log entry就被认为是committed。如图a,S1作为当前term即term2的leader,log index为2的日志被majority写盘了,这条log entry被认为是commited

2. leader正在replicate更早的term的log entry给其它follower。图b的状态是这么出来的:

S1作为term2的leader,给S1和S2 replicate完log index=2的日志后crash,当前状态为:

S1 1 2 宕机

S2 1 2

S3 1

S4 1

S5 1

S5被选为term3的leader(由于S5的最后一条log entry比S3,S4的最后一条log entry更新或一样新,接收到S3,S4,S5的投票),自己产生了一条term3的日志,没有给任何人复制,就crash了,当前状态如下:

S1 1 2

S2 1 2

S3 1

S4 1

S5 1 3 宕机

接着S1重启后,又被选为term4的leader(接收到S1,S2,S3的投票,文中没有指出S4?),然后S1给S3复制了log index为2的log entry,当前状态如下:

S1 1 2

S2 1 2

S3 1 2

S4 1

S5 1 3 宕机

这个时候S5重启,被选为了term5的主(接收了S2,S3,S4,S5的投票),那么S5会把log index为2的日志3复制给其它server,那么日志2就被overwrite了。

所以虽然这里日志2被majority的server写盘了,但是并不代表它是commited的。


对commit加一个限制:主的当前term的至少一条log entry被majority写盘

如:c图中,就是主的当前term 4的一条log entry被majority写盘了,假设这个时候S1宕机了,S5是不可能变成主的。因为S2和S3的log entry的term为4,比S5的3大。


关于算法的正确性证明见:Raft implementations

Log Compaction

在实际的系统中,不能让日志无限增长,否则系统重启时需要花很长的时间进行回放,从而影响availability。Raft采用对整个系统进行snapshot来处理,snapshot之前的日志都可以丢弃。

snapshot技术在Chubby和ZooKeeper系统中都有采用。


每个server独立的对自己的系统状态进行snapshot,并且只能对已经committed log entry(已经apply到了状态机)进行snapshot,snapshot有一些元数据,包括last_included_index,即snapshot覆盖的最后一条commited log entry的 log index,和last_included_term,即这条日志的termid。这两个值在snapshot之后的第一条log entry的AppendEntriesRPC的consistency check的时候会被用上,之前讲过。一旦这个server做完了snapshot,就可以把这条记录的最后一条log index及其之前的所有的log entry都删掉。

snapshot的缺点就是不是增量的,即使内存中某个值没有变,下次做snapshot的时候同样会被dump到磁盘。

当leader需要发给某个follower的log entry被丢弃了(因为leader做了snapshot),leader会将snapshot发给落后太多的follower。或者当新加进一台机器时,也会发送snapshot给它。

发送snapshot使用新的RPC,InstalledSnapshot。

做snapshot有一些需要注意的性能点,1. 不要做太频繁,否则消耗磁盘带宽。 2. 不要做的太不频繁,否则一旦server重启需要回放大量日志,影响availability。系统推荐当日志达到某个固定的大小做一次snapshot。3. 做一次snapshot可能耗时过长,会影响正常log entry的replicate。这个可以通过使用copy-on-write的技术来避免snapshot过程影响正常log entry的replicate。


Cluster membership changes

Raft将有server加入集群或者从集群中删除也纳入一致性协议中考虑,避免由于下线老集群上线新集群而引起的不可用。集群的成员列表重配置也是一条log entry,log内容包含了集群成员列表。

老集群配置用Cold表示,新集群配置用Cnew表示。

当集群成员配置改变时,leader收到人工发出的重配置命令从Cold切成Cnew,leader 给其它server复制一条特殊的log entry给其它的server,内容包括Cold∪Cnew,一旦server收到了这条特殊的配置log entry,其后的log entry会被replicate到Cold∪Cnew中,一条log entry被认为是committed的需要满足这条日志既被Cold的majority写盘,也被Cnew的majority写盘。一旦Cold∪Cnew这条log entry被确认为committed,leader就会产生一条只包含了Cnew的log entry,同样复制给所有server,server收到log后,老集群的server就可以自动下线了。

Performance


横坐标代表没有leader的ms数,每条线代表election timeout的随机取值区间。

上图说明只要给个5ms的区间,就能避免反复的投票被瓜分。超过10s没有leader的情况都是因为投票被瓜分的情况。

150-150ms的election timeout区间,没有主的时间平均287ms。

系统推荐使用150ms~300ms


Implementation

由于Go语言内置RPC,Channel,goroutine等高级编程组件,实现一个相对于其他语言还是容易些,这里有一个Go的实现 Raft

参考资料:

In Search of an Understandable Consensus Algorithm



推荐阅读
  • CEPH LIO iSCSI Gateway及其使用参考文档
    本文介绍了CEPH LIO iSCSI Gateway以及使用该网关的参考文档,包括Ceph Block Device、CEPH ISCSI GATEWAY、USING AN ISCSI GATEWAY等。同时提供了多个参考链接,详细介绍了CEPH LIO iSCSI Gateway的配置和使用方法。 ... [详细]
  • 在springmvc框架中,前台ajax调用方法,对图片批量下载,如何弹出提示保存位置选框?Controller方法 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 阿里Treebased Deep Match(TDM) 学习笔记及技术发展回顾
    本文介绍了阿里Treebased Deep Match(TDM)的学习笔记,同时回顾了工业界技术发展的几代演进。从基于统计的启发式规则方法到基于内积模型的向量检索方法,再到引入复杂深度学习模型的下一代匹配技术。文章详细解释了基于统计的启发式规则方法和基于内积模型的向量检索方法的原理和应用,并介绍了TDM的背景和优势。最后,文章提到了向量距离和基于向量聚类的索引结构对于加速匹配效率的作用。本文对于理解TDM的学习过程和了解匹配技术的发展具有重要意义。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了在rhel5.5操作系统下搭建网关+LAMP+postfix+dhcp的步骤和配置方法。通过配置dhcp自动分配ip、实现外网访问公司网站、内网收发邮件、内网上网以及SNAT转换等功能。详细介绍了安装dhcp和配置相关文件的步骤,并提供了相关的命令和配置示例。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • Inno Setup区段之Components篇相关知识详解
    本文详细介绍了Inno Setup区段之Components篇相关的知识,包括Components和Types的使用方式以及各个参数的说明,希望对读者有一定的参考价值。内容涵盖了ComponentsName、Description、Types、ExtraDiskSpaceRequired、ExtraDiskSpaceRequiredFlags等多个关键词,帮助读者更好地理解和应用Inno Setup区段之Components篇的知识。 ... [详细]
  • Python开源库和第三方包的常用框架及库
    本文介绍了Python开源库和第三方包中常用的框架和库,包括Django、CubicWeb等。同时还整理了GitHub中最受欢迎的15个Python开源框架,涵盖了事件I/O、OLAP、Web开发、高性能网络通信、测试和爬虫等领域。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
author-avatar
Jump_jiedB0_666
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有