前言
本文章把所以分布式理论集中在一处介绍,对比,部分内容摘自:孙玄老师的公众号文章《架构之美》 。
CAP:
CAP定理,关注的复制存储(replicated storage)的问题,包含一致性、可用性、分区容错性;
- 一致性:是指状态的一致性,也称之为原子性;
- 可用性:特指分布式的服务不能间断,过分延迟,保证服务质量;
- 分区容错性:分区是指状态在不同系统中不一致,当不一致的时候,具有恢复一致性的容错机制
CAP也被戏称为“帽子理论”,Eric Brewer在2000年ACM提的一想法:“一致性、可用性和分区容错性三者无法在分布式系统中被同时满足,并且最多只能满足其中两个!”
证明:2002年,Seth Gilbert和Nancy Lynch采用反正法证明了猜想:“如果三者可同时满足,则因为允许P的存在,一定存在Server之间的丢包,如此则不能保证C。” 在该证明中,对CAP的定义进行了更明确的声明。
它告诉我们,因为分布式系统丢包的存在,P是必然的硬性条件,而剩下的CP,AP二选一,但它们都不是简单的放弃第三方,而是在保证两方前提下,追求第三方,比如我们经常听到大部分场景都是使用最终消息一致性C,使得AP充分发挥。
如果让CP充分发挥,追求A,于是产生了一些过半写入就认为达到了一致性的算法,即最快速度保证返回,如后面要介绍的 Paxos,2PC,3PC等,过半写入后,分布式节点可以根据少数服从多数完成数据的一致性要求。因此产生了最大的效益
- 分布式实例的更高可用性,对所有实例不在全部写入成功才认为是成功。
- 分布式实例的更快响应性,使用广播快速获取过半结果后直接认定结果。依靠补充手段实现数据的一致性。
场景选择:我们一般是根据系统的业务场景去选择CP和AP,但是高可用是互联网分布式应用的特性,所以我们绝大部分情况是追求AP,尽量让系统满足更多的用户。然后基于某些场景数据的强一致性必要性去选择CP。
总结:在分布式环境下,对cap的要求。不管cp 还是ap,并不是完全丢弃另一个,而是优先级问题;在满足C或者A的基础上去追求另外一个,结论如下:
- CP--在强一致性的底线上追求可用性 (案例-过半写入)。
- AP—在高可用的基础上追求数据的一致性(案例-最终一致性)。
- 系统以AP为基调,在一些数据高即时、一致性场景使用CP进行补充。
FLP:
FLP定理是由 Fischer,Lynch 和 Patterson 三位科学家于 1985 年发表,是分布式理论中最重要的理论之一,它指出在最小化异步网络通信场景下,即使只有一个节点失败,也没有一种确定性的共识算法能够使得在其他节点之间保持数据一致。
模型假设
-
异步通信 与同步通信的最大区别是没有时钟、不能时间同步、不能使用超时、不能探测失败、消息可任意延迟、消息可乱序
-
通信健壮 只要进程非失败,消息虽会被无限延迟,但最终会被送达;并且消息仅会被送达一次(无重复)
-
fail-stop模型 进程失败如同宕机,不再处理任何消息。相对Byzantine模型,不会产生错误消息
-
失败进程数量 最多一个进程失败
衡量标准
-
Termination(终止性)非失败进程最终可以做出选择
-
Agreement(一致性)所有的进程必须做出相同的决议
-
Validity(合法性)进程的决议值,必须是其他进程提交的请求值 终止性,描述了算法必须在有限时间内结束,不能无限循环下去;一致性描述了我们期望的相同决议;合法性是为了排除进程初始值对自身的干扰。
CAP & FLP:
FLP讨论的分布式共识(distributed consensus)的问题。分布式共识可实现的功能包括:
-
leader election
-
replicated state machine
-
distributed commit
而CAP关注的是复制存储(replicated storage)的问题,replicated storage可以看作是replicated state machine的一个特例。可以看出,复制存储是分布式共识的子问题。也即,FLP关注的问题更加通用,CAP问题是FLP问题的子集。此外,CAP中的复制存储问题只讨论了这样一类问题:同一份数据在不同节点上进行存储(主从复制即是这样一类问题);而FLP中的分布式共识问题除了CAP中的问题外,还讨论了这样一类问题:多个任务(数据)被调度到不同节点上并行执行(存储),不同节点上的任务和状态可能是不同的(2PC协议即包含了这样一类问题)。由此也可见,FLP中讨论的问题更加复杂。一些方案可能无法解决FLP中的问题,但可能能够解决CAP中的问题。
BASE:
BASE主要是以下的组成
-
Basically Available-基本可用
-
Soft state-柔性状态
-
Eventual consistency-最终一致性
基本可用指分布式系统出现不可知故障时,允许损失部分可用性。具体表现为,一响应时间的损失:出现故障后,通过延长响应(服务重试其他提供者)来保障可用性;二功能上的损失:流量高峰时,通过服务降级、限流等治理手段提供有损服务。
柔性状态指允许系统的数据存在中间状态,并认为中间状态的存在不影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
最终一致性指系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
柔性事务主要分为补偿型和通知型。补偿型事务又分TCC、Saga,通知型事务分MQ事务消息、最大努力通知型。补偿型事务都是同步的,通知型事务都是异步的,所以有时可以听到另外一种划分。
一致性模型
线性一致性:或称原子一致性或严格一致性,指的是程序在执行顺序组合中存在可线性化点P的执行模型,这意味着一个操作将在程序的调用和返回之间的某个点P生效,之后被系统中并发运行的所有其他线程所感知。在多线程中用白话文表达就是:在并发场景下,一个线程对共享变量的操作能立即被其他线程感知到,如java的volatile关键字。
顺序一致性:假设执行结果与这些处理器以某一串行(多处理器的组合顺序中的其中一种串行就行)顺序执行的结果相同,同时每个处理器内部操作的执行看起来又与程序描述的顺序一致
- 执行结果与这些处理器以某一串行顺序执行的结果相同
- 每个处理器内部操作的执行看起来又与程序描述的顺序一致
线性一致性是对顺序一致性的加强,两者都要保证在线程内部操作的相对顺序,但线性一致性暗含着一个全局时钟,所有线程按实际发生的时间顺序执行,而顺序一致性只需要保证相对顺序即可。
简单解释下:a中只要保证单进程的内部执行顺序基础上,R(x)和R(y)分别在W(x,4)和W(y,2)之后就得到期望结果(不论结果正确与否),符合顺序一致性,但是因为从全局时钟来看,R(x)在W(x)之后但是不能读取最新值,所以不符合线性一致性。在b中能读取到最新值,所以符合线性一致性。c因为x和y都不能读取最新值,所以不满足顺序一致性更不谈线性一致性。
因果一致性:
保证具有因果关系的逻辑一致性正确。
一致性方案
有了分布式,因为网络等原因就会有分布式一致性问题,本质是状态的分布式不一致,而解决分布式一致性问题的方式有两种,第一是分布式事务,第二就是从业务上避免状态不一致(设计手段)
事务就可能需要多个分布式服务共同协作,称为分布式事务,分布式事务的解决方式按CAP、BASE定理的取舍,可以分为强一致性的同步式分布式,和弱一致性的异步式(补偿型)事务。分布式事务的触发场景:
- 跨数据库分布式事务:数据库的物理分割下保障跨库操作的ACID。
- 跨服务分布式事务:服务的网络分割下保障多服务的事务完整性。
- 混合式分布式事务:跨数据库分布式事务 + 跨服务分布式事务。
最根本的原因就是事务参与者出现在不同的网络中,需要网络通讯进行交互,因此不可避免的出现失败、超时等情况,所以需要分布式事务来处理。
强同步模式:XP(2PC)3PC
弱异步式子:TCC/FMT、Saga(状态机模式、Aop模式)、本地事务消息、消息事务(半消息)
XA规范(强一致性):
XA规范是 X/Open DTP 定义的交易中间件与数据库之间的接口规范(即接口函数),交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等。XA 接口函数由数据库厂商提供。X/Open DTP是X/Open 组织(即现在的 Open Group )1994定义的分布式事务处理模型
规范定义将事务参与者分成了TM、AP、RM、CRM 四个角色。应用程序( AP )、事务管理器( TM )、资源管理器( RM )、通信资源管理器( CRM )四部分。一般,常见的事务管理器( TM )是交易中间件,常见的资源管理器( RM )是数据库,常见的通信资源管理器( CRM )是消息中间件。
XA规范要求事务资源(如数据库) 本身提供对规范和协议的支持,这样的好处是如下:
- 可以从任何角度保证数据的隔离。
- 实现数据的全局一致性。
- 对业务无侵入。
XA规范在1994年就出现了,至今没有大规模流行起来,必然有他一定的缺陷:
- 数据锁定:数据在事务未结束前,为了保障一致性,根据数据隔离级别进行锁定。
- 协议阻塞:本地事务在全局事务 没 commit 或 callback前都是阻塞等待的。
- 性能损耗高:主要体现在事务协调增加的RT成本,并发事务数据使用锁进行竞争阻塞。
2PC(强一致性):
2PC 通常使用到XA中的三个角色TM、AP、RM,整体过程如下,如果第一阶段返回失败,那么马上向第十五参与者发送回滚操作
2PC缺陷:
1、全流程的同步阻塞:不管是第一阶段还是第二阶段,所有参与节点都是事务阻塞型。当参与者占有公共资源时,其他第三方访问公共资源可能不得不处于阻塞状态。
2、TM单点故障:由于全流程依赖TM的协调,一旦TM发生故障。参与者会一直阻塞下去。尤其在第二阶段,TM发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。所有参与者必须等待TM重新上线(TM重新选举)后才能继续工作。
3、TM脑裂引起数据不一致:在第二阶段中,当TM向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中TM发生了故障,这会导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据不一致性的现象。
4、TM脑裂引起事务状态不确定:TM再发出commit消息之后宕机,而接收到这条消息的参与者同时也宕机了。那么即使通过选举协议产生了新的TM,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
3PC(强一致性):
3PC 分成3个阶段:CanCommit(准备阶段)、PreCommit(对齐阶段)、DoCommit(提交阶段);
3PC新增了参与者(RM)的超时机制,一方面辅助解决了2PC的事务/事务问题,还能降低一定的同步阻塞问题。因为TM、RM双向超时机制,所以维基百科对3PC定义为“非阻塞”协议。
- 准备阶段:跟2PC的表决阶段很类似,TM向参与者发送commit请求,参与者如果可以提交就返回Yes,否则返回No,询问超时默认参与者为No。唯一差别在于SQL层面:准备阶段只做了SQL处理,并未记录事务日志(Undo 和Redo)
- 对齐阶段:TM 和 各个参与者对齐事务状态,TM 通知各个参与者事务最终状态,各个参与者如果一致未收到事务对齐通知,会在超时后从TM反查事务状态实现事务状态对齐。在SQL层面:事务状态对齐后,记录事务日志(Undo 和Redo)
- 提交阶段:该阶段进行真正的事务提交。根据第二阶段得到的事务状态结果,各参与者根据TM的通知命令进行提交/abort或者超时后自动提交/abort。
TCC:(弱一致性 补偿型)
TCC概念由Pat Helland于2007年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出, 在该论文中,TCC还是以Tentative-Confirmation-Cancellation命名。正式以Try-Confirm-Cancel作为名称的是Atomikos公司,并且还注册了TCC商标。
TCC模型完全交由业务实现,每个子业务都需要实现Try-Confirm-Cancel三个接口,对业务侵入大,资源锁定交由业务方。
- Try:尝试执行业务,完成所有业务检查(一致性),预留必要的业务资源(准隔离性)。
- Confirm:确认执行业务,不再做业务检查。只使用Try阶段预留的业务资源,Confirm操作满足幂等性。
- Cancel:取消执行业务释放Try阶段预留业务资源。
因为TCC对业务的强侵入性,使用成本非常昂贵,虽然提供了更灵活的资源锁粒度,对标2PC拥有更高的吞吐量。但是相对于2PC的强一致性来说,TCC的实施成本和数据一致性的牺牲带来的相对高吞吐量,总体表现出来的性价比非常低,反而在市面上成熟的大型企业中几乎没有使用。
Saga(弱一致性 补偿性)
Saga模型是把一个分布式事务拆分为多个本地事务,每个本地事务都有相应的执行模块和补偿模块(对应TCC中的Confirm和Cancel),当Saga事务中任意一个本地事务出错时,可以通过调用相关的补偿方法恢复之前的事务,达到事务最终一致性。
Saga模型主要分:
- 一串子事务(本地事务)的事务链
- 每个Saga子事务Tn, 都有对应的补偿定义 Cn用于撤销Tn造成的结果
- 每个Tn都没有“预留”动作,直接提交到库。
执行顺序:
- 子事务序列 T1, T2, …, Tn得以完成 (最佳情况)
- 或者序列 T1, T2, …, Tj, Cj-1, …, C2, C1, 0 < j < n, 得以完成
数据隔离性:
- 业务层控制并发
- 在应用层加锁
- 应用层预先冻结资源等
恢复方式:
- 向后恢复:补偿所有已完成的事务,如果任一子事务失败
- 向前恢复:重试失败的事务,假设每个子事务最终都会成功
从Saga模型的上述定义中,Saga 模型可以满足事务的三个特性:
- 原子性:Saga 协调器协调事务链中的本地事务要么全部提交,要么全部回滚。
- 一致性:Saga 事务可以实现最终一致性。
- 持久性:基于本地事务,所以这个特性可以很好实现。
Saga设计必须遵循允许空补偿、保持幂等性、防止资源悬挂三个策略,因为Saga事务不保证隔离性,在极端情况下可能由于脏写无法完成回滚操作。比如举一个极端的例子, 分布式事务内先给用户A充值, 然后给用户B扣减余额, 如果在给A用户充值成功, 在事务提交以前, A用户把余额消费掉了, 如果事务发生回滚, 这时则没有办法进行补偿了。这就是缺乏隔离性造成的典型的问题, 实践中一般的应对方法是:
-
业务流程设计时遵循“宁可长款, 不可短款”的原则, 长款意思是客户少了钱机构多了钱, 以机构信誉可以给客户退款, 反之则是短款, 少的钱可能追不回来了。所以在业务流程设计上一定是先扣款。
-
有些业务场景可以允许让业务最终成功, 在回滚不了的情况下可以继续重试完成后面的流程, 所以要求Saga除了提供“回滚”能力还需要提供“向前”恢复上下文继续执行的能力, 让业务最终执行成功, 达到最终一致性的目的。
最大努力通知 | 事物消息:(弱一致性 通知型)
最大努力通知事务的主流实现仍是基于MQ来进行事务控制。最大努力通知事务和事务消息都是通知型事务,主要适用于那些需要异步更新数据,并且对数据的实时性要求较低的场景;
最大努力通知事务主要用于外部系统,因为外部的网络环境更加复杂和不可信,所以只能尽最大努力去通知实现数据最终一致性,比如充值平台与运营商、支付对接、商户通知等等跨平台、跨企业的系统间业务交互场景;而事务消息主要适用于内部系统的数据最终一致性保障,因为内部相对比较可控,比如订单和购物车、收货与清算、支付与结算等等场景。
普通消息是无法解决本地事务执行和消息发送的一致性问题的。因为消息发送是一个网络通信的过程,发送消息的过程就有可能出现发送失败、或者超时的情况。超时有可能发送成功了,有可能发送失败了,消息的发送方是无法确定的,所以此时消息发送方无论是提交事务还是回滚事务,都有可能不一致性出现。所以通知型事务的难度在于投递消息和参与者自身本地事务的一致性保障。因为核心要点一致,都是为了保证消息的一致性投递,所以最大努力通知事务在投递流程上跟事务消息是一样的,因此也有两个分支:l 基于MQ自身的事务消息方案l 基于DB的本地事务消息表方案
最大努力通知 vs 事物消息
- 从参与者来说:最大努力通知事务适用于跨平台、跨企业的系统间业务交互;事务消息更适用于同网络体系的内部服务交付。
- 从消息层面说:最大努力通知事务需要主动推送并提供多档次时间的重试机制来保证数据的通知;而事务消息只需要消息消费者主动去消费。
- 从数据层面说:最大努力通知事务还需额外的定期校验机制对数据进行兜底,保证数据的最终一致性;而事务消息秩序保证消息的可靠投递即可,自身无需对数据进行兜底处理。
单数据库事务可以满足事务ACID 四个特性,提供强一致性保证,但在分布式事务要完全遵循 ACID 特性会比较困难。在互联网时代中,我们通常追求分布式系统的高可用和高吞吐,所以分布式事务一般选择最终一致性。
咱们把提供强一致性的事务称之为刚性事务,把提供最终一致性的事务称之为柔性事务。刚性事务可以完全满足 ACID 四个特性,柔性事务对事务的 ACID 特性的支持情况如下:
-
原子性:完全支持。
-
一致性:只提供最终一致性支持。
-
隔离性:不完全保证,通常为了系统的吞吐和性能,会一定程度上放弃对隔离性的要求。
-
持久性:完全支持。
总结:
在分布式系统中,CAP理论通常是指导思维,而BASE理论是CAP理论中的AP延申,是对 CAP 中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。而柔性事务其实就是BASE理论的实践产物。因为分布式事务难免会对系统造成一定的性能损耗,所以在互联网高可用和高吞吐的要求下。我们通常处理分布式事务的原则是:业务规避 > BASE柔性事务 > CP刚性事务。柔性事务通常推荐 同步Saga、异步事务消息;刚性事务推荐 2PC实现。
Paxos:
对于数据一致性,Google Chubby的作者Mike Burrows说过:“there is only one consensus protocol, and that’s Paxos” – all other approaches are just broken versions of Paxos。”
译文:世上只有一种一致性算法,那就是Paxos,所有其他一致性算法都是Paxos算法的不完整版。
这个一致性算法,由Leslie Lamport发明,解决的是分布式系统就某个值(决议),达成一致的方案。
前提:计算机中的同步异步是很重要概念,而计算机中的有状态对象,服务,数据结构,也是很重要的概念,所以分布式一致性,讨论的就是状态,分布式状态一致性方案有两种,第一种是共享内存,第二种是消息传递,而Paxos就是共享内存的方案;
原理:在分布式系统中,如何才能让各个系统的状态保持一致呢?举个有应用的例子,也是CQRS或者REDIS的AOF,他们用到的状态一致方式,都是Event Source事件溯源,也就是根据事件按照事务顺序一一处理,直到计算出来的最后一个状态,就是最终状态。
再明显一点就是,假设server1,server2,server3它们如果想状态一致,那么他们只需执行相同的指令来得到最终的指令reduce结果即可。
所以如果分布式中的各系统,各个系统的事件顺序是一致的,那么他们就会执行相同的事务顺序,最终达成一致的状态。分布式系统经过消息传递达成状态一致的方式,就简化为:如何保证每一步的操作一致,比如,第一个指令大家都执行什么呢?第二个指令大家都执行什么呢?第三个呢?
在Paxos中,这个第一个,第二个,第三个,就是一轮轮的提案,而每一个提案的值,就是该轮要执行的事件。下面分析一轮提案的过程,也就是Paxos的过程: