草庐IT

分布式系统共识机制:一致性算法设计思想

Jinze_L 2023-12-01 原文

分布式系统共识机制:一致性算法设计思想


这次以一个宏观的角度去总结 自己学习过的一致性算法。一致性算法的目标就是让分布式系统里的大部分节点 保持数据一致。
区块链中的共识算法,pow、pos这类就属于这个范围,但他们仅仅是在区块链领域内应用的,下面介绍一致性算法是在分布式系统中 应用广泛的,当然也肯定适用于区块链,并且最后我总结了他们的设计思想,其实是有一定套路的。

Paxos 算法

首先是paxos算法,他是在大量工程实践中得到检验的,google很多项目和大数据组件zookeeper中都用它。他是实现是很复杂的,但是基本思想不难理解。

节点角色

paxos的节点有三种角色,分别是:
第一类是提案者,负责提出议案,也就是要同步的数据。
第二类 批准者,提案者提出的提案 必须获得超过半数的 批准者的投票后才能通过。
第三类 学习者。不参与提案,只负责接收已确定的提案,一般用于提高集群对外提供 读 服务的能力。

算法流程

算法具体流程主要有三个阶段:
首先是 Prepare阶段,提案者选择一个提案编号N,向批准者们发送 这个编号的Prepare请求。批准者收到请求后,判断N是不是大于 本地已经响应的Prepare请求的编号,是的话就将这个编号反馈给提案者,同时不会再批准 编号小于N的提案。
然后是 Accept阶段,如果提案者收到 超过半数批准者的响应,就发送一个针对[N,val]提案的Accept请求 给批准者,val是提案值。然后批准者接受对应值并返回给提案者一个响应。
到这时候,批准者和提案者都达成了共识。
最后就进入Learn阶段,这个阶段不属于选定提案的过程,提案者将通过的提案 同步到所有的学习者。
最终大部分节点就会达成共识。

很明显,这个算法不适用于存在拜占庭节点的分布式系统,只能适用于普通的分布式系统。

Raft 算法

虽然Paxos的原理比较容易理解,但是它在工程上的实现是非常复杂的,所以出现了Raft算法,是Paxos算法的一种简化实现。基本思路也差不多。

节点角色


同样的,节点有三种角色:
首先是 follower追随者,是集群的初始状态,节点在加入时默认是追随者,也就是从节点。
然后是 candidate候选人,是在选举的时候,被投票者的称谓。这是一个中间角色,比如followerA投票给followerB,那followerB的角色就是candidate。
最后是 leader主节点,用来接收用户请求,进行数据同步。

核心机制

RAFT算法分为两个阶段:Leader选举日志复制
首先看leader是如何选出来的。

leader选举

算法刚开始时,所有结点都是Follower,每个结点都会有一个定时器,在收到Leader 的消息时就会重置定时器。如果定时器超时,说明一段时间内没有收到 Leader 的消息,那么就认为 Leader不存在了,那么该结点就会转变成 Candidate,准备竞争Leader。
成为 Candidate 后,节点会向其他结点发送 请求投票的请求,其他结点在收到请求后 会判断是否投给他并返回结果。Candidate如果收到了半数以上的投票 就可以成为Leader,成为之后会在任期内 定期发送心跳消息 通知其他结点 新的 Leader 信息,用来重置其他节点的定时器,避免其他结点成为 Candidate。

日志复制


leader选举出来后,就要开始同步数据。
由leader收到客户端的请求,会将请求包装成日志包的形式发到其它节点,这个过程叫做日志复制。其它节点接收到数据后向主节点响应ACK。
leader等待集群中超过一半的节点响应后, 再向客户端回复数据已接收。此时进入 数据已提交的状态。
最后 Leader 节点再向其它节点 告知数据状态已提交,其它节点开始commit自己的数据,此时集群达到主节点和从节点的一致。

raft算法和刚才介绍的paxos算法,都假设不存在拜占庭将军问题,只考虑节点宕机、网络分区、消息不可靠等问题。下面这类算法就考虑到节点作恶的情况。最经典实用的是pbft,之前也多次介绍过这个算法的实现细节,但这次以另一种的角度去解读

PBFT


首先,也是分为不同店节点角色,分别是:
主节点,负责将交易打包成区块,每轮共识过程只能有一个主节点。
副本节点,负责共识投票,每轮共识过程中有多个副本节点。

流程:
客户端发送请求给主节点
主节点广播请求给其它节点,节点执行PBFT算法的三阶段共识流程。
三阶段流程后,返回消息给客户端。
客户端收到来自 f+1 个节点的相同消息后,代表共识已经正确完成。

