![]() |
正直的桔子 · iphone怎么导入课表- 抖音· 6 月前 · |
![]() |
强健的日记本 · 北华大学历史文化学院_百度百科· 1 年前 · |
![]() |
冷冷的火锅 · 台男子诈骗被捕诡辩:我是关公干儿子怎会干坏事 ...· 1 年前 · |
![]() |
温柔的手套 · 如何评价美剧《曼达洛人》第二季? - 知乎· 1 年前 · |
![]() |
稳重的佛珠 · AI升级版无间道_人工智能· 1 年前 · |
ACID
的特性,即原子性、一致性、隔离性和持久性。
ACID
特性,高效,可靠,状态可以只在资源管理器中维护,而且应用编程模型简单。但是本地事务不具备分布式事务的处理能力,隔离的最小单位受限于资源管理器。
TX
协议:应用或者应用服务器与事务管理器的接口。
XA
协议:全局事务管理器与资源管理器的接口。
XA
是由
X/Open
组织提出的分布式事务规范。该规范主要定义了全局事务管理器和局部资源管理器之间的接口。主流的数据库产品都实现了
XA
接口。
XA
接口是一个双向的系统接口,在事务管理器以及多个资源管理器之间作为通信桥梁。之所以需要
XA
是因为在分布式系统中从理论上讲两台机器是无法达到一致性状态的,因此引入一个单点进行协调。由全局事务管理器管理和协调的事务可以跨越多个资源和进程。全局事务管理器一般使用
XA
二阶段协议与数据库进行交互。
AP
:应用程序,可以理解为使用
DTP
(
Data Tools Platform
)的程序。
RM
:资源管理器,这里可以是一个
DBMS
或者消息服务器管理系统,应用程序通过资源管理器对资源进行控制,资源必须实现
XA
定义的接口。资源管理器负责控制和管理实际的资源。
TM
:事务管理器,负责协调和管理事务,提供给
AP
编程接口以及管理资源管理器。事务管理器控制着全局事务,管理事务的生命周期,并且协调资源。
XA
用于在全局事务中协调多个资源的机制。
TM
和
RM
之间采取两阶段提交的方案来解决一致性问题。两节点提交需要一个协调者(
TM
)来掌控所有参与者(
RM
)节点的操作结果并且指引这些节点是否需要最终提交。两阶段提交的局限在于协议成本,准备阶段的持久成本,全局事务状态的持久成本,潜在故障点多带来的脆弱性,准备后,提交前的故障引发一系列隔离与恢复难题。
BASE
理论:
BA
指的是基本业务可用性,支持分区失败,
S
表示柔性状态,也就是允许短时间内不同步,
E
表示最终一致性,数据最终是一致的,但是实时是不一致的。原子性和持久性必须从根本上保障,为了可用性、性能和服务降级的需要,只有降低一致性和隔离性的要求。
CAP
定理:对于共享数据系统,最多只能同时拥有
CAP
其中的两个,任意两个都有其适应的场景,真是的业务系统中通常是
ACID
与
CAP
的混合体。分布式系统中最重要的是满足业务需求,而不是追求高度抽象,绝对的系统特性。
C
表示一致性,也就是所有用户看到的数据是一样的。
A
表示可用性,是指总能找到一个可用的数据副本。
P
表示分区容错性,能够容忍网络中断等故障。
分布式事务与分布式锁的区别?
分布式锁解决的是分布式资源抢占的问题;分布式事务和本地事务是解决流程化提交问题。
当本地事务要扩展到分布式时,它的复杂性进一步增加了。
存储端的多样性
首先就是存储端的多样性。本地事务的情况下,所有数据都会落到同一个
DB
中,但是,在分布式的情况下,就会出现数据可能要落到多个
DB
,或者还会落到
Redis
,落到
MQ
等中。
存储端多样性,如下图所示:
事务链路的延展性
本地事务的情况下,通常所有事务相关的业务操作,会被我们封装到一个
Service
方法中。而在分布式的情况下,请求链路被延展,拉长,一个操作会被拆分成多个服务,它们呈现线状或网状,依靠网络通信构建成一个整体。在这种情况下,事务无疑变得更复杂。
事务链路延展性,如下图所示:
基于上述两个复杂性,期望有一个统一的分布式事务方案,能够像本地事务一样,以几乎无侵入的方式,满足各种存储介质,各种复杂链路,是不现实的。
至少,在当前,还没有一个十分成熟的解决方案。所以,一般情况下,在分布式下,事务会被拆分解决,并根据不同的情况,采用不同的解决方案。
对于分布式系统而言,需要保证分布式系统中的数据一致性,保证数据在子系统中始终保持一致,避免业务出现问题。分布式系统中对事务要么一起成功,要么一起失败,必须是一个整体性的事务。
分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
简单的说,在分布式系统上一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务节点上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。
举个例子:在电商网站中,用户对商品进行下单,需要在订单表中创建一条订单数据,同时需要在库存表中修改当前商品的剩余库存数量,两步操作一个添加,一个修改,我们一定要保证这两步操作一定同时操作成功或失败,否则业务就会出现问题。
任何事务机制在实现时,都应该考虑事务的
ACID
特性,包括:本地事务、分布式事务。对于分布式事务而言,即使不能都很好的满足,也要考虑支持到什么程度。
典型的分布式事务场景:
跨库事务指的是,一个应用某个功能需要操作多个库,不同的库中存储不同的业务数据。下图演示了一个服务同时操作
2
个库的情况:
通常一个库数据量比较大或者预期未来的数据量比较大,都会进行水平拆分,也就是分库分表。如下图,将数据库
B
拆分成了
2
个库:
对于分库分表的情况,一般开发人员都会使用一些数据库中间件来降低sql操作的复杂性。如,对于
sql
:
insert into user(id,name) values (1,"tianshouzhi"),(2,"wangxiaoxiao")
。这条
sql
是操作单库的语法,单库情况下,可以保证事务的一致性。但是由于现在进行了分库分表,开发人员希望将
1
号记录插入分库
1
,
2
号记录插入分库
2
。所以数据库中间件要将其改写为
2
条
sql
,分别插入两个不同的分库,此时要保证两个库要不都成功,要不都失败,因此基本上所有的数据库中间件都面临着分布式事务的问题。
微服务架构是目前一个比较火的概念。例如某个应用同时操作了
9
个库,这样的应用业务逻辑必然非常复杂,对于开发人员是极大的挑战,应该拆分成不同的独立服务,以简化业务逻辑。拆分后,独立服务之间通过
RPC
框架来进行远程调用,实现彼此的通信。下图演示了一个
3
个服务之间彼此调用的架构:
Service A
完成某个功能需要直接操作数据库,同时需要调用
Service B
和
Service C
,而
Service B
又同时操作了
2
个数据库,
Service C
也操作了一个库。需要保证这些跨服务的对多个数据库的操作要不都成功,要不都失败,实际上这可能是最典型的分布式事务场景。
分布式事务实现方案必须要考虑性能的问题,如果为了严格保证
ACID
特性,导致性能严重下降,那么对于一些要求快速响应的业务,是无法接受的。
数据库事务
ACID
四大特性,无法满足分布式事务的实际需求,这个时候又有一些新的大牛提出一些新的理论。
CAP
定理是由加州大学伯克利分校
Eric Brewer
教授提出来的,他指出
WEB
服务无法同时满足一下
3
个属性:
Consistency
):客户端知道一系列的操作都会同时发生(生效)
Availability
):每个操作都必须以可预期的响应结束
Partition tolerance
):即使出现单个组件无法可用,操作依然可以完成
具体地讲在分布式系统中,一个
Web
应用至多只能同时支持上面的两个属性。因此,设计人员必须在一致性与可用性之间做出选择。
2000年7月
Eric Brewer
教授仅仅提出来的是一个猜想,
2
年后,麻省理工学院的
Seth Gilbert
和
Nancy Lynch
从理论上证明了
CAP
理论,并且而一个分布式系统最多只能满足
CAP
中的
2
项。之后,
CAP
理论正式成为分布式计算领域的公认定理。
所以,
CAP
定理在迄今为止的分布式系统中都是适用的!
数据一致性指“all nodes see the same data at the same time”,即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致,不能存在中间状态。
分布式环境中,一致性是指多个副本之间能否保持一致的特性。在一致性的需求下,当一个系统在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处理一致的状态。
例如对于电商系统用户下单操作,库存减少、用户资金账户扣减、积分增加等操作必须在用户下单操作完成后必须是一致的。不能出现类似于库存已经减少,而用户资金账户尚未扣减,积分也未增加的情况。如果出现了这种情况,那么就认为是不一致的。
数据一致性分为强一致性、弱一致性、最终一致性
系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
两个度量的维度:
(1)有限时间内
对于用户的一个操作请求,系统必须能够在指定的时间(响应时间)内返回对应的处理结果,
如果超过了这个时间范围,那么系统就被认为是不可用的
。即这个响应时间必须在一个合理的值内,不让用户感到失望。
试想,如果一个下单操作,为了保证分布式事务的一致性,需要10分钟才能处理完,那么用户显然是无法忍受的。
(2)返回正常结果
要求系统在完成对用户请求的处理后,返回一个正常的响应结果。正常的响应结果通常能够明确地反映出对请求的处理结果,即成功或失败,而不是一个让用户感到困惑的返回结果。比如返回一个系统错误如
OutOfMemory
,则认为系统是不可用的。
“返回结果”是可用性的另一个非常重要的指标,它要求系统在完成对用户请求的处理后,返回一个正常的响应结果,不论这个结果是成功还是失败。
即分布式系统在遇到任何网络分区故障时,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
网络分区,是指分布式系统中,不同的节点分布在不同的子网络(机房/异地网络)中,由于一些特殊的原因导致这些子网络之间出现网络不连通的状态,但各个子网络的内部网络是正常的,从而导致整个系统的网络环境被切分成了若干孤立的区域。组成一个分布式系统的每个节点的加入与退出都可以看做是一个特殊的网络分区。
放弃分区容错性的话,则放弃了分布式,放弃了系统的可扩展性
放弃可用性的话,则在遇到网络分区或其他故障时,受影响的服务需要等待一定的时间,再此期间无法对外提供政策的服务,即不可用
放弃一致性的话(这里指强一致),则系统无法保证数据保持实时的一致性,在数据达到最终一致性时,有个时间窗口,在时间窗口内,数据是不一致的。
对于分布式系统来说,
P
是不能放弃的,因此架构师通常是在
可用性和一致性之间权衡
。
目前很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。
基于
CAP
理论,很多系统在设计之初就要对这三者做出取舍:
任何一个分布式系统都无法同时满足一致性(
Consistency
)、可用性(
Availability
)和分区容错性(
Partition tolerance
),最多只能同时满足两项。在互联网领域的绝大多数的场景中,都需要
牺牲强一致性来换取系统的高可用性
,系统往往只需要保证最终一致性。
问:为什么分布式系统中无法同时保证一致性和可用性?
答:首先一个前提,对于分布式系统而言,分区容错性是一个最基本的要求,因此基本上我们在设计分布式系统的时候只能从一致性(
C
)和可用性(
A
)之间进行取舍。
如果保证了一致性(
C
):对于节点
N1
和
N2
,当往
N1
里写数据时,
N2
上的操作必须被暂停,只有当
N1
同步数据到
N2
时才能对
N2
进行读写请求,在
N2
被暂停操作期间客户端提交的请求会收到失败或超时。显然,这与可用性是相悖的。
如果保证了可用性(
A
):那就不能暂停
N2
的读写操作,但同时
N1
在写数据的话,这就违背了一致性的要求。
通过
CAP
理论,我们知道无法同时满足一致性、可用性和分区容错性这三个特性,那要舍弃哪个呢?
对于多数大型互联网应用的场景,主机众多、部署分散,而且现在的集群规模越来越大,所以节点故障、网络故障是常态,而且要保证服务可用性达到
N
个
9
,即保证
PA
,舍弃
C
(退而求其次保证最终一致性)。虽然某些地方会影响客户体验,但没达到造成用户流程的严重程度。对于涉及到钱财这样不能有一丝让步的场景,
C
必须保证。网络发生故障宁可停止服务,这是保证
CA
,舍弃
P
。貌似这几年国内银行业发生了不下
10
起事故,但影响面不大,报道也不多,广大群众知道的少。还有一种是保证
CP
,舍弃
A
。例如网络故障是只读不写。
ACID
中的
A
指的是原子性(
Atomicity
),是指事务被视为一个不可分割的最小工作单元,事务中的所有操作要么全部提交成功,要么全部失败回滚;
CAP
中的
A
指的是可用性(
Availability
),是指集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求;
ACID
一致性是有关数据库规则,数据库总是从一个一致性的状态转换到另外一个一致性的状态;
CAP
的一致性是分布式多服务器之间复制数据令这些服务器拥有同样的数据,由于网速限制,这种复制在不同的服务器上所消耗的时间是不固定的,集群通过组织客户端查看不同节点上还未同步的数据维持逻辑视图,这是一种分布式领域的一致性概念;
ACID
里的一致性指的是事务执行前后,数据库完整性,而
CAP
的一致性,指的是分布式节点的数据的一致性。背景不同,无从可比
CAP
是分布式系统设计理论,
BASE
是
CAP
理论中
AP
方案的延伸,对于
C
我们采用的方式和策略就是保证最终一致性;
eBay
的架构师
Dan Pritchett
源于对大规模分布式系统的实践总结,在
ACM
上发表文章提出
BASE
理论,
BASE
理论是对
CAP
理论的延伸,核心思想是即使无法做到强一致性(
StrongConsistency
,
CAP
的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(
Eventual Consitency
)。
BASE
是基本可用(
Basically Available
)、软状态(
Soft state
)和最终一致性(
Eventually consistent
)三个短语的缩写。
BASE
基于
CAP
定理演化而来,核心思想是即时无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
基本可用是指分布式系统在出现不可预知的故障的时候,允许损失部分可用性,但不等于系统不可用。
(1)响应时间上的损失
当出现故障时,响应时间增加
(2)功能上的损失
当流量高峰期时,屏蔽一些功能的使用以保证系统稳定性(服务降级)
指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性。
与硬状态相对,即是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
强调系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。其本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
最终一致性可分为如下几种 :
Causal consistency
)
Read your writes
)
Session consistency
)
Monotonic read consistency
)
Monotoic write consistency
)
BASE
理论是提出通过牺牲一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。
BASE
理论面向的是大型高可用可扩展的分布式系统,和传统的事物
ACID
特性是相反的。它完全不同于
ACID
的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。但同时,在实际的分布式场景中,不同业务单元和组件对数据一致性的要求是不同的,因此在具体的分布式系统架构设计过程中,
ACID
特性和
BASE
理论往往又会结合在一起。
BASE
理论是对
CAP
中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于
CAP
定理逐步演化而来的。
BASE
理论的核心思想是:
即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性
。
BASE
理论其实就是对
CAP
理论的延伸和补充,主要是对
AP
的补充。牺牲数据的强一致性,来保证数据的可用性,虽然存在中间装填,但数据最终一致。
ACID
是传统数据库常用的设计理念,追求强一致性模型。
BASE
支持的是大型分布式系统,提出通过牺牲强一致性获得高可用性。
ACID
和
BASE
代表了两种截然相反的设计哲学,在分布式系统设计的场景中,系统组件对一致性要求是不同的,因此
ACID
和
BASE
又会结合使用。
分布式场景下,多个服务同时对服务一个流程,比如电商下单场景,需要支付服务进行支付、库存服务扣减库存、订单服务进行订单生成、物流服务更新物流信息等。如果某一个服务执行失败,或者网络不通引起的请求丢失,那么整个系统可能出现数据不一致的原因。
上述场景就是分布式一致性问题,追根到底,分布式一致性的根本原因在于数据的分布式操作,引起的本地事务无法保障数据的原子性引起。
分布式一致性问题的解决思路有两种,一种是分布式事务,一种是尽量通过业务流程避免分布式事务。分布式事务是直接解决问题,而业务规避其实通过解决出问题的地方(解决提问题的人)。其实在真实业务场景中,如果业务规避不是很麻烦的前提,最优雅的解决方案就是业务规避。
分布式事务实现方案从类型上去分 刚性事务、柔型事务 :
CAP
的
CP
理论
BASE
理论(基本可用,最终一致)
刚性事务:通常无业务改造,强一致性,原生支持回滚/隔离性,低并发,适合短事务。
原则:刚性事务满足
CAP
的
CP
理论
刚性事务指的是,要使分布式事务,达到像本地式事务一样,具备数据强一致性,从
CAP
来看,就是说,要达到
CP
状态。
刚性事务:
XA
协议(
2PC
(AT)、
JTA
、
JTS
)、
3PC
,但由于同步阻塞,处理效率低,不适合大型网站分布式场景。
AT
模式是增强的
2PC
。
柔性事务指的是,不要求强一致性,而是要求最终一致性,允许有中间状态,也就是
Base
理论,换句话说,就是
AP
状态。
与刚性事务相比,柔性事务的特点为:有业务改造,最终一致性,实现补偿接口,实现资源锁定接口,高并发,适合长事务。
柔性事务分为:
柔型事务:
TCC/FMT
、
Saga
(状态机模式、
Aop
模式)、本地事务消息、消息事务(半消息)
X/OPEN
是一个组织
.X/Open
国际联盟有限公司是一个欧洲基金会,它的建立是为了向
UNIX
环境提供标准。它主要的目标是促进对
UNIX
语言、接口、网络和应用的开放式系统协议的制定。它还促进在不同的
UNIX
环境之间的应用程序的互操作性,以及支持对电气电子工程师协会(
IEEE
)对
UNIX
的可移植操作系统接口(
POSIX
)规范。
X/Open
DTP(Distributed Transaction Process)
是一个分布式事务模型。这个模型主要使用了两段提交(
2PC - Two-Phase-Commit
)来保证分布式事务的完整性。
在X/Open DTP(Distributed Transaction Process) 模型里面,有三个角色:
应用程序(
Application
简称
AP
): 也就是业务层。哪些操作属于一个事务,就是
AP
定义的。
事务管理器(
Transaction Manager
简称
TM
): 接收
AP
的事务请求,对全局事务进行管理,管理事务分支状态,协调
RM
的处理,通知
RM
哪些操作属于哪些全局事务以及事务分支等等。这个也是整个事务调度模型的核心部分。
资源管理器(
Resource Manager
简称
RM
):一般是数据库,也可以是其他的资源管理器,如消息队列(如
JMS
数据源),文件系统等。
XA
把参与事务的角色分成
AP
,
RM
,
TM
。
AP
即应用,也就是我们的业务服务。
RM
指的是资源管理器,即
DB
,
MQ
等。
TM
则是事务管理器。
AP
自己操作
TM
,当需要事务时,
AP
向
TM
请求发起事务,
TM
负责整个事务的提交,回滚等。
XA
规范主要定义了(全局)事务管理器(
TM
)和(局部)资源管理器(
RM
)之间的接口。
XA
接口是双向的系统接口,在事务管理器(
TM
)以及一个或多个资源管理器(
RM
)之间形成通信桥梁。
XA
之所以需要引入事务管理器是因为,在分布式系统中,从理论上讲(参考
Fischer
等的论文),两台机器理论上无法达到一致的状态,需要引入一个单点进行协调。事务管理器控制着全局事务,管理事务生命周期,并协调资源。资源管理器负责控制和管理实际资源(如数据库或
JMS
队列)
什么是
XA
?用非常官方的话来说:
XA
规范是
X/Open
组织定义的分布式事务处理(
DTP
,
Distributed Transaction Processing
)标准。
XA
规范描述了全局的事务管理器与局部的资源管理器之间的接口。
XA
规范的目的是允许的多个资源(如数据库,应用服务器,消息队列等)在同一事务中访问,这样可以使
ACID
属性跨越应用程序而保持有效。
XA
规范使用两阶段提交(
2PC
,
Two-Phase Commit
)协议来保证所有资源同时提交或回滚任何特定的事务。
XA
规范在上世纪
90
年代初就被提出。目前,几乎所有主流的数据库都对
XA
规范提供了支持。
XA
规范(
XA Specification
)是
X/OPEN
提出的分布式事务处理规范。
XA
则规范了
TM
与
RM
之间的通信接口,在
TM
与多个
RM
之间形成一个双向通信桥梁,从而在多个数据库资源下保证
ACID
四个特性。目前知名的数据库,如
Oracle
、
DB2
、
MySQL
等,都是实现了
XA
接口的,都可以作为
RM
。
XA
是数据库的分布式事务,强一致性,在整个过程中,数据一张锁住状态,即从
prepare
到
commit
、
rollback
的整个过程中,
TM
一直把持折数据库的锁,如果有其他人要修改数据库的该条数据,就必须等待锁的释放,存在长事务风险。
以下的函数使事务管理器可以对资源管理器进行的操作
:
1)
xa_open
,
xa_close
:建立和关闭与资源管理器的连接。
2)
xa_start
,
xa_end
:开始和结束一个本地事务。
3)
xa_prepare
,
xa_commit
,
xa_rollback
:预提交、提交和回滚一个本地事务。
4)
xa_recover
:回滚一个已进行预提交的事务。
5)
ax_
开头的函数使资源管理器可以动态地在事务管理器中进行注册,并可以对
XID
(
Transaction Ids
)进行操作。
6)
ax_reg,ax_unreg
;允许一个资源管理器在一个
TMS
(
Transaction Manager Server
)中动态注册或撤消注册。
XA各个阶段的处理流程
两阶段提交(
2PC
)协议是
XA
规范定义的数据一致性协议。
三阶段提交(
3PC
)协议对
2PC
协议的一种扩展。
作为
java
平台上事务规范
JTA
(
Java Transaction API
)也定义了对
XA
事务的支持,实际上,
JTA
是基于
XA
架构上建模的,在
JTA
中,事务管理器抽象为
javax.transaction.TransactionManager
接口,并通过底层事务服务(即
JTS
)实现。像很多其他的
java
规范一样,
JTA
仅仅定义了接口,具体的实现则是由供应商(如
J2EE
厂商)负责提供,目前
JTA
的实现主要由以下几种:
1.
J2EE
容器所提供的
JTA
实现(
JBoss
)
2.独立的
JTA
实现:如
JOTM
,
Atomikos
。
这些实现可以应用在那些不使用
J2EE
应用服务器的环境里用以提供分布事事务保证。如
Tomcat
,
Jetty
以及普通的
java
应用。
JTA
规范下载地址:
Java Transaction API
事务是编程中必不可少的一项内容,基于此,为了规范事务开发,
Java
增加了关于事务的规范,即
JTA
和
JTS
JTA
定义了一套接口,其中约定了几种主要的角色:
TransactionManager
、
UserTransaction
、
Transaction
、
XAResource
,并定义了这些角色之间需要遵守的规范,如
Transaction
的委托给
TransactionManager
等。
JTS
也是一组规范,上面提到
JTA
中需要角色之间的交互,那应该如何交互?
JTS
就是约定了交互细节的规范。
总体上来说
JTA
更多的是从框架的角度来约定程序角色的接口,而
JTS
则是从具体实现的角度来约定程序角色之间的接口,两者各司其职。
XA
协议。目前
MySQL
中只有
InnoDB
存储引擎支持
XA
协议。
2PC
即
Two-Phase Commit
,二阶段提交。
广泛应用在数据库领域,为了使得基于分布式架构的所有节点可以在进行事务处理时能够保持原子性和一致性。绝大部分关系型数据库,都是基于
2PC
完成分布式的事务处理。
顾名思义,
2PC
分为两个阶段处理,阶段一:提交事务请求、阶段二:执行事务提交;如果阶段一超时或者出现异常,
2PC
的阶段二:中断事务
Undo
和
Redo
操作计入本机事务日志;
Yes
,否则返回
No
。
协调者在阶段二决定是否最终执行事务提交操作。这一阶段包含两种情形:
执行事务提交
所有参与者
reply Yes
,那么执行事务提交。
Commit
请求;
Commit
请求后,会
正式执行事务提交操作
,并在完成提交操作之后,释放在整个事务执行期间占用的资源;
Ack
消息确认;
Ack
后,完成事务。
事情总会出现意外,当存在某一参与者向协调者发送
No
响应,或者等待超时。协调者只要无法收到所有参与者的
Yes
响应,就会中断事务。
Rollback
请求;
Undo
信息,执行
Rollback
操作。并在回滚结束后释放该事务所占用的系统资源;
Ack
消息;
Ack
消息后,完成事务中断。
顾名思义,两阶段提交在处理分布式事务时分为两个阶段:
voting
(投票阶段,有的地方会叫做
prepare
阶段)和
commit
阶段。
2pc
中存在两个角色,事务协调者(seata、atomikos、lcn)和事务参与者,事务参与者通常是指应用的数据库。
2PC
方案中,有一个事务管理器的角色,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复
ok
,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不
ok
,那么就回滚事务。
2PC
方案比较适合单体应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。
2PC
方案实际很少用,一般来说某个系统内部如果出现跨多个库的这么一个操作,是不合规的。我可以给大家介绍一下,现在微服务,一个大的系统分成几百个服务,几十个服务。一般来说,我们的规定和规范,是要求每个服务只能操作自己对应的一个数据库。
如果你要操作别的服务对应的库,不允许直连别的服务的库,违反微服务架构的规范,你随便交叉胡乱访问,几百个服务的话,全体乱套,这样的一套服务是没法管理的,没法治理的,可能会出现数据被别人改错,自己的库被别人写挂等情况。
如果你要操作别人的服务的库,你必须是通过调用别的服务的接口来实现,绝对不允许交叉访问别人的数据库。
优点主要体现在实现原理简单;
缺点比较多:
2PC
的提交在执行过程中,所有参与事务操作的逻辑都处于阻塞状态,也就是说,各个参与者都在等待其他参与者响应,无法进行其他操作;
Commit
请求后,发生局部网络异常或者协调者在尚未发送完
Commit
请求,即出现崩溃,最终导致只有部分参与者收到、执行请求。于是整个系统将会出现数据不一致的情形;
2PC
没有完善的容错机制,当参与者出现故障时,协调者无法快速得知这一失败,只能严格依赖超时设置来决定是否进一步的执行提交还是中断事务。
实际上分布式事务是一件非常复杂的事情,两阶段提交只是通过增加了事务协调者(Coordinator)的角色来通过
2
个阶段的处理流程来解决分布式系统中一个事务需要跨多个服务节点的数据一致性问题。但是从异常情况上考虑,这个流程也并不是那么的无懈可击。
假设如果在第二个阶段中
Coordinator
在接收到
Partcipant
的"Vote_Request"后挂掉了或者网络出现了异常,那么此时
Partcipant
节点就会一直处于本地事务挂起的状态,从而长时间地占用资源。当然这种情况只会出现在极端情况下,然而作为一套健壮的软件系统而言,异常
Case
的处理才是真正考验方案正确性的地方。
从流程上我们可以看得出,其最大缺点就在于它的执行过程中间,节点都处于阻塞状态。各个操作数据库的节点此时都占用着数据库资源,只有当所有节点准备完毕,事务协调者才会通知进行全局提交,参与者进行本地事务提交后才会释放资源。这样的过程会比较漫长,对性能影响比较大。
事务协调者是整个
XA
模型的核心,一旦事务协调者节点挂掉,会导致参与者收不到提交或回滚的通知,从而导致参与者节点始终处于事务无法完成的中间状态。
在第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就会导致节点间数据的不一致问题。
针对
2PC
的缺点,研究者提出了
3PC
,即
Three-Phase Commit
。作为
2PC
的改进版,
3PC
将原有的两阶段过程,重新划分为
CanCommit
、
PreCommit
和
doCommit
三个阶段。
canCommit
的请求,询问是否可以执行事务提交,并等待应答;
Yes
,否则返回
No
。
在本阶段,协调者会根据上一阶段的反馈情况来决定是否可以执行事务的
PreCommit
操作。有以下两种可能:
执行事务预提交
PreCommit
请求,并进入
prepared
阶段;
PreCommit
请求后,会执行事务操作,并将
Undo
和
Redo
日志写入本机事务日志;
Ack
响应形式发送给协调者,同事等待最终的
Commit
或
Abort
指令。
加入任意一个参与者向协调者发送
No
响应,或者等待超时,协调者在没有得到所有参与者响应时,即可以中断事务:
Abort
请求;
Abort
请求,还是等待协调者请求过程中出现超时,参与者都会中断事务;
在这个阶段,会真正的进行事务提交,同样存在两种可能。
Ack
响应,那么将从预提交转换到提交状态,并向所有参与者,发送
doCommit
请求;
doCommit
请求后,会正式执行事务提交操作,并在完成提交操作后释放占用资源;
Ack
消息;
Ack
消息后,完成事务。
在该阶段,假设正常状态的协调者接收到任一个参与者发送的
No
响应,或在超时时间内,仍旧没收到反馈消息,就会中断事务:
abort
请求;
abort
请求后,会利用阶段二中的
Undo
消息执行事务回滚,并在完成回滚后释放占用资源;
Ack
消息;
Ack
消息后,完成事务中断。
3PC
有效降低了
2PC
带来的参与者阻塞范围,并且能够在出现单点故障后继续达成一致;
但
3PC
带来了新的问题,在参与者收到
preCommit
消息后,如果网络出现分区,协调者和参与者无法进行后续的通信,这种情况下,参与者在等待超时后,依旧会执行事务提交,这样会导致数据的不一致。
三阶段提交协议在协调者和参与者中都引入
超时机制
,并且把两阶段提交协议的第一个阶段拆分成了两步:询问,然后再锁资源,最后真正提交。三阶段提交的三个阶段分别为:
can_commit
,
pre_commit
,
do_commit
。
在
doCommit
阶段,如果参与者无法及时接收到来自协调者的
doCommit
或者
abort
请求时,会在等待超时之后,继续进行事务的提交。(其实这个应该是基于概率来决定的,当进入第三阶段时,说明参与者在第二阶段已经收到了
PreCommit
请求,那么协调者产生
PreCommit
请求的前提条件是他在第二阶段开始之前,收到所有参与者的
CanCommit
响应都是
Yes
。(一旦参与者收到了
PreCommit
,意味他知道大家其实都同意修改了)所以,一句话概括就是,当进入第三阶段时,
由于网络超时等原因,虽然参与者没有收到
commit
或者
abort
响应,但是他有理由相信:成功提交的几率很大
)
相对于
2PC
,
3PC
主要解决的单点故障问题,并减少阻塞,
因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行
commit
。而不会一直持有事务资源并处于阻塞状态
。
但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的
abort
响应没有及时被参与者接收到,那么参与者在等待超时之后执行了
commit
操作。这样就和其他接到
abort
命令并执行回滚的参与者之间存在数据不一致的情况。
相比较
2PC
而言,
3PC
对于协调者(
Coordinator
)和参与者(
Partcipant
)都设置了超时时间,而
2PC
只有协调者才拥有超时机制。这解决了一个什么问题呢?
这个优化点,主要是避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地
commit
从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。
另外,通过
CanCommit
、
PreCommit
、
doCommit
三个阶段的设计,相较于
2PC
而言,多设置了一个缓冲阶段保证了在最后提交阶段之前各参与节点的状态是一致的。
以上就是
3PC
相对于
2PC
的一个提高(相对缓解了
2PC
中的前两个问题),但是
3PC
依然没有完全解决数据不一致的问题。假如在
doCommit
过程,参与者A无法接收协调者的通信,那么参与者A会自动提交,但是提交失败了,其他参与者成功了,此时数据就会不一致。
但是
XA
规范在
1994
年就出现了,至今没有大规模流行起来,必然有他一定的缺陷:
commit
或
callback
前都是阻塞等待的。
XA
协议比较简单,而且一旦商业数据库实现了
XA
协议,使用分布式事务的成本也比较低。但是,
XA
也有致命的缺点,那就是性能不理想,特别是在交易下单链路,往往并发量很高,
XA
无法满足高并发场景。
XA
目前在商业数据库支持的比较理想,在
MySQL
数据库中支持的不太理想,
MySQL
的
XA
实现,没有记录
prepare
阶段日志,主备切换回导致主库与备库数据不一致。许多
nosql
也没有支持
XA
,这让
XA
的应用场景变得非常狭隘。
其实也并非不用,例如在
IBM
大型机上基于
CICS
很多跨资源是基于
XA
协议实现的分布式事务,事实上
XA
也算分布式事务处理的规范了,但在为什么互联网中很少使用,究其原因有以下几个:
MySQL 5.7
之前都有缺陷);
J2EE
中间件(早期重量级
Weblogic
、
Jboss
、后期轻量级
Atomikos
、
Narayana
和
Bitronix
);
DBA
缺少这方面经验;
XA
协议;
准确讲
XA
是一个规范、协议,它只是定义了一系列的接口,只是目前大多数实现
XA
的都是数据库或者
MQ
,所以提起
XA
往往多指基于资源层的底层分布式事务解决方案。其实现在也有些数据分片框架或者中间件也支持
XA
协议,毕竟它的兼容性、普遍性更好。
在电商领域等互联网场景下,刚性事务在数据库性能和处理能力上都暴露出了瓶颈。
柔性事务有两个特性:基本可用和柔性状态。
柔性事务主要分为 补偿型 和 通知型 。补偿型事务都是同步的,通知型事务都是异步的。
补偿型事务:
TCC
、
Saga
;
通知型事务:
MQ
事务消息、最大努力通知型。
通知型事务的主流实现是通过
MQ
(消息队列)来通知其他事务参与者自己事务的执行状态,引入
MQ
组件,有效的将事务参与者进行解耦,各参与者都可以异步执行,所以通知型事务又被称为
异步事务
。
通知型事务主要适用于需要异步更新数据,并且对数据的实时性要求较低的场景,主要包含: 异步确保型事务 和 最大努力通知事务 两种。
指将一系列同步的事务操作修改为基于消息队列异步执行的操作,来避免分布式事务中同步阻塞带来的数据操作性能的下降。
基于
MQ
的事务消息方案主要依靠
MQ
的
半消息机制
来实现投递消息和参与者自身本地事务的一致性保障。半消息机制实现原理其实借鉴的
2PC
的思路,是二阶段提交的广义拓展。
半消息
:在原有队列消息执行后的逻辑,如果后面的本地逻辑出错,则不发送该消息,如果通过则告知
MQ
发送;
1、 流程
MQ
;
MQ
通知发送方消息发送成功;
commit
或者是
rollback
;
rollback
,
MQ
将丢弃该消息不投递;如果是
commit
,
MQ
将会消息发送给消息订阅方;
MQ
中将该消息标记为已消费;
MQ
服务器端将不停的询问
producer
来获取事务状态;
Consumer
端的消费成功机制有
MQ
保证;
2、 异步确保型事务使用示例
举个例子,假设存在业务规则:某笔订单成功后,为用户加一定的积分。在这条规则里,管理订单数据源的服务为事务发起方,管理积分数据源的服务为事务跟随者。从这个过程可以看到,基于消息队列实现的事务存在以下操作:
我们可以看到它的整体流程是比较简单的,同时业务开发工作量也不大:
可以看到该事务形态过程简单,性能消耗小,发起方与跟随方之间的流量峰谷可以使用队列填平,同时业务开发工作量也基本与单机事务没有差别,都不需要编写反向的业务逻辑过程。因此基于消息队列实现的事务是我们除了单机事务外最优先考虑使用的形态。
3、 基于RocketMQ实现MQ异步确保型事务
有一些第三方的
MQ
是支持事务消息的,这些消息队列,支持半消息机制,比如
RocketMQ
,
ActiveMQ
。但是有一些常用的
MQ
也不支持事务消息,比如
RabbitMQ
和
Kafka
都不支持。
以阿里的
RocketMQ
中间件为例,其思路大致为:
producer
(本例中指
A
系统)发送半消息到
broker
,这个半消息不是说消息内容不完整,它包含完整的消息内容,在
producer
端和普通消息的发送逻辑一致
broker
存储半消息,半消息存储逻辑与普通消息一致,只是属性有所不同,
topic
是固定的
RMQ_SYS_TRANS_HALF_TOPIC
,
queueId
也是固定为
0
,这个
tiopic
中的消息对消费者是不可见的,所以里面的消息永远不会被消费。这就保证了在半消息提交成功之前,消费者是消费不到这个半消息的
broker
端半消息存储成功并返回后,
A
系统执行本地事务,并根据本地事务的执行结果来决定半消息的提交状态为提交或者回滚
A
系统发送结束半消息的请求,并带上提交状态(提交
or
回滚)
broker
端收到请求后,首先从
RMQ_SYS_TRANS_HALF_TOPIC
的
queue
中查出该消息,设置为完成状态。如果消息状态为提交,则把半消息从
RMQ_SYS_TRANS_HALF_TOPIC
队列中复制到这个消息原始
topic
的
queue
中去(之后这条消息就能被正常消费了);如果消息状态为回滚,则什么也不做。
producer
发送的半消息结束请求是
oneway
的,也就是发送后就不管了,只靠这个是无法保证半消息一定被提交的,
RocketMQ
提供了一个兜底方案,这个方案叫消息反查机制,
Broker
启动时,会启动一个
TransactionalMessageCheckService
任务,该任务会定时从半消息队列中读出所有超时未完成的半消息,针对每条未完成的消息,
Broker
会给对应的
Producer
发送一个消息反查请求,根据反查结果来决定这个半消息是需要提交还是回滚,或者后面继续来反查
consumer
(本例中指
B
系统)消费消息,执行本地数据变更(至于
B
是否能消费成功,消费失败是否重试,这属于正常消息消费需要考虑的问题)
在
RocketMQ
中,不论是
producer
收到
broker
存储半消息成功返回后执行本地事务,还是
broker
向
producer
反查消息状态,都是通过
回调机制
完成,
producer
端的代码如下:
半消息发送时,会传入一个回调类
TransactionListener
,使用时必须实现其中的两个方法,
executeLocalTransaction
方法会在
broker
返回半消息存储成功后执行,我们会在其中执行本地事务;
checkLocalTransaction
方法会在
broker
向
producer
发起反查时执行,我们会在其中查询库表状态。两个方法的返回值都是消息状态,就是告诉
broker
应该提交或者回滚半消息
有时候我们目前的
MQ
组件并不支持事务消息,或者我们想尽量少的侵入业务方。这时我们需要另外一种方案“基于
DB
本地消息表“。本地消息表最初由
eBay
提出来解决分布式事务的问题。是目前业界使用的比较多的方案之一,它的核心思想就是将分布式事务
拆分
成本地事务进行处理。
1、 本地消息表流程
发送消息方:
MQ
消息队列。使用专门的投递工作线程进行事务消息投递到
MQ
,根据投递
ACK
去删除事务消息表记录
消息消费方:
生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。
2、 本地消息表优缺点:
IO
的,因此在高并发下是有性能瓶颈的
1、 二者的共性:
MQ
进行事务通知,所以都是异步的。
2、 二者的区别:
MQ
事务消息:
MQ
支持半消息机制或者类似特性,在重复投递上具有比较好的去重处理;
DB
本地消息表:
MQ
的要求,但是增加了存储成本;
最大努力通知方案的目标,就是发起通知方通过一定的机制,最大努力将业务处理结果通知到接收方。
最大努力通知型的最终一致性:
本质是通过引入 定期校验机制 实现最终一致性,对业务的侵入性较低,适合于对最终一致性敏感度比较低、业务链路较短的场景。
最大努力通知事务 主要用于 外部系统 ,因为外部的网络环境更加复杂和不可信,所以只能尽最大努力去通知实现数据最终一致性, 比如充值平台与运营商、支付对接、商户通知等等跨平台、跨企业的系统间业务交互场景 ;
而 异步确保型事务 主要适用于 内部系统 的数据最终一致性保障,因为内部相对比较可控,比如订单和购物车、收货与清算、支付与结算等等场景。
普通消息是无法解决本地事务执行和消息发送的一致性问题的。因为消息发送是一个网络通信的过程,发送消息的过程就有可能出现发送失败、或者超时的情况。超时有可能发送成功了,有可能发送失败了,消息的发送方是无法确定的,所以此时消息发送方无论是提交事务还是回滚事务,都有可能不一致性出现。
所以,通知型事务的难度在于: 投递消息和参与者本地事务的一致性保障 。
因为核心要点一致,都是为了保证消息的一致性投递 ,所以,最大努力通知事务在投递流程上跟异步确保型是一样的,因此也有 两个分支 :
MQ
自身的事务消息方案
DB
的本地事务消息表方案
要实现最大努力通知,可以采用
MQ
的
ACK
机制。最大努力通知事务在投递之前,跟异步确保型流程都差不多,关键在于投递后的处理。因为异步确保型在于内部的事务处理,所以
MQ
和系统是直连并且无需严格的权限、安全等方面的思路设计。最大努力通知事务在于第三方系统的对接,所以最大努力通知事务有几个特性:
要实现最大努力通知,可以采用定期检查本地消息表的机制。
发送消息方:
MQ
消息队列。使用专门的投递工作线程进行事务消息投递到
MQ
,根据投递
ACK
去删除事务消息表记录
最大努力通知事务在我认知中,其实是基于异步确保型事务发展而来适用于外部对接的一种业务实现。他们主要有的是业务差别,如下:
• 从参与者来说:最大努力通知事务适用于跨平台、跨企业的系统间业务交互;异步确保型事务更适用于同网络体系的内部服务交付。
• 从消息层面说:最大努力通知事务需要主动推送并提供多档次时间的重试机制来保证数据的通知;而异步确保型事务只需要消息消费者主动去消费。
• 从数据层面说:最大努力通知事务还需额外的定期校验机制对数据进行兜底,保证数据的最终一致性;而异步确保型事务只需保证消息的可靠投递即可,自身无需对数据进行兜底处理。
通知型事务,是无法解决本地事务执行和消息发送的一致性问题的。因为消息发送是一个网络通信的过程,发送消息的过程就有可能出现发送失败、或者超时的情况。超时有可能发送成功了,有可能发送失败了,消息的发送方是无法确定的,所以此时消息发送方无论是提交事务还是回滚事务,都有可能不一致性出现。
消息中间件在分布式系统中的核心作用就是异步通讯、应用解耦和并发缓冲(也叫作流量削峰)。在分布式环境下,需要通过网络进行通讯,就引入了数据传输的不确定性,也就是
CAP
理论中的分区容错性。
消息发送一致性是指 产生消息的业务动作与消息发送动作一致 ,也就是说如果业务操作成功,那么由这个业务操作所产生的消息一定要发送出去,否则就丢失。
常规MQ消息处理流程和特点
常规的
MQ
队列处理流程无法实现消息的一致性。所以,需要借助半消息、本地消息表,保障一致性。
对于未确认的消息,采用按规则重新投递的方式进行处理。
对于以上流程,消息重复发送会导致业务处理接口出现重复调用的问题。消息消费过程中消息重复发送的主要原因就是消费者成功接收处理完消息后,消息中间件没有及时更新投递状态导致的。如果允许消息重复发送,那么消费方应该实现业务接口的幂等性设计。
但是基于消息实现的事务并不能解决所有的业务场景,例如以下场景:某笔订单完成时,同时扣掉用户的现金。这里事务发起方是管理订单库的服务,但对整个事务是否提交并不能只由订单服务决定,因为还要确保用户有足够的钱,才能完成这笔交易,而这个信息在管理现金的服务里。这里我们可以引入基于补偿实现的事务,其流程如下:
以上这个是正常成功的流程,异常流程需要回滚的话,将额外发送远程调用到现金服务以加上之前扣掉的金额。以上流程比基于消息队列实现的事务的流程要复杂,同时开发的工作量也更多:
可以看到,该事务流程相对于基于消息实现的分布式事务更为复杂,需要额外开发相关的业务回滚方法,也失去了服务间流量削峰填谷的功能。但其仅仅只比基于消息的事务复杂多一点,若不能使用基于消息队列的最终一致性事务,那么可以优先考虑使用基于补偿的事务形态。
什么是补偿模式?
补偿模式使用一个额外的协调服务来协调各个需要保证一致性的业务服务,协调服务按顺序调用各个业务微服务,如果某个业务服务调用异常(包括业务异常和技术异常)就取消之前所有已经调用成功的业务服务。
补偿模式大致有
TCC
和
Saga
两种细分的方案。
TCC
(
Try-Confirm-Cancel
)的概念来源于
Pat Helland
发表的一篇名为“Life beyond Distributed Transactions:an Apostate’s Opinion”的论文。
TCC分布式事务模型包括三部分:
1.
主业务服务
:主业务服务为整个业务活动的发起方,服务的编排者,负责发起并完成整个业务活动。
2.
从业务服务
:从业务服务是整个业务活动的参与方,负责提供
TCC
业务操作,实现初步操作(
Try
)、确认操作(
Confirm
)、取消操作(
Cancel
)三个接口,供主业务服务调用。
3.
业务活动管理器
:业务活动管理器管理控制整个业务活动,包括记录维护
TCC
全局事务的事务状态和每个从业务服务的子事务状态,并在业务活动提交时调用所有从业务服务的
Confirm
操作,在业务活动取消时调用所有从业务服务的
Cancel
操作。
TCC
提出了一种新的事务模型,基于业务层面的事务定义,锁粒度完全由业务自己控制,目的是解决复杂业务中,跨表跨库等大颗粒度资源锁定的问题。
TCC
把事务运行过程分成
Try
、
Confirm/Cancel
两个阶段,每个阶段的逻辑由业务代码控制,避免了长事务,可以获取更高的性能。
TCC
(
Try-Confirm-Cancel
)分布式事务模型相对于
XA
等传统模型,其特征在于
它不依赖资源管理器(
RM
)对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务
。
TCC
模型认为对于业务系统中一个特定的业务逻辑,其对外提供服务时,必须接受一些不确定性,即对业务逻辑初步操作的调用仅是一个临时性操作,调用它的主业务服务保留了后续的取消权。如果主业务服务认为全局事务应该回滚,它会要求取消之前的临时性操作,这就对应从业务服务的取消操作。而当主业务服务认为全局事务应该提交时,它会放弃之前临时性操作的取消权,这对应从业务服务的确认操作。每一个初步操作,最终都会被确认或取消。因此,针对一个具体的业务服务,
TCC
分布式事务模型需要业务系统提供三段业务逻辑:
Try
:完成所有业务检查,预留必须的业务资源。
Confirm
:真正执行的业务逻辑,不作任何业务检查,只使用
Try
阶段预留的业务资源。因此,只要
Try
操作成功,
Confirm
必须能成功。另外,
Confirm
操作需满足幂等性,保证一笔分布式事务有且只能成功一次。
Cancel
:释放
Try
阶段预留的业务资源。同样的,
Cancel
操作也需要满足幂等性。
TCC分布式事务模型包括三部分:
Try
阶段:调用
Try
接口,尝试执行业务,完成所有业务检查,预留业务资源。
Confirm/Cancel
阶段:两者是互斥的,只能进入其中一个,并且都满足幂等性,允许失败重试。
Confirm
操作:对业务系统做确认提交,确认执行业务操作,不做其他业务检查,只使用
Try
阶段预留的业务资源。
Cancel
操作:在业务执行错误,需要回滚的状态下执行业务取消,释放预留资源。
Try
阶段失败可以
Cancel
,如果
Confirm
和
Cancel
阶段失败了怎么办?
TCC
中会添加事务日志,如果
Confirm
或者
Cancel
阶段出错,则会进行重试,所以这两个阶段需要支持幂等;如果重试失败,则需要人工介入进行恢复和处理等。
然而基于补偿的事务形态也并非能实现所有的需求,如以下场景:某笔订单完成时,同时扣掉用户的现金,但交易未完成,也未被取消时,不能让客户看到钱变少了。这时我们可以引入
TCC
,其流程如下:
以上是正常完成的流程,若为异常流程,则需要发送远程调用请求到现金服务,撤销冻结的金额。以上流程比基于补偿实现的事务的流程要复杂,同时开发的工作量也更多:
TCC
实际上是最为复杂的一种情况,其能处理所有的业务场景,但无论出于性能上的考虑,还是开发复杂度上的考虑,都应该尽量避免该类事务。
TCC
操作:
Try
阶段:尝试执行业务,完成所有业务的检查,实现一致性;预留必须的业务资源,实现准隔离性。
Confirm
阶段:真正的去执行业务,不做任何检查,仅适用
Try
阶段预留的业务资源,
Confirm
操作还要满足幂等性。
Cancel
阶段:取消执行业务,释放
Try
阶段预留的业务资源,
Cancel
操作要满足幂等性。
Do
阶段:真正的执行业务处理,业务处理结果外部可见。
Compensate
阶段:抵消或者部分撤销正向业务操作的业务结果,补偿操作满足幂等性。
约束:补偿操作在业务上可行,由于业务执行结果未隔离或者补偿不完整带来的风险与成本可控。实际上,
TCC
的
Confirm
和
Cancel
操作可以看做是补偿操作。
比较一下
TCC
事务模型和
DTP
事务模型,如下所示:
这两张图看起来差别较大,实际上很多地方是类似的!
1、
TCC
模型中的主业务服务相当于
DTP
模型中的
AP
,
TCC
模型中的从业务服务相当于
DTP
模型中的
RM
DTP
模型中,应用
AP
操作多个资源管理器RM上的资源;而在
TCC
模型中,是主业务服务操作多个从业务服务上的资源。例如航班预定案例中,美团
App
就是主业务服务,而川航和东航就是从业务服务,主业务服务需要使用从业务服务上的机票资源。不同的是
DTP
模型中的资源提供者是类似于
MySQL
这种关系型数据库,而
TCC
模型中资源的提供者是其他业务服务。
2、
TCC
模型中,从业务服务提供的
try
、
confirm
、
cancel
接口相当于
DTP
模型中
RM
提供的
prepare
、
commit
、
rollback
接口
XA
协议中规定了
DTP
模型中定RM需要提供
prepare
、
commit
、
rollback
接口给
TM
调用,以实现两阶段提交。
TCC
模型中,从业务服务相当于
RM
,提供了类似的
try
、
confirm
、
cancel
接口。
3、事务管理器
DTP
模型和
TCC
模型中都有一个事务管理器。不同的是:
DTP
模型中,阶段
1
的(
prepare
)和阶段
2
的(
commit
、
rollback
),都是由
TM
进行调用的。
TCC
模型中,阶段
1
的
try
接口是主业务服务调用(绿色箭头),阶段
2
的(
confirm
、
cancel
接口)是事务管理器
TM
调用(红色箭头)。这就是
TCC
分布式事务模型的二阶段异步化功能,从业务服务的第一阶段执行成功,主业务服务就可以提交完成,然后再由事务管理器框架异步的执行各从业务服务的第二阶段。这里牺牲了一定的隔离性和一致性的,但是提高了长事务的可用性。
TCC
与
2PC
(两阶段提交)协议的区别:
TCC
位于业务服务层而不是资源层,
TCC
没有单独准备阶段,
Try
操作兼备资源操作与准备的能力,
TCC
中
Try
操作可以灵活的选择业务资源,锁定粒度。
TCC
的开发成本比
2PC
高。实际上
TCC
也属于两阶段操作,但是
TCC
不等同于
2PC
操作。
T
就是
Try
,两个
C
分别是
Confirm
和`Cancel。
Try
就是尝试,请求链路中每个参与者依次执行
Try
逻辑,如果都成功,就再执行
Confirm
逻辑,如果有失败,就执行
Cancel
逻辑。
TCC
与
XA
两阶段提交有着异曲同工之妙,下图列出了二者之间的对比
XA
中,各个
RM
准备提交各自的事务分支,事实上就是准备提交资源的更新操作(
insert
、
delete
、
update
等);
TCC
中,是主业务活动请求(
try
)各个从业务服务预留资源。
XA
根据第一阶段每个
RM
是否都
prepare
成功,判断是要提交还是回滚。如果都
prepare
成功,那么就
commit
每个事务分支,反之则
rollback
每个事务分支。
TCC
中,如果在第一阶段所有业务资源都预留成功,那么
confirm
各个从业务服务,否则取消(
cancel
)所有从业务服务的资源预留请求。
XA
是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。基于数据库锁实现,需要数据库支持
XA
协议,由于在执行事务的全程都需要对相关数据加锁,一般高并发性能会比较差
TCC
是业务层面的分布式事务,最终一致性,不会一直持有资源的锁,性能较好。但是对微服务的侵入性强,微服务的每个事务都必须实现
try
、
confirm
、
cancel
等
3
个方法,开发成本高,今后维护改造的成本也高为了达到事务的一致性要求,
try
、
confirm
、
cancel
接口必须实现幂等性操作由于事务管理器要记录事务日志,必定会损耗一定的性能,并使得整个
TCC
事务时间拉长
TCC
它会弱化每个步骤中对于资源的锁定,以达到一个能承受高并发的目的(基于最终一致性)。
具体的说明如下:
XA
是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。
XA
事务中的两阶段提交内部过程是对开发者屏蔽的,开发者从代码层面是感知不到这个过程的。而事务管理器在两阶段提交过程中,从
prepare
到
commit/rollback
过程中,资源实际上一直都是被加锁的。如果有其他人需要更新这两条记录,那么就必须等待锁释放。
TCC
是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。
TCC
中的两阶段提交并没有对开发者完全屏蔽,也就是说从代码层面,开发者是可以感受到两阶段提交的存在。
try
、
confirm/cancel
在执行过程中,一般都会开启各自的本地事务,来保证方法内部业务逻辑的
ACID
特性。其中:
1、
try
过程的本地事务,是保证资源预留的业务逻辑的正确性。
2、
confirm/cancel
执行的本地事务逻辑确认/取消预留资源,以保证最终一致性,也就是所谓的补偿型事务(
Compensation-Based Transactions
)。由于是多个独立的本地事务,因此不会对资源一直加锁。
另外,这里提到
confirm/cancel
执行的本地事务是
补偿性事务:
补偿是一个独立的支持
ACID
特性的本地事务,用于在逻辑上取消服务提供者上一个
ACID
事务造成的影响,对于一个长事务(
long-running transaction
),与其实现一个巨大的分布式
ACID
事务,不如使用基于补偿性的方案,把每一次服务调用当做一个较短的本地
ACID
事务来处理,执行完就立即提交
TCC
是可以解决部分场景下的分布式事务的,但是,它的一个问题在于,需要每个参与者都分别实现
Try
,
Confirm
和
Cancel
接口及逻辑,这对于业务的侵入性是巨大的。
TCC
方案严重依赖回滚和补偿代码,最终的结果是:回滚代码逻辑复杂,业务代码很难维护。所以,
TCC
方案的使用场景较少,但是也有使用的场景。
比如说跟钱打交道的,支付、交易相关的场景,大家会用
TCC
方案,严格保
证分布式事务要么全部成功,要么全部自动回滚
,严格保证资金的正确性,保证在资金上不会出现问题。
SAGA
可以看做一个异步的、利用队列实现的补偿事务。
1987
年普林斯顿大学的
Hector Garcia-Molina
和
Kenneth Salem
发表了一篇
Paper Sagas
,讲述的是如何处理
long lived transaction
(长活事务)。
Saga
是一个长活事务可被分解成可以交错运行的子事务集合。其中每个子事务都是一个保持数据库一致性的真实事务。
论文地址: sagas
Saga
模型是把一个分布式事务拆分为多个本地事务,每个本地事务都有相应的执行模块和补偿模块(对应
TCC
中的
Confirm
和
Cancel
),当
Saga
事务中任意一个本地事务出错时,可以通过调用相关的补偿方法恢复之前的事务,达到事务最终一致性。
这样的
SAGA
事务模型,是牺牲了一定的隔离性和一致性的,但是提高了
long-running
事务的可用性。
Saga模型由三部分组成 :
LLT = T1+T2+T3+...+Ti
。
Ti
有对应的补偿
Ci
。
Saga的执行顺序有两种:
Backward Recovery
):撤销掉之前所有成功子事务。如果任意本地子事务失败,则补偿已完成的事务。如异常情况的执行顺序
T1,T2,T3,..Ti,Ci,...C3,C2,C1
。
Forward Recovery
):即重试失败的事务,适用于必须要成功的场景,该情况下不需要
Ci
。执行顺序:
T1,T2,...,Tj(失败),Tj(重试),...,Ti
。
显然,向前恢复没有必要提供补偿事务,如果你的业务中,子事务(最终)总会成功,或补偿事务难以定义或不可能,向前恢复更符合你的需求。
理论上补偿事务永不失败,然而,在分布式世界中,服务器可能会宕机,网络可能会失败,甚至数据中心也可能会停电。在这种情况下我们能做些什么?最后的手段是提供回退措施,比如人工干预。
Saga
看起来很有希望满足我们的需求。所有长活事务都可以这样做吗?这里有一些限制:
Saga
只允许
两个层次的嵌套
,顶级的
Saga
和简单子事务
sagas
可能会看到其他
sagas
的部分结果
补偿也有需考虑的事项:
Ti
的行为,但未必能将数据库返回到执行
Ti
时的状态。(例如,如果事务触发导弹发射,则可能无法撤消此操作)
但这对我们的业务来说不是问题。其实难以撤消的行为也有可能被补偿。例如,发送电邮的事务可以通过发送解释问题的另一封电邮来补偿。
Saga
对于
ACID
的保证和
TCC
一样:
Atomicity
):正常情况下保证。
Consistency
),在某个时间点,会出现
A
库和
B
库的数据违反一致性要求的情况,但是最终是一致的。
Isolation
),在某个时间点,
A
事务能够读到
B
事务部分提交的结果。
Durability
),和本地事务一样,只要
commit
则数据被持久。
Saga
不提供
ACID
保证,因为原子性和隔离性不能得到满足。原论文描述如下:
full atomicity is not provided. That is, sagas may view the partial results of other sagas
通过
saga log
,
saga
可以保证一致性和持久性。
SAGA
模型的核心思想是,通过某种方案,将分布式事务转化为本地事务,从而降低问题的复杂性。
比如以
DB
和
MQ
的场景为例,业务逻辑如下:
DB
中插入一条数据。
MQ
中发送一条消息。
由于上述逻辑中,对应了两种存储端,即
DB
和
MQ
,所以,简单的通过本地事务是无法解决的。那么,依照
SAGA
模型,可以有两种解决方案。
方案一:半消息模式
RocketMQ
新版本中,就支持了这种模式。
首先,什么是半消息。简单来说,就是在消息上加了一个状态。当发送者第一次将消息放入
MQ
后,该消息为待确认状态。该状态下,该消息是不能被消费者消费的。发送者必须二次和
MQ
进行交互,将消息从待确认状态变更为确认状态后,消息才能被消费者消费。待确认状态的消息,就称之为半消息。
半消息的完整事务逻辑如下:
MQ
发送半消息。
DB
插入数据。
MQ
发送确认消息。
通过半消息的形式,将
DB
的操作夹在了两个
MQ
操作的中间。假设,第
2
步失败了,那么,
MQ
中的消息就会一直是半消息状态,也就不会被消费者消费。
那么,半消息就一直存在于
MQ
中吗?或者是说如果第
3
步失败了呢?
为了解决上面的问题,
MQ
引入了一个扫描的机制。即
MQ
会每隔一段时间,对所有的半消息进行扫描,并就扫描到的存在时间过长的半消息,向发送者进行询问,询问如果得到确认回复,则将消息改为确认状态,如得到失败回复,则将消息删除。
如上,半消息机制的一个问题是:要求业务方提供查询消息状态接口,对业务方依然有较大的侵入性。
方案二:本地消息表
在
DB
中,新增一个消息表,用于存放消息。如下:
DB
业务表中插入数据。
DB
消息表中插入数据。
MQ
,收到
ack
后,删除消息表中的消息。
如上,通过上述逻辑,将一个分布式的事务,拆分成两大步。第
1
和第
2
,构成了一个本地的事务,从而解决了分布式事务的问题。
这种解决方案,不需要业务端提供消息查询接口,只需要稍微修改业务逻辑,侵入性是最小的。
SAGA
适用于无需马上返回业务发起方最终状态的场景,例如:你的请求已提交,请稍后查询或留意通知之类。
将上述补偿事务的场景用
SAGA
改写,其流程如下:
以上为成功的流程,若现金服务扣除金额失败,那么,最后一步订单服务将会更新订单状态为失败。
其业务编码工作量比补偿事务多一点,包括以下内容:
但其相对于补偿事务形态有性能上的优势,所有的本地子事务执行过程中,都无需等待其调用的子事务执行,减少了加锁的时间,这在事务流程较多较长的业务中性能优势更为明显。同时,其利用队列进行进行通讯,具有削峰填谷的作用。
因此该形式适用于不需要同步返回发起方执行最终结果、可以进行补偿、对性能要求较高、不介意额外编码的业务场景。
但当然
SAGA
也可以进行稍微改造,变成与
TCC
类似、可以进行资源预留的形态
Saga
相比
TCC
的缺点是缺少预留动作,导致补偿动作的实现比较麻烦:
Ti
就是
commit
,比如一个业务是发送邮件,在
TCC
模式下,先保存草稿(
Try
)再发送(
Confirm
),撤销的话直接删除草稿(
Cancel)
就行了。而
Saga
则就直接发送邮件了(
Ti
),如果要撤销则得再发送一份邮件说明撤销(
Ci
),实现起来有一些麻烦。
如果把上面的发邮件的例子换成:
A
服务在完成
Ti
后立即发送
Event
到
ESB
(企业服务总线,可以认为是一个消息中间件),下游服务监听到这个
Event
做自己的一些工作然后再发送
Event
到
ESB
,如果
A
服务执行补偿动作
Ci
,那么整个补偿动作的层级就很深。
不过没有预留动作也可以认为是优点:
TCC
需要修改原来的业务逻辑,而
Saga
只需要添加一个补偿动作就行了。
TCC
最少通信次数为
2n
,而
Saga
为
n
(
n=sub-transaction
的数量)。
Try
接口,
TCC
模式实现起来就比较
tricky
了,而
Saga
则很简单。
Saga对比TCC
Saga
和
TCC
都是补偿型事务,他们的区别为:
Saga
对业务侵入较小,只需要提供一个逆向操作的
Cancel
即可;而
TCC
需要对业务进行全局性的流程改造;
Seata
(曾用名Fescar,开源版本GTS)是阿里巴巴和蚂蚁金服联合打造的分布式事务框架。其
AT
事务的目标是让开发者像使用本地事务一样使用分布式事务。
Seata
支持
XA
、
TCC
、
Saga
模式,但支持的主要方式是
AT
模式。
Seata AT
模式是增强型
2PC
模式。
AT
模式:两阶段提交协议的演变,没有一直锁表
具体请参考 分布式事务Seata
TX-LCN 5.0
以后由于框架兼容了
LCN(2PC)、TCC、TXC
三种事务模式,为了区分
LCN
模式,特此将
LCN
分布式事务改名为
TX-LCN
分布式事务框架。
TX-LCN
定位于一款事务协调性框架,框架其本身并不生产事务,而是本地事务的协调者,从而达到事务一致性的效果。
TX-LCN
主要有两个模块,
Tx-Client
(
TC
),
Tx-Manager
(
TM
).
TM
(
Tx-Manager
):是独立的服务,是分布式事务的控制方,协调分布式事务的提交,回滚
TC
(
Tx-Client
):由业务系统集成,事务发起方、参与方都由
TxClient
端来控制
ShardingSphere
,
官方文档
,
github
ShardingSphere
是一套开源的分布式数据库解决方案组成的生态圈,它由
JDBC
、
Proxy
和
Sidecar
(规划中)这
3
款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的数据水平扩展、分布式事务和分布式治理等功能,可适用于如
Java
同构、异构语言、云原生等各种多样化的应用场景。
ShardingSphere 5.x
版本开始致力于可插拔架构,项目的功能组件能够灵活的以可插拔的方式进行扩展。目前,数据分片、读写分离、数据加密、影子库压测等功能,以及
MySQL
、
PostgreSQL
、
SQLServer
、
Oracle
等
SQL
与协议的支持,均通过插件的方式织入项目。 开发者能够像使用积木一样定制属于自己的独特系统。
ShardingSphere
目前已提供数十个
SPI
作为系统的扩展点,仍在不断增加中。
ShardingSphere
已于2020年4月16日成为
Apache
软件基金会的顶级项目。
ShardingSphere
通过整合常用的几个事务开源实现,如
Atomkkos
、
Narayana
,为本地事务、两阶段事务和柔性事务提供统一的分布式事务接口,并弥补当前方案的不足,提供一站式的分布式事务解决方案是
ShardingSphere
的设计目标。
使用实例: https://gitee.com/mmcLine/spring-cloud-transaction
Atomikos
,
官方文档
其旗下最著名的产品就是事务管理器。产品分两个版本:
TransactionEssentials
:开源的免费产品
ExtremeTransactions
:商业版,需要收费
这两个产品的关系如下图所示:
可以看到,在开源版本中支持
JTA/XA
、
JDBC
、
JMS
的事务。
atomikos也支持与spring事务整合
。
spring
事务管理器的顶级抽象是
PlatformTransactionManager
接口,其提供了个重要的实现类:
显然,这里配置的是
JTATransactionManager
。
单体项目多数据源可以使用
JTA+ Atomikos
。
Hmily
一款金融级的分布式事务解决方案,支持
Dubbo
、
Spring Cloud
、
Motan
,
GRPC
,
BRCP
等
RPC
框架进行分布式事务。
Spring-Boot
,
Spring-Namespace
快速与业务系统集成。
Metrics
多项指标性能监控,以及
admin
管理后台UI展示。
RPC
: 支持
Dubbo
,
SpringCloud
,
Montan
,
brpc
,
tars
等知名RPC框架。
mysql
,
oracle
,
mongodb
,
redis
,
zookeeper
等方式。
RPC
嵌套调用事务
EasyTransaction
,
Github
。
EasyTransaction
(简称
ET
)的目标也是构建出一个全面分布式事务解决方案,到目前为止其包含
TCC
、
Saga
事务、可靠消息、补偿等多种形态。可一站式解决分布式
SOA
(包括微服务等)的事务问题。不过该项目目前已停止维护。
消息队列
RocketMQ
版提供类似
XA
或
Open XA
的分布式事务功能,通过消息队列
RocketMQ
版事务消息,能达到分布式事务的最终一致。
具体请参考: 消息队列RocketMQ应用
tcc-transaction 、 ByteTCC 、 spring-cloud-rest-tcc
总体的方案对比
异步确保型事务 尽最大努力通知
![]() |
正直的桔子 · iphone怎么导入课表- 抖音 6 月前 |
![]() |
强健的日记本 · 北华大学历史文化学院_百度百科 1 年前 |
![]() |
温柔的手套 · 如何评价美剧《曼达洛人》第二季? - 知乎 1 年前 |
![]() |
稳重的佛珠 · AI升级版无间道_人工智能 1 年前 |