事务是由一系列对系统中的数据进行访问和更新的操作所组成的一个程序执行逻辑单元。狭义上的事务指的就是数据库的事务。
在事务处理的ACID属性中,一致性是最基本的属性,其它的三个特性都为了达到一致性而存在的。即一致性是事务的最终目的,原子性、持久性、隔离性是为了实现一致性所使用的方法。
原子性保证在同一个事务内部的一组操作必须全部执行成功(或者全部失败),如果一部分操作成功,一部分操作失败,结果肯定不是一致的状态。原子性一般是通过undo log实现。将对数据库的更新操作记录在undo log中,如果事务中的一部分操作已经成功,但之后的操作因错误不能执行,可以通过回溯undo log将已经成功执行的操作撤销,从而达到全部失败的的目的。
但是原子性是无法完全保证一致性的。在多个事务并发进行的情况下,即使保证了每一个事务的原子性,仍然可能导致数据不一致的结果,即脏读、不可重复读、幻读等。为了保证并发情况下的一致性,引入了隔离性。隔离性级别从低到高有读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。MYSQL隔离性一般使用锁和MVCC实现,具体原理可查看https://blog.csdn.net/qq_41775852/article/details/104853909
有了原子性和隔离性之后,还是不能保证完全的一致性。数据库为了提高写性能,通常会将数据先写入内存中,然后再刷写到磁盘中。如果数据还未刷写到磁盘中时,程序异常崩溃,会导致数据丢失。所以又引入持久性,其一般使用redo log实现。同样,将所有对数据的更新操作都写入 redo log。当数据库系统崩溃后重启,此时数据库处于不一致的状态,必须先执行一个crash recovery的过程:读取redo log进行REDO操作,重演将所有已经执行成功但尚未写入到磁盘的操作,保证持久性。
MYSQL原子性和持久性具体原理可查看:https://www.jianshu.com/p/9b83ea78b380
综上,我们知道,为了实现数据库数据的一致性,必须保证原子性、隔离性和持久性。
分布式一致性是指数据在多个副本之间是否保持一致性的状态。
详细解释:https://blog.csdn.net/qq_41775852/article/details/105002061
对于分布式系统,会出现系统的可用性与严格一致性(这里指分布式一致性)的冲突。因为当要求系统严格分布式一致性时,会降低系统的可用性。对于分布式系统,必须找到兼容分布式一致性和可用性的方法,所以出现了CAP和BASE理论。
CAP指的是Consistency(一致性)、Availability(可用性)和Partition tolerance(分区容错性)。
BASE理论是对CAP理论中一致性和可用性权衡的结果,核心思想是即使无法做到强一致性(StrongConsistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)
BASE理论面对的是大型的高可用可扩展的分布式系统,其通过牺牲强一致性来获得可用性,允许数据在一段时间内是不一致的,但是最终会达到一致性的状态。
分布式事务是指事务的参与者、支持事务的服务器,资源管理器以及事务管理器分别位于分布式系统的不同节点上。一个分布式事务会涉及多个数据源或是业务系统的操作。
一个分布式事务可以看作是由多个分布式操作序列组成的(例如转账业务涉及两个独立银行服务,一个取款,一个存款),通常可以把这一系列分布式操作序列称为子事务(类似下文的本地事务),每个子事务都能在本地保证自己的ACID。但是由于各个子事务的执行是分布式的,所以要实现刚性的分布式事务,必须保证所有子事务的原子性,即同时成功或者同时失败。但是严格保证子事务的原子性会影响分布式事务的性能,所以大多采用柔性事务。
在计算机系统中,更多的是通过关系型数据库来控制事务,这是利用数据库本身的事务特性来实现的,因此叫数据库事务,由于应用主要靠关系数据库来控制事务,而数据库通常和应用在同一个服务器,所以基于关系型数据库的事务又被称为本地事务。
刚性事务是指强一致性事务,严格遵循ACID, 本地事务是刚性事务。
柔性事务是指遵循BASE理论的事务, 通常用在分布式环境中。
以我的理解,分布式一致性和分布式事务其实是两回事。分布式一致性是表示在分布式系统中多个数据副本的一致性性状态。分布式事务表示的是一系列发生在分布式系统中不同节点的操作是否能遵循ACID。
但是经常会发现分布式一致性和分布式事务混为一谈,交叉不清,给我造成了很大困惑。我觉得这是对分布式一致性的认识的不同造成的。
上文所说的分布式一致性是分布式系统上多个节点上数据副本的一致性。但是也可以将其理解为分布式系统上多个节点上的数据的一致性,即分布式事务一致性(自己的理解,因为分布式事务的目的就是保证分布式系统上的数据保持一致的状态)。不同之处在于后者强调的是在不同节点上的数据保持一致(比如转账时,取款的银行扣钱,存款的银行加钱),但是并不要求这多个数据为同一个数据的副本。以此看来,分布式事务一致性其实是包括分布式一致性的。
由于分布式一致性保持多个副本数据一致和分布式事务保持多个数据一致是有很多共性之处的,所以针对于分布式一致性的理论,如CAP、BASE、一致性协议,都可以应用与分布式事务之上。
一致性状态也可以应用于分布式事务,强一致性表示事务提交后,分布式系统的多个数据必须全部保持一致。最终一致性表示,事务提交后,多个数据可以有不一致的状态,但是最终会达到一致。
对于分布式事务,CAP表示保持分布式系统上多个数据强一致性和系统可用性会造成冲突。所以经常采用遵循BASE原理的分布式事务,即最终一致性。
一些一致性协议也可以应用于分布式事务的实现中,比如2pc、3pc、等。
在一个分布式系统的设计过程中,往往会在系统的可用性和分布式一致性之间的进行反复地权衡,于是就产生了一系列的一致性协议。
逻辑时钟和向量时钟
NWR是一种在分布式存储系统中用于控制一致性级别的一种策略。在Amazon的Dynamo云存储系统中,就应用NWR来控制一致性。
NWR值的不同组合会产生不同的一致性效果。
在具体实现系统时,仅仅依靠NWR协议还不能完成一致性保证,因为在上述过程中,当读取到多个备份数据时,需要判断哪些数据是最新的,如何判断数据的新旧?这需要向量时钟来配合,所以对于Dynamo来说,是通过NWR协议结合向量时钟来共同完成一致性保证的。
ZAB协议中的原子广播阶段使用了二阶段提交协议,是为了保证数据写操作在多个节点上面的原子性。原子广播和崩溃选举后的数据同步过程保证了zookeeper分布式数据一致性。
https://blog.csdn.net/qq_41775852/article/details/104947943
Gossip协议如其名,流行病协议,一个节点有状态需要更新到网络的其它节点的时候,它会随机的选择周围的几个节点散播消息,收到消息的节点会重复这个过程,直到网络中所有的节点都收到了消息。这个过程需要一定的时间,消息之间的传递具有一定的延迟性。但是理论上所有的节点都会收到所有的消息,因此它是一个最终一致性消息。ElasticSearch在寻找Node时候使用了类Gossip的协议。
这两个协议是一致性协议,也是分布式共识算法。因为这两者解决的是对某个提案(proposal)大家达成一致意见的过程,其不仅可用于解决分布式一致性,也可以用于解决选主等需要所有节点达到共识的问题,实际上zookeeper的zab协议就是简化的paxos协议,用于解决崩溃选举。
2pc和3pc都可以用来在分布式事务中保证原子性。但是他们都有缺陷:
如果参与者之一或网络发生故障时会发生什么情况:如果任何一个准备请求失败或者超时,协调者就会中止事务。如果任何提交或中止请求失败,协调者将无条件重试。但是如果协调者崩溃,会发生什么情况就不太清楚了。
如果协调者在发送准备请求之前失败,参与者可以安全地中止事务。但是,一旦参与者收到了准备请求并投了“是”,就不能再单方面放弃 —— 必须等待协调者回答事务是否已经提交或中止。如果此时协调者崩溃或网络出现故障,参与者什么也做不了只能等待。参与者的这种事务状态称为存疑(in doubt)的或不确定(uncertain)的。
没有协调者的消息,参与者无法知道是提交还是放弃。原则上参与者可以相互沟通,找出每个参与者是如何投票的,并达成一致,但这不是2PC协议的一部分。
可以完成2PC的唯一方法是等待协调者恢复。这就是为什么协调者必须在向参与者发送提交或中止请求之前,将其提交或中止决定写入磁盘上的事务日志:协调者恢复后,通过读取其事务日志来确定所有存疑事务的状态。任何在协调者日志中没有提交记录的事务都会中止。因此,2PC的提交点归结为协调者上的常规单节点原子提交。
所以2pc最大的问题是阻塞问题:协调者故障,所有参与者必须阻塞等待协调者恢复。
3pc将提交分成三阶段,第二阶段和第三阶段引入了参与者超时机制处理机制,其中第三阶段参与者不需要阻塞直到与协调者恢复,超时自行提交事务,但是这可能会导致数据不一致。因为并不能确定协调者是真的故障了,还是只是因为网络原因与参与者无法通信。可能出现,协调者决定abort事务,但是因为网络原因,没有成功发送到某一参与者,超时后此参与者提交事务,则与其他参与者处于数据不一致的状态。
参考博客:https://blog.51cto.com/11821908/2058651
X/Open DTP(X/Open Distributed Transaction Processing Reference Model) 是X/Open 这个组织定义的一套分布式事务的标准,也就是了定义了规范和API接口,由各个厂商进行具体的实现。 X/Open DTP 定义了三个组件: AP,TM,RM。
其中在DTP定义了以下几个概念:
如果一个事务管理器管理着多个资源管理器,DTP是通过两阶段提交协议来控制全局事务和分支事务。
XA的ACID特性:
特点:
XA的优点:强一致性
XA的缺点与2pc协议的缺点类似,主要为同步阻塞、单点问题、数据不一致、容错性不好
TCC(Try-Confirm-Cancel)分布式事务模型相对于 XA 等传统模型,其特征在于它不依赖资源管理器(RM)对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务。
一个完整的TCC业务由一个主业务服务和若干个从业务服务组成,主业务服务发起并完成整个业务活动,TCC模式要求从服务提供三个接口:Try、Confirm、Cancel。
整个TCC业务分成两个阶段完成:
第一阶段:主业务服务分别调用所有从业务的try操作,并在活动管理器中登记所有从业务服务。当所有从业务服务的try操作都调用成功或者某个从业务服务的try操作失败,进入第二阶段。
第二阶段:活动管理器根据第一阶段的执行结果来执行confirm或cancel操作。如果第一阶段所有try操作都成功,则活动管理器调用所有从业务活动的confirm操作。如果有try操作失败,则调用所有从业务服务的cancel操作,回滚try操作。
如果confirm或者cancel失败,则不断进行重试直至成功,所以confirm和cancel需要满足幂等性。
如果confirm阶段如果有一个参与者失败了,该如何处理?其实上面操作都是xts-client做的,还有一个xts-server专门做事务补偿的。(蚂蚁金服基于TCC实现了XTS,XTS的解决方案)。
特点:
TCC是对二阶段的一个改进,try阶段通过预留资源(比如余额1000,预留资源100块,其余900可正常使用)的方式避免了同步阻塞资源的情况,但是TCC编程需要业务自己实现try,confirm,cancle方法,对业务入侵太大,实现起来也比较复杂。
TCC是应用层的2PC, 具有最终一致性。之前一直不理解为什么是最终一致性而不是强一致性。当执行try操作之后,try会预留部分资源,比如余额1000,预留资源100块,所以当前余额900,则当读取余额时会返回900块,但这并不是真实一致的数据,因为有可能其他节点try失败,所有节点回滚try操作,余额又变成1000。所以TCC只能保证数据的最终一致性。
TCC位于业务服务层,不在资源层,不与具体的服务框架耦合。
优点:
Try操作可以灵活选择业务资源的锁定粒度,不会造成同步阻塞。
最终一致性,严格保证分布式事务要么全部成功,要么全部自动回滚。
缺点:
Canfirm和Cancel的幂等性很难保证。、
每个服务都要实现TCC接口, 较为复杂。
与业务层耦合性高,难以扩展。
适用:
严格一致性
执行时间短
实时性要求高
举例: 红包, 收付款, 实时汇款业务.
Saga 是 30 年前一篇数据库伦理提到的一个概念。其核心思想是将长事务拆分为多个本地短事务,由 Saga 事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。
**Saga 的组成:**每个 Saga 由一系列 sub-transaction Ti 组成,每个 Ti 都有对应的补偿动作 Ci,补偿动作用于撤销 Ti 造成的结果。这里的每个 T,都是一个本地事务。
可以看到,和 TCC 相比,Saga 没有“预留 try”动作,它的 Ti 就是直接提交到库。
Saga 的执行顺序有两种:
T1,T2,T3,…,Tn。
T1,T2,…,Tj,Cj,…,C2,C1,其中 0 < j < n 。
Saga 定义了两种恢复策略:
**向后恢复,**即上面提到的第二种执行顺序,其中 j 是发生错误的 sub-transaction,这种做法的效果是撤销掉之前所有成功的 sub-transation,使得整个 Saga 的执行结果撤销。
**向前恢复,**适用于必须要成功的场景,执行顺序是类似于这样的:T1,T2,…,Tj(失败),Tj(重试),…,Tn,其中 j 是发生错误的 sub-transaction。该情况下不需要 Ci。
**在 Saga 模式中不能保证隔离性,因为没有锁住资源,其他事务依然可以覆盖或者影响当前事务。**可以从业务层面入手加入一 Session 以及锁的机制来保证能够串行化操作资源。
特点:
数据一致性有问题,因为Saga不保证隔离性,必须在业务层自己保证隔离性
这是分布式事务中要求最低的一种, 通过消息中间件实现, 在消息由MQ Server投递到消费者之后, 允许在达到最大重试次数之后直接结束事务, 无需人工介入确保成功.
最大努力通知方案的实现:
业务活动的主动方,在完成业务处理之后,向业务活动的被动方发送消息,允许消息丢失(丢失之后,被动方可以主动查询消息)。
会有个专门消费 MQ 的最大努力通知服务(有多种实现方法,一种直接使用MQ进行最大努力通知,另一种是独立的通知程序消费MQ,再进行最大努力通知服务,还可以主动方直接向被动方发消息),这个服务会消费 MQ 然后写入数据库中记录下来,或者是放入个内存队列也可以。然后发送通知消息给被动方。
业务活动的被动方如果正常接收了数据,就正常返回响应,并结束事务。否则,最大努力通知服务会按规则重复通知,直到通知N次后不再通知。
由于通知次数是有限的,不可能无限制的通知。所以主动方提供校对查询接口给被动方按需校对查询,用于恢复丢失的业务消息。
如果被动方没有正常接收消息,根据定时策略,向业务活动主动方查询,恢复丢失的业务消息,达到最终一致性。
特点:
eBay 的架构师Dan Pritchett,曾在一篇解释BASE 原理的论文《Base:An Acid Alternative》中提到一个eBay 分布式系统一致性问题的解决方案。它的核心思想是将需要分布式处理的任务通过消息或者日志的方式来异步执行,消息或日志可以存到本地文件、数据库或消息队列,再通过业务规则进行失败重试,它要求各服务的接口是幂等的。
本地消息表借助关系型数据库中的表实现。
主要过程为:
特点:
这个方案保证了最终一致性,哪怕 B 事务失败了,但是 A 会不断重发消息,直到 B 成功,但是其严重依赖于数据库的消息表来管理事务,会影响并发性。
有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交。所以可以不再使用数据库事务实现本地操作和消息发送的原子性。
主要过程:
特点:
实现了最终一致性,不需要依赖本地数据库事务。这种方式适合的业务场景广泛,而且比较可靠。不过这种方式技术实现的难度比较大。目前主流的开源MQ(ActiveMQ、RabbitMQ、Kafka)均未实现对事务消息的支持,所以需二次开发。
参考博客:
https://blog.csdn.net/qq_36142146/article/details/85247960
https://www.cnblogs.com/jajian/p/10014145.html
https://www.cnblogs.com/mayundalao/p/11798502.html
https://www.cnblogs.com/bluemiaomiao/p/11216380.html
https://blog.csdn.net/weixin_34211761/article/details/91441749
https://www.liangzl.com/get-article-detail-97306.html
https://blog.csdn.net/oldshaui/article/details/88743085
https://blog.csdn.net/congyihao/article/details/70195154?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
https://www.jianshu.com/p/f94c6f2a8e0f
https://www.jianshu.com/p/ac101d023d6b
https://www.cnblogs.com/luxiaoxun/p/8832915.html