基于投票的共识算法 其实基本就是一个套路:确定一个leader提出提案,其他节点负责投票。根据投票结果来确定提案是否通过。当主节点提出提案后,其他节点的 投票和收集投票 都由各个节点单独完成的,这是因为有拜占庭节点的存在,节点只相信自己获取到的投票信息,每一个节点 基于自己收集的消息来确定 该提案是否形成了共识。
所以就设计了四个阶段:
首先pre-prepare阶段 就是主节点向副本节点发送提案,副本节点接收提案并进行验证,但并不知道其它节点的状态。
在prepare阶段,每个合法的节点都接收到至少2/3的投票数,我们将一个节点接收到至少2/3的投票数 称为事件A,显然至少2/3节点都发生了事件A,但是节点之间 不知道彼此是否发生了事件A;
所以就有commit阶段,每个节点都将 自己发生了事件A的消息 广播给其他节点,同时也收集其他节点 关于事件A的广播,我们把收集到至少2/3个节点的事件A 称作事件B。此时,每个节点都知道 至少有2/3节点都发生了事件A,那么大部分节点达成了共识。但是,客户端还不知道结果呢。
最后,在reply阶段中,每个节点都将事件B返回给客户端,此时客户端只要收集到至少f+1个节点的 事件B的广播,就可以判断系统已经形成共识。我们将收集到至少f+1个节点的事件B的广播 称作事件C
所以,pbft设置了四个阶段就是为了保证这三个事件的发生

Hotstuff

根据刚才的介绍,PBFT中的关键点在于 每个节点都独立做 收集投票的工作,这就导致了整个算法中 节点的工作量是重复的。而PBFT之所以这么做的原因是 节点只相信自己获取到的投票信息,如果能解决这个信任问题,那么就省去这些重复工作。HotStuff所进行的优化也就是基于此,通过使用门限签名,确保投票结果不能被伪造。

门限签名

节点角色和PBFT的角色一样,分为主节点和副本节点。
门限签名 简单介绍下:一个(k,n)门限签名方案指 由n个成员组成的签名群体,所有成员共同拥有一个公共密钥,每个成员拥有各自的私钥。只要收集到k个成员的签名,且生成一个完整的签名,这个签名可以通过公钥进行验证。
这里不去详细探讨门限签名的技术细节,主要聚焦在算法是怎样应用的。在HotStuff中,leader除了提出提案以外,还需要收集其他节点的投票,并将投票结果整合成一个 容易检查合法性但又无法伪造的证据。门限签名的特点就是,当且仅当 对同一个数据具备了足够多的子签名,才能合成一个签名,而其他人只需要验证最终的签名 就能确保整个签名构建过程是合法的。门限签名的使用,使得所有节点都可以将 收集投票信息的工作委派给leader,并可以确保leader无法作假。因此,HotStuff最终的算法复杂度是直接降低了一个量级。

核心机制


HotStuff主要是将**“leader负责收集每一轮的投票信息”**思想融合到pbft中。pbft中出现了2轮所有节点广播以及收集投票+1轮客户端收集投票。如果换成leader收集投票,需要3轮来保证这三个事件的发生。
第一阶段,收集至少2/3节点的投票,即leader节点发生了事件A,此时leader节点把这个时间点的证据保留下来,广播给其他节点。其他节点也就相当于发生了事件A;但是不知道 其他节点是否接收到leader广播的事件A;
第二阶段,所以收到事件A的节点 会发送消息给leader;leader会收集这些投票,即leader发生了事件B,同理,将这个事件B广播给其他节点,其他的节点在收到 时也相当于发生了事件B,但是还是不知道 其他节点是否有收到事件B;
第三阶段,跟第2步一样,收到事件B的节点也会发送投票给leader,leader收集,此时leader发生了事件C,同理,将这个事件广播给其他节点,其他节点收到后,就确认了共识已经达成。

总结以上几种一致性算法的设计思想,可以分成二阶段提交协议三阶段提交协议

二阶段提交协议


二阶段主要采取:先尝试,后提交。
可以分成两个角色:协调者和参与者。

第一阶段是提交事务请求
协调者向参与者发送事务内容,询问是否可以执行事务提交操作,等待响应;
参与者执行事务操作,并回复协调者,执行成功则回Yes否则No。
第二阶段是执行事务提交
如果参与者都回复Yes,那么协调者向参与者发送 提交请求,否则发送回滚请求。
参与者根据协调者的请求 执行事务提交或回滚,并向协调者发送Ack消息。
协调者收到所有的Ack消息后,判断分布式系统事务的结果是完成还是失败。
刚才介绍的paxos和raft都是基于二阶段提交的思想实现的。

