分布式系统杂谈
一、单机的痛点
当我们使用单台机器对外提供服务时,有如下两个主要问题:
第一、单台机器的性能已经无法满足我们的要求
第二、单台机器容易造成单点故障,导致服务可用性不高
二、分布式系统
分布式就是为了解决这两个问题应运而生的。具体来讲,我们通常可以通过如下方式来解决这两个问题
1.数据分区:将每个数据块分别放在不同的机器上,这样可以解决单台机器的性能问题,却无法解决单点故障问题
2.数据镜像:让所有的服务器都保存该数据。它可以用来解决单点故障问题
但是这两种方式却带来新的问题,下面我们以最经典的案例来说明:A向B转账200元
这个案例需要分为如下六个步骤:
1.读取A的账户余额
2.将A的账户余额减去200
3.设置A的账户余额
4.读取B的账户余额
5.将B的账户余额增加200
6设置B的账户余额
当然这六个步骤是需要事务化的执行,即要么全做,要么一个也不做。否则的话,容易导致A减了200,B却没有增加等错误情况发生。
当我们采用数据分区时:
假设A和B的账户资料不在同一个分区上,这样便产生了跨机器的分布式事务一致性问题
当我们采用数据镜像时:
当我们对一个数据镜像上的A和B的账户余额进行操作时,如何将数据的更改同步到其他镜像?
当有两个镜像分别对A的账户余额进行操作时,我们如何写操作的一致性?
同样是跨机器分布式一致性问题。
三、分布式的痛点
首先我们来介绍一下CAP定理
C:Consistency,即(强)一致性。表示任何时间在不同的节点访问到的数据都是相同的。
A:Availablity,可用性。即不论什么情况下,集群都可以正常对外提供服务。
P:Partition tolerance,分区容忍性,即是否可以容忍将集群划分成不可连接的分区。(在互联网中,由于网络的不稳定性、以及宕机的风险存在,分区容忍性是必须要满足的)
CAP定理表示:我们不可能同时满足CAP三个条件,最多只能同时满足其中两个。举例来说:
1)当我们需要满足A和P时,为了满足A,需要在多个分区有数据备份,那么由于网络的隔离,则无法保证C。
2)当我们需要满足C和A时,同样为了A,需要有多个备份,并且备份间网络互通才能保证C,则P无法满足。
3)当我们需要满足P和C时,由于网络隔离,并且需要强一致性,则不能有备份,这样的话容易产生单点故障,则无法保证A。
当然,由于CAP定理中所说的C是指强一致性,那么在互联网P条件是一定的情况下,我们可以通过降低一致性的方法,来提高可用性(当然这种情况下的可用性,也是低于只满足P、而不满足C的情况的,无非是权衡罢了)。所以CAP不是必须3选2的,而是三者都可以得到一定程度上的满足,注意是一定程度上的,而不是完全满足。
四、分布式一致性
分布式一致性分为两种:弱一致性和强一致性,其中弱一致性又包括最终一致性。
弱一致性:当我们修改一个数据后,无法保证在任何节点上获取的数据完全一致。
最终一致性:当我们修改其中一个数据时,虽然最开始通过不同节点获取的数据可能不同,但是经过一段时间后其一定相同。
强一致性:我们修改一个数据后,任何时候,通过任何节点获取的数据一定相同。
下面我们先通过主从架构为例分别进行说明,架构图如下(请忽略我的渣画工):
1)向master中写数据时,只有向所有的slave同步成功时,才返回成功,否则只要有一个slave写入失败,则回滚。这就是强一致性的实现,由此可见,强一致性一般是由同步的方式实现的。
在网络隔离的情况下,由于不能像所有slave中成功写入数据,所以写入可用性得不到保障。这种方式适合银行等对数据一致性要求非常高的应用场景(例如存款数据)下使用。
2)向master中写数据时,只要向其中的一个或者几个(非全部)slave写入成功时就返回成功,其他未写入的slave则等待后续的异步写入。这就是最终一致性的实现,由上可见,弱一致性一般是由异步的方式实现的。这样即使在存在网络隔离的情况下,假如需要写入的slave和master在一个网络分区中,写入可用性也是存在的。所以这种情况相当于牺牲了部分一致性来换取了部分可用性,也印证了上面所说的“CAP中三者是可以在一定程度上存在的”这种论点。这种方式在互联网这种对数据一致性要求不高的场景下(例如评论数据等)应用比较多。
另外,在这种处理模式下,master写入成功,slave写入失败时则有两种处理方法:
第一:回滚所有的成功写入,并返回失败
第二:标记写入失败的slave为不可用,返回成功,当该slave恢复可用时,则从master同步该数据。
五、分布式事务
5.1 两阶段提交
由于一个节点很难知道其他节点事务的执行情况,所以在实现分布式事务时,通常需要一个协调者的角色,两阶段提交也不例外。
所谓两阶段提交,就是指将分布式事务的提交分成两个部分:预备部分和提交部分。有人说预备部分像西方婚礼中的宣誓部分(征求男女双方是否愿意),提交部分则像是婚礼执行部分,我觉得不是很准确,理由下面我会讲到。
请求阶段:
1.协调者向参与者发送prepare request
2.参与者收到消息后,锁定事务所需要的资源并开始执行事务但并不提交,并将事务执行结果发送给协调者。由于这里对事务所需要的资源进行了锁定,并且执行完了事务(只是没有提交),所以说上面所说的拿西方婚礼宣誓部分来表示该阶段是不太准确的(宣誓只是征求意愿,并没有执行婚礼)。
提交阶段:
1.协调者收到参与者返回的消息后,有三种可能:
(1) 全部收到YES,则再发送commit消息给参与者,提示参与者提交事务
(2) 收到有至少一个NO,则发送abort给参与者,表示回滚事务
(3) 超时未收到(某参与者的)回复,重试或者标记该参与者为不可用
2.参与者收到1中协调者发来的消息,有两种可能:
(1) 如果收到commit消息,则提交事务
(2) 如果收到abort消息,则回滚事务
同时,返回确认给协调者。
上述就是两阶段提交的执行过程。下面我们来分析下两阶段提交的缺点:
1.由于两阶段提交的分布式事务是同步的,所以性能是其一大短板。
2.协调者的单点问题。
3.网络抖动TIMEOUT情况的发生所导致的问题,其中一个比较头疼的情况是:由于网络抖动等原因而导致的接收不到请求包或者响应包的情况。当在分布式事务中的时候,由于参与者没有延时机制,假如参与者无法接收到协调者发来的消息,那么参与者将处于不知所措的状态,其将永远处于该事务的执行中而无法退出。如果是部分参与者收到消息而部分没有收到,还有数据一致性问题。接下来所说的三阶段提交就是为了解决这个问题而生。
CAP分析:由上述3可以看出,两阶段提交不满足分区容忍性P,因为如果发生分区,导致协调者和参与者无法通信,那些协调者将会处于阻塞状态。但是两阶段提交却可以满足一致性C,因为参与者要么提交事务,要么回滚事务,即使发生网络分区,那些与协调者不能通信的参与者将进入阻塞状态,不会产生不一致性。对于可用性A,当协调者正常运作时,系统是可以提供服务的,但是一旦协调者宕机,整个系统的所有参与者将全部阻塞,所以有一定可用性,但是可用性不强,不过可以通过给协调者增加备机等方式来改善,所以说两阶段提交算法可以满足可用性。总结来说,两阶段满足CA,不满足P。
ACID分析:事务有四特性,分别是:A-原子性,C-一致性,I-隔离性,D-持久性。
A-原子性:对于两阶段提交,其所有参与者如果可以提交,那么则提交,如果有一个以上参与者不可提交,则全部回滚,可以看出是支持原子性的。
C-一致性:在上面CAP中已经分析过了
I-隔离性和D-持久性:由各个子事务保证,既然组成分布式事务的子事务满足这些条件,分布式事务同样也满足。
5.2 三阶段提交
为了解决上述第4个问题,三阶段提交引进了参与者延时机制以及CanCommit阶段。概括来说,引入CanCommit阶段来增强判断事务执行成功的判断正确的可能性,以便于在参与者延时的时候猜测事务执行成功而提交事务,而不用像二阶段提交中参与者那样的不知所措。
下面讲一下三阶段提交的步骤:
CanCommit:
1.协调者向参与者发送请求,征求其是否可以执行事务提交。
2.参与者接收到该请求后,并不锁定资源,也不执行事务,而是根据其他的一些条件判断该事务是否可执行并提交。如果可以,则返回YES,否则返回NO
PreCommit与DoCommit:
这两个阶段与两阶段提交的两步基本相同,只不过是增加了参与者延时机制。此时如果参与者由于网络抖动或者协调者宕机,导致参与者无法接收到协调者发来的消息,由于经过CanCommit阶段的预判,那么事务提交成功的成功率则大大提高,所以此时参与者则会提交事务。
虽然三阶段提交解决了二阶段提交的第4个问题,然而一致性问题却没有得到解决。例如,在PreCommit和DoCommit阶段中,由于某些原因导致协调者发送回滚事务的消息时,一些参与者无法接收到该消息。那么这些参与者则会提交事务,而另外收到消息的参与者则回滚事务,此时便产生了不一致性。
CAP分析:由于延时机制的加入,导致系统产生了不一致性C,但是却增加了其分区可容忍性P,即系统及时出现分区,系统一样可以对外提供服务。即两阶段满足PA,不满足C
六 分布式一致性算法
paxos:
raft:
未完待续...