上一篇讲了分布式系统的几种存储引擎的类型,这一章我们将分布式系统中的基础理论以及常见的分布式协议。
分布式系统,
顾名思义,数据是分布在不同的节点上,那么数据分布就是首先需要考虑的一点。
1、数据如何均匀分布到不同的节点上,涉及到负载均衡
2、为了保证数据的可靠些,需要对数据设置多个副本,那么如何保证副本之间的一致性
3、节点是廉价的pc机,如果节点宕机,那么如何自动检测,并迁移数据
分布式最基础的两个协议,一个是paxos选举协议,一个是两阶段提交协议。
paxos选举协议:用于在多个节点中选举一个总控节点
两阶段提交协议:保证在多个节点中事务操作的原子性,要么完全成功,要么全部失败。
————————————————————————————————————————————
分布式系统的异常
分布式系统中各个节点都需要认为是可靠的,所以需要有一套机制,来保证不可靠的情况下数据是如何迁移的,如何快速恢复的,那么在分布式系统是如何完成的,诸如这些问题,首先还是要知道分布式的异常来自于哪些?
1、异常:服务器的宕机、网络的异常、磁盘的故障
2、网络超时:网络异常在分布式系统中是一定存在的,所以分布式系统的处理结果存在三态的概念,如果某个节点像另外一个节点发起调用,那么这个调用执行的结果只有三种状态,成功、失败、超时三种状态。处理这种超时的状态也是需要涉及考虑的一部分。
————————————————————————————————————————————
分布式系统中的一致性
分布式中为了处理容错,发生故障的数据恢复问题,一般容错采用的是多副本策略,即当一个节点出现故障的时候,可以从其他的副本读取数据,保证副本的有效性。
在分布式中如何保证多个副本的一致性呢?这也是需要解决的问题
一致性主要分为以下三个等级:
1、强一致性:某个节点写了数据,其他节点读取操作都一定会返回最新的数据值
2、弱一致性:某个节点写了数据,不能保证其他后续的节点读取操作可以返回最新的值
3、最终一致性:某个节点写了数据,在之后的某一段时间内,其他节点读取操作都一定会返回最新的数据值,超过这段时间可能不会。
————————————————————————————————————————————
分布式系统中的数据分布
由于数据是分布在不同节点上,那么数据分布是分布式必须要考虑的一点,数据部分主要有两种:
1、哈希分布,例如一致性哈希分布
2、顺序分布:将一个整体顺序的大表,按照顺序划分为各个子表,然后把各个子表分到不同的节点上。
对于数据分布中重要的一点是需要完成自动的负载均衡,就是分布式系统能够自动识别各个节点的负载情况,迁移各个负载之间的数据,保证各个节点之间数据是均衡。
1、哈希分布:
哈希分布的思想很简单,根据数据的某一个特征计算哈希值,哈希值对应了服务器的节点,从而根据映射的不同哈希值分布在不同的服务器上。
优点:
哈希的方式可以很均匀的将数据分散在不同的集群中,而且哈希方式需要记录的原信息也非常小,只需要计算哈希函数的计算方式即可。
缺点:
找到一个很好的哈希函数很难,因为选择什么样的数据特征进行散列是非常难的,例如如果使用主键散列,那么同一个用户的id下的主键就被散列在不同的服务器上,那么同一个用户id下的操作就会涉及到多个服务器的使用,这会变得很困难。同样使用用户id进行散射的话,可能导致某个服务器上数据量很大,出现倾斜。
当服务器出现下线和上线的时候,哈希函数映射将完全的打乱,这个时候几乎所有的数据都需要重新的迁移分布,会带来大量的迁移开销
改进:
上面传统的哈希方法多多少少都会带来两个缺点,那么如何改进呢,下面给出了比较常见的改进措施:
1、不再讲哈希值和服务器直接映射,而且将哈希值和服务器作为元数据进行存储,专门设置一个元数据服务器,当数据访问的时候,首先去元数据服务器上找对应哈希的服务器,那么当集群增加或者减少的时候,可以将部分的映射哈希值分配给新的机器即可,不需要重新动其他的机器上的数据。
2、第二种方式就是一致性哈希算法,该算法将所有的服务器构成一个环,并给这些服务器随机分配token值,通过数据的主键计算哈希值,然后比较这个哈希值和token值,顺时针将该数据放置在大于token值的第一个节点上。当增加或者减少服务器时,影响的只是该节点相邻的节点,不会影响到其他的节点上。
同时引入虚拟节点技术,可以保证数据在哈希环上不会发送倾斜。
2、顺序分布
哈希分布虽然有优点,但是它致命的是不能支持顺序的扫描操作,因为数据是随机分布的,只支持堆积读取操作,需要支持顺序扫描操作的分布式存储系统上,数据的分布使用的一般是顺序分布的操作,这种操作很简单,就是将一个大表装换称为许多个小表。需要额外考虑的时,在分成很多小表的时候,需要定期进行合并小表,特别是那些无效的表,防止系统中出现过多的太小的子表。
————————————————————————————————————————————
分布式系统中负载均衡
分布式系统中每个集群之间一般存在一个总控节点,这个总控节点负责根据全局的负载信息进行整体的调度管理,当一个节点发生异常的时候,这个时候总控节点负责检测出,并且生成迁移任务放入迁移队列中。
看起来这种总控节点可以解决这个问题,那么如果总控节点本身发送了故障怎么办???
这个时候会采用分布式系统常见的poxia选举协议,从其他的节点中选举一个作为新的总控节点。
负载均衡中还会涉及到复制技术,简单来说,就是多副本复制的技术,节点不能将数据直接复制给多个副本,那么这个时候可能复制多副本的时间长,阻塞了原本的正常写服务,系统的可用性就受到影响。
分布式中存放多个副本,首先选取一个副本为主副本,其他副本为备副本,常见的做法是客户端首先将数据写入到主副本,然后有主副本按照顺序复制到其他的副本上。
分为强同步和异步复制:
强同步
整个流程如下:
1、客户端将写的请求发送给主副本,
2、主副本首先将操作日志同步到各备副本,同步成功后发给主副本
3、主副本成功后发回给客户端。
异步复制:
1、客户端将写的请求发送给主副本,
2、主副本写完即返回给客户端,不关系背副本是否同步成功
————————————————————————————————————————————
分布式系统中的容错机制
在分布式系统中,每天都可能发送故障,如果去检测这些故障,并且恢复这些故障是很重要的
常见检测故障的方法有以下几种:
1、心跳包,不同的发送心跳包,有回复则是好的,没有回复就是坏的。
2、租约机制,机器A检测B是否发生故障,没有发生故障就给予租期,在租期内可以进行服务,租期到了再次检测并申请租期,申请成功后才能再次服务。如果某个机器发生了故障,那么就不能申请租期,一旦租期到了就不会提供服务,将原本的服务转由其他的服务完成。
————————————————————————————————————————————
分布式协议
分布式系统的基本协议就是上面将的两阶段提交协议和paxos选举协议
两阶段提交协议
这个协议主要解决的是分布式事务的原子性,一般在进行事务处理的时候,有一个协调者和事务的参与者,这个参与者一般是分布式的,包含多个。
主要的执行过程有两个阶段:
1、第一阶段请求阶段,协调者首先告诉各个参与者是否可以提交执行该事务,所有的参与者需要给予回复,是否可以参与这个事务的处理
2、第二阶段提交阶段,协调者根据所有参与者的回答,决定这个事务是否可以执行,或者是取消,这样可以保证事务只有可能是执行,或者是不能执行,两种状态保证了事务的原子性。
存在的两种问题:
1、事务的参与者出现故障,一直不给回复,所有参与者和协调者都等待,解决办法,设置一个超时时间,超时就代表不能执行该事务。
2、协调者出现故障,这个时候需要将协调者的相关事务转移到备协调者上,如果再次出现故障,则再次转移。如果协调者出现了永久性故障,这个时候参与者得不到响应会一直阻塞,是一种阻塞式协议。
paxos选举协议
这个协议主要解决多个节点中一致性的问题,多个节点为了保证一致性,都是通过操作日志 同保护数据,如果只有一个主节点,那么操作日志可以保证是一值的,但是如果主节点出现了故障,这个时候需要选举出一个新的主节点,完成全局的一致性。
选举新主节点的过程主要 如下:
1、批准阶段:提议者发送接受信息给其他的所有节点,告诉他们,我要称为新的主节点
2、确认阶段:如果超过一半的参与者同意提议者是新的主节点,则提议者发送确认的信息,告诉所有的节点,现在我是新的主节点。
如果出现了多个节点都提出自己的新的主节点,这个时候协议会按照提出序号n进行判断,即选取一个序号最大的重新循环的执行选举过程,直到最后有超过一半的同意。
————————————————————————————————————————————
以下转载自其它博客,原文链接没找到
Paxos算法
Paxos算法是莱斯利·兰伯特(Leslie Lamport)于1990年提出的一种基于消息传递的一致性算法。 Paxos 算法是一个解决分布式系统中,多个节点之间就某个值(注意是某一个值,不是一系列值)达成一致的通信协议。能够处理在少数派离线的情况下,剩余的多数派节点仍然能够达成一致。
Lamport是通过故事的方式提出Paxos 问题:
希腊岛屿Paxon 上的执法者在议会大厅中表决通过法律(一次paxos过程),并通过服务员(proposer)传递纸条的方式交流信息,每个执法者会将通过的法律记录在自己的账目上。问题在于执法者和服务员都不可靠,他们随时会因为各种事情离开议会大厅(服务器拓机或网络断开),并随时可能有新的执法者进入议会大厅进行法律表决(新加入机器),使用何种方式能够使得这个表决过程正常进行,且通过的法律不发生矛盾(对一个值达成一致)。 注意:paxos过程中不存在拜占庭将军问题(消息不会被篡改)和两将军问题(信道可靠)。
paxos 协议中有四种角色:
● client 议题产生者,产生一个待分布式系统达成一致的值v
● proposer 提议者,用client产生的值v,向acceptor发出提议
● acceptor 决策者,决定是否接受proposer的提议,大多数接受了提议,结果达成一致,达成一致的结果不可更改
● learner 决策学习者,学习最终达成一致的结果。一旦学习成功,关闭对应的paxos过程(paxos instance),并通知acceptor(或acceptor主动向learner获取)。
这四种角色中,proposer和acceptor比较重要,协议主要的交互逻辑都在这两种角色中。
paxos 是一个两阶段的通信协议:
1. 第一阶段 Prepare
client产生一个值v,并告知proposer,我这里产生了一个待accept的值。
proposer收到通知后,生成一个全局唯一并且递增的提案ID,带着这个ID(不需要携带v)向集群中的所有acceptor发送PrepareRequest请求。 acceptor收到PrepareRequest请求后,检查一下之前接收到的提案ID(包括第一阶段和第二阶段),新接收的提案ID用n表示,之前接收到的提案ID用N表示。如果n <= N,返回拒绝,并携带N的值。如果n > N,把n记录下来,以后不再接收提案ID比n小的提议,
这时分两种情况:
○ 之前没有accept任何值v,返回可以接收提议
○ 之前已经accept过值,返回可以接收提议,并携带已经accept的,并且提案ID最大的值
2. 第二阶段 Accept
如果proposer收到大多数acceptor的拒绝应答,回到第一阶段,根据接收到的最大的N,把提案ID增大,继续发送PrepareRequest。
如果proposer收到大多数acceptor可以接收提议的应答,从多个应答中选出提案ID最大的值(第一阶段如果acceptor已经accept过值,会返回提案ID最大的值),作为提案值。如果应答中没有值,选择client产生的值v作为提案值。然后携带当前的提案ID,一起向集群中所有acceptor发送AccpetRequest请求。 对这段话进行解释:第一阶段client产生的值v,不一定作为Accept阶段的提案值。为了更快的达成一致,如果之前已经accept了值,那么proposer会倾向于把提案值修改为之前接受的值。各个proposer不是针锋相对,而是合作共赢。 acceptor收到AccpetRequest后,检查请求中携带的提案ID,如果此提案ID大于或等于acceptor记录的提案ID(在第一阶段和第二阶段,acceptor记录最大的提案ID),接受提议并记录提案ID和提案值。否则拒绝,并返回记录的提案ID。 proposer收到大多数acceptor接受提案的应答,形成决议,达成一致。
如果proposer收到大多数acceptor拒绝的应答,回到第一阶段,把提案增大(增加幅度依据acceptor返回的提案ID),发送PrepareRequest。
举个例子帮助理解paxos协议,把整个paxos协议的流程想象成一次竞标。有两个老板(client),每个老板都有一个属于自己的秘书(proposer),还有3个政府官员(acceptor)。
● 第一阶段
两个老板(client)分别提出议题:“XXX项目我要中标”,各自的秘书(proposer)带着现金(提案ID),去找政府官员(acceptor)。
作为政府官员(acceptor),谁给的钱多答应给谁中标(只是口头答应,并没有签约)。钱少的直接拒绝。
○ 场景1:秘书p1带着10000美金去找3个官员a1、a2、a3,这时候还没有人来找过a1、a2、a3,所以这3个官员直接就答应了p1,并告诉p1他们之前没有接受过任何提议。这样p1在一阶段获得了全票支持。过一会,秘书p2带着8000美金去找3个官员,3个官员一看只有8000美金,直接拒绝p2,告诉p2别人已经出价10000。
○ 场景2:p1的行为和第一个场景相同。p2带着12000美金去找3个官员,3个官员一看p2给的钱比p1多,把p2的钱记下来,很高兴的告诉p2,可以接收p2的提议。这样在第一阶段p1和p2都获得了全票支持。
○ 场景3:p1和p2同时带着10000美金去找3个官员,p1先找到了a1、a2,此时没有人找过a1和a2,a1和a2答应p1,告诉p1他们之前没有接受过任何提议。p2在p1之前找到了a3,之前没有人找过a3,a3答应p2,告诉p2他之前没有接受过任何提议。这时候p1也找到了a3,a3一看和之前收到的钱一样多,拒绝了p1。同样,在p2找到a1和a2时,应为钱数一样,也被拒绝。最终,在第一阶段,p1获得2票(a1和a2),超过半数,p1可以进入第二阶段。p2获得1票,少于半数,p2被打回第一阶段。
● 第二阶段
○ 场景1:在第一阶段p1获得了全票支持,p1带着老板的议题去找官员签合同,并告诉官员之前已经给了10000美金。官员看了一下收款记录,没错是10000美金,3个官员和p1成功签约,达成一致。在第一阶段p2被拒绝,这时p2回到第一阶段,带了12000美金去找官员。官员一看12000比刚才接受的10000多,告诉p2,你的钱多,可以接受你的议题,但是之前已经接受了p1的议题,p1的议题内容为“XXXXXX”。p2看了下p1的议题,觉得自己的老板没前途,干脆跟着p1的老板干算了,于是把自己的议题修改为p1的议题(为了更快达成一致)。p2带着修改之后的议题去找官员,告诉官员之前给了12000。官员看了一下记录,没错是12000,接受了p2的议题(这时p2的议题和p1一样,虽然官员同时接受了p1和p2,但是系统仍然是一致的)。
○ 场景2:在第一阶段p1获得了全票支持,p1带着老板的议题去找官员签合同,并告诉官员之前已经给了10000美金。官员看了一下收款记录,记录上写的是12000,于是告诉p1,已经有人出了12000,我们不能accept你的提议。在第一阶段p2也获得了全票支持,这时候p2来找官员,告诉官员之前给了12000,官员核实了一下记录,发现没错,接受了p2的议题。p1被拒绝后,回到第一阶段,带了14000美金去找官员。官员一看14000比刚才接受的12000多,告诉p1,你的钱多,可以接受你的议题,但是之前已经接受了p2的议题,p2的议题内容为“XXXXXX”。p1看了下p2的议题,觉得自己的老板没前途,干脆跟着p2的老板干算了,于是把自己的议题修改为p2的议题(为了更快达成一致)。p1带着修改之后的议题去找官员,告诉官员之前给了14000。官员看了一下记录,没错是14000,接受了p1的议题(这时p1的议题和p2一样,虽然官员同时接受了p1和p2,但是系统仍然是一致的)。
○ 场景3-1:在第一阶段p1获得a1、a2的应答,p1带着老板的议题去找a1、a2签合同,并告诉a1、a2之前已经给了10000美金。核实之后,a1和a2接受了p1的议题。由于大多数官员接受了p1的议题,所以p1的议题被确定接受。
○ 场景3-2:在第一阶段p1获得a1、a2的应答,p1带着老板的议题去找a1、a2签合同。但是p2在被打回第一阶段后,以很快的速度带着12000美金,赶在p1之前再次找到a1和a2以及a3。a1、a2、a3答应p2,并把记录金额修改为12000,并告诉p2,我们没有接受任何议题(p1还在签约途中)。这时p1终于找到了a1和a2,准备签约,但是a1和a2告诉p1,我们刚才收了12000,比你给的10000要多,拒绝接受你的议题,p1被打回第一阶段。在第一阶段p2获得全票,于是带着议题去找a1、a2、a3签约,a1、a2、a3核实金额之后,成功签约,达成一致。
paxos协议中的活锁问题
在上边描述的场景中,如果在官员签约之前,p1和p2一直不停的往上加金额,就算进入第二阶段,也会因为有更高的报价,导致签约失败被打回第一阶段。就像拍卖会中,多个买家一直往上飙价格,最后一件东西都没有卖出去。这种情况称为活锁,没有产生阻塞,但是一直无法达成一致。
有三种解决方案:
- 在被打回第一阶段再次发起PrepareRequest请求前加入随机等待时间。
- 设置一个超时时间,到达超时时间后,不再接收PrepareRequest请求。
- 在proposer中选举出一个leader,通过leader统一发出PrepareRequest和AcceptRequest。
multi paxos一次paxos过程(paxos instance)只能对一个值达成一致。multi paxos是运行多个paxos instance来对多个值达成一致。
下边以MySQL组复制为例,说明multi paxos。
MySQL组复制中存在多个master,可以同时接受client写入和读取数据,并且可以保证多个master中数据一致,每一次写入数据都会生成一条log日志,通过paxos协议进行一致性处理,达成一致之后完成log复制。针对一系列操作,需要运行多次paxos过程(paxos instance),需要引入一个ID来标识相应的paxos instance,这里用logID表示。每一次写入操作,都生成一个logID与log日志对应,也同时与paxos instance对应,logID全局唯一且自增。
有了logID之后,每个paxos instance独立运行,可以对每条log日志达成一致。这样问题貌似已经解决了,但是还有优化的空间。
每一次paxos instance都是一个两阶段过程(prepare和accept),所有proposer地位平等,都可以提出议题。在有多个proposer同时提出议题时,有很大概率冲突,每次冲突都会重新执行prepare阶段,网络和性能开销较大。同时paxos协议本身存在活锁问题,有可能导致一个议题始终无法达成一致。为了解决这两个问题,multi paxos中引入了leader的概念,从多个proposer中选举出一个leader作为提议代表,每个提议都通过leader发出。这样的话,解决了活锁问题(活锁问题的第三个解决方案)。因为提议都是通过leader发出,只要leader保证提案ID自增,就可以跳过prepare阶段,直接进行accept阶段(两阶段变为一阶段)。
那么问题来了,如何选举leader?运行一次paxos instance,提议的v就是“选举自己为leader”。但是还有问题,一次paxos instance只能对一个值达成一致。举个例子:A、B、C三台机器,运行paxos选举leader。第一次选举产生结果,A是leader。之后A拓机,再次运行paxos,选举的结果还是A是leader。运行多个paxos instance(multi paxos)可以选举出不同的leader,但是multi paxos需要选举出一个leader来优化网络损耗和活锁的问题,这里产生一个问题递归依赖。
换个思路,给每一个达成一致的值v一个过期时间,达到过期时间清空v,这样用一个paxos instance就可以选出不同的leader。每台机器上都启动一个倒计时T,leader在存活状态下,不断重置T,T在倒计时为零时清空上次选举的结果,并发起选举。
上边简单说明了一种选举leader的算法,通过选举leader,可以提高paxos的性能(两阶段变为一阶段),并解决活锁问题。paxos的正确性不依赖于选举结果,在选举失败或者同时选出多个leader的情况下,退化为普通paxos。
总结
paxos协议可以归纳为几个原则:
- 不接受旧值(通过递增ID确定新旧)
- 为了更快达成一致,proposer会把值修改为最有可能达成一致的值
- 只有多数派接受了值,值才达成一致
- 一旦一个值达成一致,不可更改
paxos是一致性协议的基础,其他的协议(raft、zab等)都是paxos的改进版本。paxos侧重理论,实现paxos非常困难。谷歌chubby论文中提到,从paxos出发,在实现过程中处理了很多实际中的细节之后,已经变成另外一个算法了,这时候正确性无法得到理论的保证。所以才出现了许多基于paxos的改进算法。