二阶段优点

  • 原理简单;
  • 保证了分布式事务的原子性,要么全部执行成功,要么全部执行失败。

二阶段缺点

  • 同步阻塞:在提交执行过程中,各个参与者都在等待 其他参与者响应的过程,无法执行其他操作。
  • 单点问题:只有一个协调者,协调者挂掉,整个二阶段提交流程无法执行;更严重的是,在阶段二时,协调者出现问题,那参与者会一直处于锁定事务状态中,无法继续完成事务操作。
  • 数据不一致:在阶段二,协调者发送了Commit请求后,如果发生了网络故障,导致只有部分参与者收到commit请求,并执行提交操作,就导致数据不一致问题。

三阶段提交协议


因为二阶段提交有很多问题,所以出现了三阶段提交

主要的改进点是 将第一阶段分为两个阶段,先发起事务询问,再执行事务
同时在协调者和参与者中引入超时机制。

第一阶段 事务询问
协调者向参与者发送 包含事务内容的询问请求,询问是否可以执行事务;
参与者根据自己状态判断并回复yes或no;

第二阶段 执行事务预提交
如果协调者收到大部分节点的yes,就发送preCommit请求,否则发布abort请求;
参与者如果收到preCommit,就执行事务然后返回Ack。如果收到abort或者超时,就中断事务;

第三阶段是 执行事务提交
如果协调者收到大部分节点是Ack,就发送doCommit请求;
参与者收到doCommit就提交事务并返回响应;
协调者收到响应后,判断是完成事务还是中断事务;

pbft和hotstuf这类算法的基本思想就是三阶段提交协议。
三阶段的优点

  • 降低了二阶段的同步阻塞范围,在第二阶段,只要参与者收到preCommit请求,就会执行事务,不会一直阻塞。
  • 解决单点问题:进入阶段三会出现两种情况: 1:协调者出现问题; 2:协调者与参与者之间出现网络故障; 都会导致参与者无法收到doCommit请求,但参与者在超时之后都会提交事务。

三阶段的缺点

  • 还是会存在数据分区问题:参与者收到preCommit请求,此时如果出现网络分区,协调者与参与者之间无法进行正常网络通信,参与者在超时之后还是会进行事务提交,就会出现数据不一致。当然,这是分布式系统的通病,要保持一致性和可用性,就必然要牺牲分区容错性,这是分布式系统的不可能三角,也就是cap理论。所以不管是二阶段提交还是三阶段提交,数据分区是不可避免的。

有关分布式系统共识机制:一致性算法设计思想的更多相关文章

  1. ruby - 分布式事务和队列,ruby,erlang,scala - 2

    我有一个涉及多台机器、消息队列和事务的问题。因此,例如用户点击网页,点击将消息发送到另一台机器,该机器将付款添加到用户的帐户。每秒可能有数千次点击。事务的所有方面都应该是容错的。我以前从未遇到过这样的事情,但一些阅读表明这是一个众所周知的问题。所以我的问题。我假设安全的方法是使用两阶段提交,但协议(protocol)是阻塞的,所以我不会获得所需的性能,我是否正确?我通常写Ruby,但似乎Redis之类的数据库和Rescue、RabbitMQ等消息队列系统对我的帮助不大——即使我实现某种两阶段提交,如果Redis崩溃,数据也会丢失,因为它本质上只是内存。所有这些让我开始关注erlang和

  2. ruby - Ruby gsub 替换中的行为不一致? - 2

    两个gsub产生不同的结果。谁能解释一下为什么?代码也可在https://gist.github.com/franklsf95/6c0f8938f28706b5644d获得.ver=9999str="\tCFBundleDevelopmentRegion\n\ten\n\tCFBundleVersion\n\t0.1.190\n\tAppID\n\t000000000000000"putsstr.gsub/(CFBundleVersion\n\t.*\.).*()/,"#{$1}#{ver}#{$2}"puts'--------'putsstr.gsub/(CFBundleVersio

  3. ruby - Mongoid 3 中 Rails 模型的强一致性 - 2

    我希望特定模型的所有数据库交互都通过集群中的mongo主节点,因此我将模型设置为使用强一致性。classPhotoincludeMongoid::Documentwithconsistency::strongfield:number,type:Integer#let'ssayaphotonumberisuniqueinthedbvalidate:unique_numberend但这似乎不起作用,因为当我保存两张非常靠近的照片时,我仍然遇到验证错误。photo1#dbhasnumber=1forthisobjectphoto1.update_attributes(number:2)pho

  4. ruby - 停止分布式 Ruby 服务 - 2

    我有一个启动DRb服务的脚本,然后生成处理程序对象并通过DRb.thread.join等待。我希望脚本一直运行直到被明确杀死,所以我添加了trap"INT"doDRb.stop_serviceend在Ruby1.8下成功停止DRb服务并退出,但在1.9下似乎死锁(在OSX10.6.7上)。对该进程进行采样显示在semaphore_wait_signal_trap中有几个线程在旋转。我假设我在调用stop_service时做错了什么,但我不确定是什么。谁能给我任何关于如何正确处理它的指示? 最佳答案 好的,我想我已经找到了解决方案。如

  5. ruby - 为什么 Gemfile 语义版本控制运算符 (~>) 会产生与一个数字不一致的结果? - 2

    gemspec语义版本控制运算符~>(又名twiddle-wakka,又名pessimistic运算符)允许限制gem版本但允许进行一些升级。我经常看到它可以读作:"~>3.1"=>"Anyversion3.x,butatleast3.1""~>3.1.1"=>"Anyversion3.1.x,butatleast3.1.1"但是有了一个数字,这条规则就失效了:"~>3"=>"Anyversionx,butatleast3"*NOTTRUE!*"~>3"=>"Anyversion3.x"*True.Butwhy?*如果我想要“任何版本3.x”,我可以只使用“~>3.0”,这是一致的。就

  6. ruby - Ruby 中不一致的隐式哈希创建? - 2

    好的,所以我将我自己的DSL中的一些东西与Ruby进行了比较。他们都支持的一个结构是这个x=["key"=>"value"]知道数组和散列的区别,我会认为这是不合法的,但是在Ruby中的结果是[{"key"=>"value"}]这是为什么?有了这种语法,你为什么不能这样做x=("key"=>"value")为什么数组是隐式创建的哈希的特例? 最佳答案 另一个特殊情况是在函数调用中,考虑:deff(x)puts"OK:#{x.inspect}"endf("foo"=>"bar")=>OK:{"foo"=>"bar"}因此在某些情况下,

  7. BigData/Cloud Computing:基于阿里云技术产品的人工智能与大数据/云计算/分布式引擎的综合应用案例目录来理解技术交互流程 - 2

    BigData/CloudComputing:基于阿里云技术产品的人工智能与大数据/云计算/分布式引擎的综合应用案例目录来理解技术交互流程目录一、云计算网站建设:部署与发布网站建设:简单动态网站搭建云服务器管理维护云数据库管理与数据迁移云存储:对象存储管理与安全超大流量网站的负载均衡二、大数据MOOC网站日志分析搭建企业级数据分析平台基于LBS的热点店铺搜索基于机器学习PAI实现精细化营销基于机器学习的客户流失预警分析使用DataV制作实时销售数据可视化大屏使用MaxCompute进行数据质量核查使用Quick BI制作图形化报表使用时间序列分解模型预测商品销量三、云安全云平台使用安全云上服务

  8. ruby - if + else 和 if -> unless 不一致 - 2

    我今天在定义我解决的自定义RSpec匹配器时遇到了一个问题,但实际上看不出任何一种方法有效而另一种方法无效的原因,这是代码:方法1——if+else:RSpec::Matchers.define:have_success_messagedo|message|matchdo|page|ifmessage.nil?page.shouldhave_selector('div.alert.alert-success')elsepage.shouldhave_selector('div.alert.alert-success',text:message)endendend方法2--if后跟unl

  9. ruby - 数组的所有可能分布,来自一个数字 - 2

    我不太确定如何表达这一点,所以我只是举个例子。如果我写:some_method(["a","b"],3)我希望它返回某种形式的[{"a"=>0,"b"=>3},{"a"=>1,"b"=>2},{"a"=>2,"b"=>1},{"a"=>3,"b"=>0}]如果我传入some_method(%w(abc),2)期望的返回值应该是[{"a"=>2,"b"=>0,"c"=>0},{"a"=>1,"b"=>1,"c"=>0},{"a"=>1,"b"=>0,"c"=>1},{"a"=>0,"b"=>2,"c"=>0},{"a"=>0,"b"=>1,"c"=>1},{"a"=>0,"b"=>0,"

  10. ruby-on-rails - 如何使用 Rails Faker gem 生成一致的数据? - 2

    为了用虚假数据填充我的Rails应用程序,我经常这样做:person=Person.create(:first_name=>Faker::Name.first_name,:last_name=>Faker::Name.last_name,:email=>Faker::Internet.email)这可能会产生一个像这样的人:Firstname:OliviaLastname:KuberaEmail:milan_nieklauson@bachmannjacob.net有没有办法生成更连贯的假数据,例如:Firstname:OliviaLastname:KuberaEmail:olivia_

随机推荐