小白学习java分布式笔记

 

事务:事务是由⼀组操作构成的可靠的ᇿ⽴的⼯作单元,事务具备
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 表示分区容
错性,能够容忍⽹络中断等故障。
分布式事务与分布式锁的区别:
分布式锁解决的是分布式资源抢占的问题;分布式事务和本地事务是
解决流程化提交问题。
事务简介
事务 (Transaction) 是操作数据库中某个数据项的⼀个程序执⾏单元 (unit)
事务应该具有 4 个属性:原⼦性、⼀致性、隔离性、持久性。这四个属性通
常称为 ACID 特性。 事务的四个特征:
1 Atomic 原⼦性
事务必须是⼀个原⼦的操作序列单元,事务中包含的各项操作在⼀次执⾏
过程中,要么全部执⾏成功,要么全部不执⾏,任何⼀项失败,整个事务
回滚,只有全部都执⾏成功,整个事务才算成功。
2 Consistency ⼀致性
事务的执⾏不能破坏数据库数据的完整性和⼀致性,事务在执⾏之前和之
后,数据库都必须处于⼀致性状态。
3 Isolation 隔离性
在并发环境中,并发的事务是相互隔离的,⼀个事务的执⾏不能被其他事
务⼲扰。即不同的事务并发操纵相同的数据时,每个事务都有各⾃完整的
数据空间,即⼀个事务内部的操作及使⽤的数据对其他并发事务是隔离
的,并发执⾏的各个事务之间不能相互⼲扰。
SQL 中的 4 个事务隔离级别:
1 )读未提交 允许脏读。如果⼀个事务正在处理某⼀数据,并对其进⾏了更新,但同时尚未
成事务,因此事务没有提交,与此同时,允许另⼀个事务也能够访问该数

据。例如 n从 0 累加到 10 才提交事务,此时 B 可能读到 n 变量从 0 10 
间有中间值。
2 )读已提交
允许不 可重复读。只允许读到已经提交的数据。即事务A在将 n 0 累加到 10
过程中, B ⽆法看到 n 的中间值,之中只能看到 10 。同时有事务 C 进⾏从 10
20 的累加,此时 B 在同⼀个事务内再次读时,读到的是 20
3 )可重复读
允许幻读。保证在事务处理过程中,多次读取同⼀个数据时,其值都和事务开
始时刻时是⼀致的。禁⽌脏读、不可 重复读。幻读即同样的事务操作,在前后

两个时间段内执⾏对同⼀个数据项的读取,可能出现不⼀致的结果。保证 B
同⼀个事务内,多次读取 n 的值,读到的都是初始值 0 。幻读,就是不同事
务,读到的 n 的数据可能是 0 ,可能 10 ,可能是 20
4 )串⾏化 最严格的事
务,要求所有事务被串⾏执⾏,不能并发执⾏。

如果不对事务进⾏并发控制,我们看看数据库并发操作是会有那些异常情
1 )⼀类丢失更新:两个事物读同⼀数据,⼀个修改字段 1 ,⼀个修改
字段 2 ,后提交的恢复了先提交修改的字段。
2 )⼆类丢失更新:两个事物读同⼀数据,都修改同⼀字段,后提交
的覆盖了先提交的修改。
3 )脏读:读到了未提交的值,万⼀该事物回滚,则产⽣脏读。
4 )不可重复读:两个查询之间,被另外⼀个事务修改了数据的内
容,产⽣内容的不⼀致。
5 )幻读:两个查询之间,被另外⼀个事务插⼊或删除了记录,产⽣
结果集的不⼀致。
4 Durability 持久性
持久性( durability ):持久性也称永久性( permanence ),指⼀个事务⼀
旦提交,它对数据库中对应数据的状态变更就应该是永久性的。
即使发⽣系统崩溃或机器宕机,只要数据库能够重新启动,那么⼀定能够
将其恢复到事务成功结束时的状态。
⽐⽅说:⼀个⼈买东⻄的时候需要记录在账本上,即使⽼板忘记了那
也有据可查。
MySQL 数据库的事务实现原理
MySQL InnoDB InnoDB MySQL 的⼀个存储引擎)为例,介绍⼀
下单⼀数据库的事务实现原理。
InnoDB 是通过 ⽇志和锁 来保证的事务的 ACID 特性,具体如下:
1 )通过数据库锁的机制,保障事务的隔离性;
2 )通过 Redo Log (重做⽇志)来,保障事务的持久性;
3 )通过 Undo Log (撤销⽇志)来,保障事务的原⼦性;
4 )通过 Undo Log (撤销⽇志)来,保障事务的⼀致性;
Undo Log 如何保障事务的原⼦性呢?
具体的⽅式为:在操作任何数据之前,⾸先将数据备份到⼀个地⽅(这个
存储数据备份的地⽅称为 Undo Log ),然后进⾏数据的修改。如果出现了
错误或者⽤户执⾏了 Rollback 语句,系统可以利⽤ Undo Log 中的备份将
数据恢复到事务开始之前的状态。 Redo Log 如何保障事务的持久性呢?
具体的⽅式为: Redo Log 记录的是新数据的备份(和 Undo Log 相反)。
在事务提交前,只要将 Redo Log 持久化即可,不需要将数据持久化。当
系统崩溃时,虽然数据没有持久化,但是 Redo Log 已经持久化。系统可
以根据 Redo Log 的内容,将所有数据恢复到崩溃之前的状态。
脏读、幻读、不可重复读
在多个事务并发操作时,数据库中会出现下⾯三种问题: 脏读,幻读,不
可重复读
脏读( Dirty Read
事务 A 读到了事务 B 还未提交的数据:
事务 A 读取的数据,事务 B 对该数据进⾏修改还未提交数据之前,事务 A
次读取数据会读到事务 B 已经修改后的数据,如果此时事务 B 进⾏回滚或再
次修改该数据然后提交,事务 A 读到的数据就是脏数据,这个情况被称为脏
读( Dirty Read )。

幻读和不可重复度的区别:
幻读 :在同⼀事务中,相同条件下,两次查询出来的 记录数 不⼀ 样;
不可重复读 :在同⼀事务中,相同条件下,两次查询出来的 数据 不⼀样;
事务的隔离级别
为了解决数据库中事务并发所产⽣的问题,在标准 SQL 规范中,定义了四
种事务隔离级别,每⼀种级别都规定了⼀个事务中所做的修改,哪些在事
务内和事务间是可⻅的,哪些是不可⻅的。 低级别的隔离级⼀般⽀持更⾼的并发处理,并拥有更低的系统开销。
-- 查看系统隔离级别:
SELECT @@global.transaction_isolation;
事务的四个隔离级别:
未提交读( READ UNCOMMITTED :所有事务都可以看到其他事务
未提交的修改。⼀般很少使⽤; 提交读( READ COMMITTED Oracle 默认隔离级别,事务之间只能
看到彼此已提交的变更修改;
可重复读( REPEATABLE READ MySQL 默认隔离级别,同⼀事务
中的多次查询会看到相同的数据⾏;可以解决不可重复读,但可能出现
幻读;
可串⾏化( SERIALIZABLE :最⾼的隔离级别,事务串⾏的执⾏,
前⼀个事务执⾏完,后⾯的事务会执⾏。读取每条数据都会加锁,会导
致⼤量的超时和锁争⽤问题;
-- 设置当前会话事务隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 设置全局事务隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
分布式事务的基本概念
分布式环境的事务复杂性
当本地事务要扩展到分布式时,它的复杂性进⼀步增加了。
存储端的多样性。 ⾸先就是存储端的多样性。本地事务的情况下,所有数据都会落到同⼀个
DB 中,但是,在分布式的情况下,就会出现数据可能要落到多个 DB ,或
者还会落到 Redis ,落到 MQ 等中。
存储端多样性 , 如下图所示:

事务链路的延展性
本地事务的情况下,通常所有事务相关的业务操作,会被我们封装到⼀个
Service ⽅法中。⽽在分布式的情况下,请求链路被延展,拉⻓,⼀个操作
会被拆分成多个服务,它们呈现线状或⽹状,依靠⽹络通信构建成⼀个整
体。在这种情况下,事务⽆疑变得更复杂。
事务链路延展性 , 如下图所示:
 
基于上述两个复杂性,期望有⼀个统⼀的分布式事务⽅案,能够像本地事
务⼀样,以⼏乎⽆侵⼊的⽅式,满⾜各种存储介质,各种复杂链路,是不
现实的。 ⾄少,在当前,还没有⼀个⼗分成熟的解决⽅案。所以,⼀般情况下,在
分布式下,事务会被拆分解决,并根据不同的情况,采⽤不同的解决⽅案。
什么是分布式事务?
对于分布式系统⽽⾔,需要保证分布式系统中的数据⼀致性,保证数据在
⼦系统中始终保持⼀致,避免业务出现问题。分布式系统中对数要么⼀起
成功,要么⼀起失败,必须是⼀个整体性的事务。
分布式事务指事务的参与者、⽀持事务的服务器、资源服务器以及事务管
理器分别位于不同的分布式系统的不同节点之上。
简单的说,在分布式系统上⼀次⼤的操作由不同的⼩操作组成,这些⼩的
操作分布在不同的服务节点上,且属于不同的应⽤,分布式事务需要保证
这些⼩操作要么全部成功,要么全部失败。
举个例⼦:在电商⽹站中,⽤户对商品进⾏下单,需要在订单表中创建⼀ 条订单数据,同时需要在库存表中修改当前商品的剩余库存数量,两步操
作⼀个添加,⼀个修改,我们⼀定要保证这两步操作⼀定同时操作成功或
失败,否则业务就会出现问题。
任何事务机制在实现时,都应该考虑事务的 ACID 特性,包括:本地事务、
分布式事务。对于分布式事务⽽⾔,即使不能都很好的满⾜,也要考虑⽀
持到什么程度。
典型的分布式事务场景:
1. 跨库事务
跨库事务指的是,⼀个应⽤某个功能需要操作多个库,不同的库中存储不
同的业务数据。笔者⻅过⼀个相对⽐较复杂的业务,⼀个业务中同时操作
9 个库。 下图演示了⼀个服务同时操作2 个库的情况:

 

 
CAP 定理
分布式事务的理论基础
数据库事务 ACID 四⼤特性,⽆法满⾜分布式事务的实际需求,这个时候⼜
有⼀些新的⼤⽜提出⼀些新的理论。
CAP 定理
CAP 定理是由加州⼤学伯克利分校 Eric Brewer 教授提出来的,他指出 WEB
服务⽆法同时满⾜⼀下 3 个属性:
⼀致性 (Consistency) : 客户端知道⼀系列的操作都会同时发⽣ ( ⽣效 )
可⽤性 (Availability) : 每个操作都必须以可预期的响应结束
分区容错性 (Partition tolerance) : 即使出现单个组件⽆法可⽤,操作
依然可以完成具体地讲在分布式系统中,⼀个Web 应⽤⾄多只能同时⽀持上⾯的两个属
性。因此,设计⼈员必须在⼀致性与可⽤性之间做出选择。
2000 7 Eric Brewer 教授仅仅提出来的是⼀个猜想, 2 年后,麻省理⼯学
院的 Seth Gilbert Nancy Lynch 从理论上证明了 CAP 理论,并且⽽⼀个分
布式系统最多只能满⾜ CAP 中的 2 项。之后, CAP 理论正式成为分布式计算
领域的公认定理。

所以, CAP 定理在迄今为⽌的分布式系统中都是适⽤的!
CAP 的⼀致性、可⽤性、分区容错性 具体如下:
1 、⼀致性
数据⼀致性指 “all nodes see the same data at the same time” ,即更新操
作成功并返回客户端完成后,所有节点在同⼀时间的数据完全⼀致,不能
存在中间状态。 分布式环境中,⼀致性是指多个副本之间能否保持⼀致的特性。在⼀致性
的需求下,当⼀个系统在数据⼀致的状态下执⾏更新操作后,应该保证系
统的数据仍然处理⼀致的状态。
例如对于电商系统⽤户下单操作,库存减少、⽤户资⾦账户扣减、积分增
加等操作必须在⽤户下单操作完成后必须是⼀致的。不能出现类似于库存
已经减少,⽽⽤户资⾦账户尚未扣减,积分也未增加的情况。如果出现了
这种情况,那么就认为是不⼀致的。 数据⼀致性分为强⼀致性、弱⼀致性、最终⼀致性
如果的确能像上⾯描述的那样时刻保证客户端看到的数据都是⼀致的,
那么称之为强⼀致性。 如果允许存在中间状态,只要求经过⼀段时间后,数据最终是⼀致的,
则称之为最终⼀致性。 此外,如果允许存在部分数据不⼀致,那么就称之为弱⼀致性。

2 、可⽤性
系统提供的服务必须⼀直处于可⽤的状态,对于⽤户的每⼀个操作请求总
是能够在有限的时间内返回结果。
两个度量的维度:
1 )有限时间内
对于⽤户的⼀个操作请求,系统必须能够在指定的时间(响应时间)内返
回对应的处理结果, 如果超过了这个时间范围,那么系统就被认为是不可
⽤的 。即这个响应时间必须在⼀个合理的值内,不让⽤户感到失望。
试想,如果⼀个下单操作,为了保证分布式事务的⼀致性,需要 10 分钟才
能处理完,那么⽤户显然是⽆法忍受的。 2 )返回正常结果
要求系统在完成对⽤户请求的处理后,返回⼀个正常的响应结果。正常的
响应结果通常能够明确地反映出对请求的处理结果,即成功或失败,⽽不
是⼀个让⽤户感到困惑的返回结果。⽐如返回⼀个系统错误如
OutOfMemory ,则认为系统是不可⽤的。
返回结果 是可⽤性的另⼀个⾮常重要的指标,它要求系统在完成对⽤户请
求的处理后,返回⼀个正常的响应结果,不论这个结果是成功还是失败。
3 、分区容错性
即分布式系统在遇到任何⽹络分区故障时,仍然需要能够保证对外提供满
⾜⼀致性和可⽤性的服务,除⾮是整个⽹络环境都发⽣了故障。
⽹络分区,是指分布式系统中,不同的节点分布在不同的⼦⽹络(机房 /
地⽹络)中,由于⼀些特殊的原因导致这些⼦⽹络之间出现⽹络不连通的
状态,但各个⼦⽹络的内部⽹络是正常的,从⽽导致整个系统的⽹络环境
被切分成了若⼲孤⽴的区域。组成⼀个分布式系统的每个节点的加⼊与退
出都可以看做是⼀个特殊的⽹络分区。
CAP 的应⽤
1 、放弃 P
放弃分区容错性的话,则放弃了分布式,放弃了系统的可扩展性 2 、放弃 A
放弃可⽤性的话,则在遇到⽹络分区或其他故障时,受影响的服务需要等
待⼀定的时间,再此期间⽆法对外提供政策的服务,即不可⽤
3 、放弃 C
放弃⼀致性的话(这⾥指强⼀致),则系统⽆法保证数据保持实时的⼀致
性,在数据达到最终⼀致性时,有个时间窗⼝,在时间窗⼝内,数据是不
⼀致的。
对于分布式系统来说, P 是不能放弃的,因此架构师通常是在可⽤性和⼀致
性之间权衡。
CAP 理论告诉我们 :
⽬前很多⼤型⽹站及应⽤都是分布式部署的,分布式场景中的数据⼀致性
问题⼀直是⼀个⽐较重要的话题。
基于 CAP 理论,很多系统在设计之初就要对这三者做出取舍 :
任何⼀个分布式系统都⽆法同时满⾜⼀致性( Consistency )、可⽤性
Availability )和分区容错性( Partition tolerance ),最多只能同时
满⾜两项。在互联⽹领域的绝⼤多数的场景中,都需要 牺牲强⼀致性
来换取系统的⾼可⽤性 ,系统往往只需要保证最终⼀致性。
问:为什么分布式系统中⽆法同时保证⼀致性和可⽤性? 答:⾸先⼀个前提,对于分布式系统⽽⾔,分区容错性是⼀个最基本
的要求,因此基本上我们在设计分布式系统的时候只能从⼀致性
C )和可⽤性( A )之间进⾏取舍。
如果保证了⼀致性( C ):对于节点 N1 N2 ,当往 N1 ⾥写数据时,
N2 上的操作必须被暂停,只有当 N1 同步数据到 N2 时才能对 N2 进⾏读
写请求,在 N2 被暂停操作期间客户端提交的请求会收到失败或超时。
显然,这与可⽤性是相悖的。
如果保证了可⽤性( A ):那就不能暂停 N2 的读写操作,但同时 N1
写数据的话,这就违背了⼀致性的要求。
CAP 权衡
通过 CAP 理论,我们知道⽆法同时满⾜⼀致性、可⽤性和分区容错
性这三个特性,那要舍弃哪个呢?
对于多数⼤型互联⽹应⽤的场景,主机众多、部署分散,⽽且现在的集群
规模越来越⼤,所以节点故障、⽹络故障是常态,⽽且要保证服务可⽤性
达到 N 9 ,即保证 P A ,舍弃 C (退⽽求其次保证最终⼀致性)。虽
然某些地⽅会影响客户体验,但没达到造成⽤户流程的严重程度。
对于涉及到钱财这样不能有⼀丝让步的场景, C 必须保证。⽹络发⽣故障
宁可停⽌服务,这是保证 CA ,舍弃 P 。貌似这⼏年国内银⾏业发⽣了不下
10 起事故,但影响⾯不⼤,报道也不多,⼴⼤群众知道的少。还有⼀种是
保证 CP ,舍弃 A 。例如⽹络故障是只读不写。 CAP ACID 中的 A C 是完全不⼀样的
A 的区别:
ACID 中的 A 指的是原⼦性 (Atomicity) ,是指事务被视为⼀个不可分割的
最⼩⼯作单元,事务中的所有操作要么全部提交成功,要么全部失败回
滚;
CAP 中的 A 指的是可⽤性 (Availability) ,是指集群中⼀部分节点故障后,
集群整体是否还能响应客户端的读写请求;
C 的区别:
ACID ⼀致性是有关数据库规则,数据库总是从⼀个⼀致性的状态转换
到另外⼀个⼀致性的状态;
CAP 的⼀致性是分布式多服务器之间复制数据令这些服务器拥有同样的
数据,由于⽹速限制,这种复制在不同的服务器上所消耗的时间是不固
定的,集群通过组织客户端查看不同节点上还未同步的数据维持逻辑视
图,这是⼀种分布式领域的⼀致性概念;
总之:
ACID ⾥的⼀致性指的是事务执⾏前后,数据库完整性,⽽ CAP 的⼀致性,
指的是分布式节点的数据的⼀致性。背景不同,⽆从可⽐
BASE 定理
CAP 是分布式系统设计理论, BASE CAP 理论中 AP ⽅案的延伸,对
C 我们采⽤的⽅式和策略就是保证最终⼀致性; eBay 的架构师 Dan Pritchett 源于对⼤规模分布式系统的实践总结,在 ACM
上发表⽂章提出 BASE 理论, BASE 理论是对 CAP 理论的延伸,核⼼思想是
即使⽆法做到强⼀致性( StrongConsistency CAP 的⼀致性就是强⼀致
性),但应⽤可以采⽤适合的⽅式达到最终⼀致性( Eventual
Consitency )。
BASE 定理
BASE Basically Available (基本可⽤)、 Soft state (软状态)和
Eventually consistent (最终⼀致性)三个短语的缩写。 BASE 基于 CAP
理演化⽽来,核⼼思想是即时⽆法做到强⼀致性,但每个应⽤都可以根据
⾃身业务特点,采⽤适当的⽅式来使系统达到最终⼀致性。
1 Basically Available (基本可⽤)

1 )响应时间上的损失

2 )功能上的损失

2 Soft state (软状态)
指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影
响系统的整体可⽤性。
与硬状态相对,即是指允许系统中的数据存在中间状态,并认为该中间状
态的存在不会影响系统的整体可⽤性,即允许系统在不同节点的数据副本
之间进⾏数据同步的过程存在延时。
3 Eventually consistent (最终⼀致性)
强调系统中所有的数据副本,在经过⼀段时间的同步后,最终能够达到⼀
个⼀致的状态。其本质是需要系统保证最终数据能够达到⼀致,⽽不需要
实时保证系统数据的强⼀致性。
最终⼀致性可分为如下⼏种:
1 )因果⼀致性( Causal consistency
即进程 A 在更新完数据后通知进程 B ,那么之后进程 B 对该项数据的范围
都是进程 A 更新后的最新值。 2 )读⼰之所写( Read your writes
进程 A 更新⼀项数据后,它⾃⼰总是能访问到⾃⼰更新过的最新值。
3 )会话⼀致性( Session consistency
将数据⼀致性框定在会话当中,在⼀个会话当中实现读⼰之所写的⼀致
性。即执⾏更新后,客户端在同⼀个会话中始终能读到该项数据的最新
4 )单调读⼀致性( Monotonic read consistency
如果⼀个进程从系统中读取出⼀个数据项的某个值后,那么系统对于该
进程后续的任何数据访问都不应该返回更旧的值。
5 )单调写⼀致性( Monotoic write consistency
⼀个系统需要保证来⾃同⼀个进程的写操作被顺序执⾏。
BASE 理论是提出通过牺牲⼀致性来获得可⽤性,并允许数据在⼀段时间内
是不⼀致的,但最终达到⼀致状态。
BASE 理论的特点:
BASE 理论⾯向的是⼤型⾼可⽤可扩展的分布式系统,和传统的事物 ACID
特性是相反的。 它完全不同于ACID 的强⼀致性模型,⽽是通过牺牲强⼀致性来获得可⽤
性,并允许数据在⼀段时间内是不⼀致的,但最终达到⼀致状态。
但同时,在实际的分布式场景中,不同业务单元和组件对数据⼀致性的要
求是不同的,因此在具体的分布式系统架构设计过程中, ACID 特性和
BASE 理论往往⼜会结合在⼀起。 BASE 理论与 CAP 的关系
BASE 理论是对 CAP 中⼀致性和可⽤性权衡的结果,其来源于对⼤规模互联
⽹系统分布式实践的总结, 是基于 CAP 定理逐步演化⽽来的。 BASE 理论
的核⼼思想是: 即使⽆法做到强⼀致性,但每个应⽤都可以根据⾃身业务
特点,采⽤适当的⽅式来使系统达到最终⼀致性
BASE 理论其实就是对 CAP 理论的延伸和补充,主要是对 AP 的补充。牺牲
数据的强⼀致性,来保证数据的可⽤性,虽然存在中间装填,但数据最终 ⼀致。
ACID BASE 的区别与联系
ACID 是传统数据库常⽤的设计理念,追求强⼀致性模型。 BASE ⽀持的是
⼤型分布式系统,提出通过牺牲强⼀致性获得⾼可⽤性。
ACID BASE 代表了两种截然相反的设计哲学,在分布式系统设计的场
景中,系统组件对⼀致性要求是不同的,因此 ACID BASE ⼜会结合使 ⽤。
分布式事务分类:柔性事务和刚性事务
分布式场景下,多个服务同时对服务⼀个流程,⽐如电商下单场景,需要
⽀付服务进⾏⽀付、库存服务扣减库存、订单服务进⾏订单⽣成、物流服
务更新物流信息等。如果某⼀个服务执⾏失败,或者⽹络不通引起的请求
丢失,那么整个系统可能出现数据不⼀致的原因。
上述场景就是分布式⼀致性问题,追根到底,分布式⼀致性的根本原因在
于数据的分布式操作,引起的本地事务⽆法保障数据的原⼦性引起。 分布式⼀致性问题的解决思路有两种,⼀种是分布式事务,⼀种是尽量通
过业务流程避免分布式事务。分布式事务是直接解决问题,⽽业务规避其
实通过解决出问题的地⽅ ( 解决提问题的⼈ ) 。其实在真实业务场景中,如果
业务规避不是很麻烦的前提,最优雅的解决⽅案就是业务规避。
分布式事务分类
分布式事务实现⽅案从类型上去分刚性事务、柔型事务:
刚性事务满⾜ CAP CP 理论
柔性事务满⾜ BASE 理论(基本可⽤,最终⼀致)
刚性事务
刚性事务:通常⽆业务改造,强⼀致性,原⽣⽀持回滚 / 隔离性,低并发,
适合短事务。
原则:刚性事务满⾜⾜ CAP CP 理论
刚性事务指的是,要使分布式事务,达到像本地式事务⼀样,具备数
据强⼀致性,从 CAP 来看,就是说,要达到 CP 状态。
刚性事务: XA 协议( 2PC JTA JTS )、 3PC ,但由于同步阻塞,处理
效率低,不适合⼤型⽹站分布式场景。 柔性事务
柔性事务指的是,不要求强⼀致性,⽽是要求最终⼀致性,允许有中间状
态,也就是 Base 理论,换句话说,就是 AP 状态。
与刚性事务相⽐,柔性事务的特点为:有业务改造,最终⼀致性,实
现补偿接⼝,实现资源锁定接⼝,⾼并发,适合⻓事务。
柔性事务分为:
补偿型
异步确保型
最⼤努⼒通知型。
柔型事务: TCC/FMT Saga (状态机模式、 Aop 模式)、本地事务消息、
消息事务(半消息)
刚性事务: XA 模型、 XA 接⼝规范、 XA 实现
XA 模型 或者 X/Open DTP 模型
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) 模型⾥⾯,有三个⻆
⾊:
AP: Application ,应⽤程序。也就是业务层。哪些操作属于⼀个事务,就
AP 定义的。
TM: Transaction Manager ,事务管理器。接收 AP 的事务请求,对全局事
务进⾏管理,管理事务分⽀状态,协调 RM 的处理,通知 RM 哪些操作属于
哪些全局事务以及事务分⽀等等。这个也是整个事务调度模型的核⼼部
分。
RM Resource Manager ,资源管理器。⼀般是数据库,也可以是其他的
资源管理器,如消息队列 ( JMS 数据源 ) ,⽂件系统等。
XA 把参与事务的⻆⾊分成 AP RM TM
AP ,即应⽤,也就是我们的业务服务。
RM 指的是资源管理器,即 DB MQ 等。
TM 则是事务管理器。
AP ⾃⼰操作 TM ,当需要事务时, AP TM 请求发起事务, TM 负责整
个事务的提交,回滚等。
XA 规范主要定义了 ( 全局 ) 事务管理器 (Transaction Manager) ( 局部 ) 资源管
理器 (Resource Manager) 之间的接⼝。 XA 接⼝是双向的系统接⼝,在事务
管理器( Transaction Manager )以及⼀个或多个资源管理器( Resource
Manager )之间形成通信桥梁。
XA 之所以需要引⼊事务管理器是因为,在分布式系统中,从理论上讲(参
Fischer 等的论⽂),两台机器理论上⽆法达到⼀致的状态,需要引⼊⼀
个单点进⾏协调。事务管理器控制着全局事务,管理事务⽣命周期,并协
调资源。资源管理器负责控制和管理实际资源(如 数据库 JMS 队列)

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 各个阶段的处理流程

XA 的主要限制
必须要拿到所有数据源,⽽且数据源还要⽀持 XA 协议。⽬前 MySQL
只有 InnoDB 存储引擎⽀持 XA 协议。
性能⽐较差,要把所有涉及到的数据都要锁定,是强⼀致性的,会产⽣
⻓事务。
Seata AT 模式
Seata AT 模式是增强型 2pc 模式。
AT 模式: 两阶段提交协议的演变,没有⼀直锁表
⼀阶段:业务数据和回滚⽇志记录在同⼀个本地事务中提交,释放本地
锁和连接资源
⼆阶段:提交异步化,⾮常快速地完成。或回滚通过⼀阶段的回滚⽇志
进⾏反向补偿
LCN 2pc
TX-LCN 官⽅⽂档 github , 3 千多星 , 5.0 以后由于框架兼容了 LCN
2pc )、 TCC TXC 三种事务模式,为了区分 LCN 模式,特此将 LCN
布式事务改名为 TX-LCN 分布式事务框架。
TX-LCN 定位于⼀款事务协调性框架,框架其本身并不⽣产事务,⽽是本地
事务的协调者,从⽽达到事务⼀致性的效果。
TX-LCN 主要有两个模块, Tx-Client(TC) Tx-Manager™.
TM Tx-Manager ):是ᇿ⽴的服务,是分布式事务的控制⽅,协调分
布式事务的提交,回滚 TC Tx-Client ):由业务系统集成,事务发起⽅、参与⽅都由 TxClient
端来控制
2PC (标准 XA 模型)
2PC Two-Phase Commit ,⼆阶段提交。
详解:⼆个阶段
⼴泛应⽤在数据库领域,为了使得基于分布式架构的所有节点可以在进⾏
事务处理时能够保持原⼦性和⼀致性。绝⼤部分关系型数据库,都是基于
2PC 完成分布式的事务处理。
顾名思义, 2PC 分为两个阶段处理,阶段⼀:提交事务请求、阶段⼆:执
⾏事务提交 ;
如果阶段⼀超时或者出现异常, 2PC 的阶段⼆:中断事务
阶段⼀:提交事务请求
1. 事务询问。协调者向所有参与者发送事务内容,询问是否可以执⾏提交
操作,并开始等待各参与者进⾏响应;
2. 执⾏事务。各参与者节点,执⾏事务操作,并将 Undo Redo 操作计⼊
本机事务⽇志;
3. 各参与者向协调者反馈事务问询的响应。成功执⾏返回 Yes ,否则返回
No 阶段⼆:执⾏事务提交
协调者在阶段⼆决定是否最终执⾏事务提交操作。这⼀阶段包含两种情
形:
执⾏事务提交
所有参与者 reply Yes ,那么执⾏事务提交。
1. 发送提交请求。协调者向所有参与者发送 Commit 请求;
2. 事务提交。参与者收到 Commit 请求后,会 正式执⾏事务提交操作 ,并
在完成提交操作之后,释放在整个事务执⾏期间占⽤的资源;
3. 反馈事务提交结果。参与者在完成事务提交后,写协调者发送 Ack 消息
确认;
4. 完成事务。协调者在收到所有参与者的 Ack 后,完成事务。
 
阶段⼆:中断事务
事情总会出现意外,当存在某⼀参与者向协调者发送 No 响应,或者等待超
时。协调者只要⽆法收到所有参与者的 Yes 响应,就会中断事务。
1. 发送回滚请求。协调者向所有参与者发送 Rollback 请求;
2. 回滚。参与者收到请求后,利⽤本机 Undo 信息,执⾏ Rollback 操作。并
在回滚结束后释放该事务所占⽤的系统资源;
3. 反馈回滚结果。参与者在完成回滚操作后,向协调者发送 Ack 消息;
4. 中断事务。协调者收到所有参与者的回滚 Ack 消息后,完成事务中断。
2pc 解决的是分布式数据强⼀致性问题
顾名思义,两阶段提交在处理分布式事务时分为两个阶段: voting (投票阶
段,有的地⽅会叫做 prepare 阶段)和 commit 阶段。
2pc 中存在两个⻆⾊,事务协调者( seata atomikos lcn )和事务参与者,事务参与者通常是指应⽤的数据库。

 

2PC ⼆阶段提交的特点
2PC ⽅案⽐较适合单体应⽤
2PC ⽅案中,有⼀个事务管理器的⻆⾊,负责协调多个数据库(资源管理
器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据
库都回复 ok ,那么就正式提交事务,在各个数据库上执⾏操作;如果任何
其中⼀个数据库回答不 ok ,那么就回滚事务。

2PC ⽅案⽐较适合单体应⽤⾥,跨多个库的分布式事务,⽽且因为严重依
赖于数据库层⾯来搞定复杂的事务,效率很低,绝对不适合⾼并发的场
景。
2PC ⽅案实际很少⽤,⼀般来说某个系统内部如果出现跨多个库的这么⼀
个操作,是不合规的。我可以给⼤家介绍⼀下, 现在微服务,⼀个⼤的系
统分成⼏百个服务,⼏⼗个服务。⼀般来说,我们的规定和规范,是要求
每个服务只能操作⾃⼰对应的⼀个数据库。
如果你要操作别的服务对应的库,不允许直连别的服务的库,违反微服务
架构的规范,你随便交叉胡乱访问,⼏百个服务的话,全体乱套,这样的
⼀套服务是没法管理的,没法治理的,可能会出现数据被别⼈改错,⾃⼰
的库被别⼈写挂等情况。
如果你要操作别⼈的服务的库,你必须是通过调⽤别的服务的接⼝来实
现,绝对不允许交叉访问别⼈的数据库。 2PC 具有明显的优缺点:
优点主要体现在实现原理简单;
缺点⽐较多:
2PC 的提交在执⾏过程中,所有参与事务操作的逻辑都处于阻塞状态,
也就是说,各个参与者都在等待其他参与者响应,⽆法进⾏其他操作;
协调者是个单点,⼀旦出现问题,其他参与者将⽆法释放事务资源,也
⽆法完成事务操作;
数据不⼀致。当执⾏事务提交过程中,如果协调者向所有参与者发送
Commit 请求后,发⽣局部⽹络异常或者协调者在尚未发送完 Commit
求,即出现崩溃,最终导致只有部分参与者收到、执⾏请求。于是整个
系统将会出现数据不⼀致的情形;
保守。 2PC 没有完善的容错机制,当参与者出现故障时,协调者⽆法快
速得知这⼀失败,只能严格依赖超时设置来决定是否进⼀步的执⾏提交
还是中断事务。
实际上分布式事务是⼀件⾮常复杂的事情,两阶段提交只是通过增加了事
务协调者( Coordinator )的⻆⾊来通过 2 个阶段的处理流程来解决分布式
系统中⼀个事务需要跨多个服务节点的数据⼀致性问题。但是从异常情况
上考虑,这个流程也并不是那么的⽆懈可击。
假设如果在第⼆个阶段中 Coordinator 在接收到 Partcipant
"Vote_Request" 后挂掉了或者⽹络出现了异常,那么此时 Partcipant 节点
就会⼀直处于本地事务挂起的状态,从⽽⻓时间地占⽤资源。当然这种情
况只会出现在极端情况下,然⽽作为⼀套健壮的软件系统⽽⾔,异常 Case
的处理才是真正考验⽅案正确性的地⽅。 性能问题
协调者单点故障问题
丢失消息导致的数据不⼀致问题
总结⼀下: XA- 两阶段提交协议中会遇到的⼀些问题
从流程上我们可以看得出,其最⼤缺点就在于它的执⾏过程中间,节点都
处于阻塞状态。各个操作数据库的节点此时都占⽤着数据库资源,只有当
所有节点准备完毕,事务协调者才会通知进⾏全局提交,参与者进⾏本地
事务提交后才会释放资源。这样的过程会⽐较漫⻓,对性能影响⽐较⼤。
事务协调者是整个 XA 模型的核⼼,⼀旦事务协调者节点挂掉,会导致参与
者收不到提交或回滚的通知,从⽽导致参与者节点始终处于事务⽆法完成
的中间状态。
在第⼆个阶段,如果发⽣局部⽹络问题,⼀部分事务参与者收到了提交消
息,另⼀部分事务参与者没收到提交消息,那么就会导致节点间数据的不
⼀致问题。
3PC
针对 2PC 的缺点,研究者提出了 3PC ,即 Three-Phase Commit
作为 2PC 的改进版, 3PC 将原有的两阶段过程,重新划分为 CanCommit
PreCommit do Commit 三个阶段。

阶段⼀: CanCommit
1. 事务询问。协调者向所有参与者发送包含事务内容的 canCommit 的请
求,询问是否可以执⾏事务提交,并等待应答;
2. 各参与者反馈事务询问。正常情况下,如果参与者认为可以顺利执⾏事
务,则返回 Yes ,否则返回 No
阶段⼆: PreCommit
在本阶段,协调者会根据上⼀阶段的反馈情况来决定是否可以执⾏事务的
PreCommit 操作。有以下两种可能: 执⾏事务预提交
1. 发送预提交请求。协调者向所有节点发出 PreCommit 请求,并进⼊
prepared 阶段;
2. 事务预提交。参与者收到 PreCommit 请求后,会执⾏事务操作,并将
Undo Redo ⽇志写⼊本机事务⽇志;
3. 各参与者成功执⾏事务操作,同时将反馈以 Ack 响应形式发送给协调
者,同事等待最终的 Commit Abort 指令。
中断事务
加⼊任意⼀个参与者向协调者发送 No 响应,或者等待超时,协调者在没有
得到所有参与者响应时,即可以中断事务:
1. 发送中断请求。 协调者向所有参与者发送 Abort 请求;
2. 中断事务。⽆论是收到协调者的 Abort 请求,还是等待协调者请求过程
中出现超时,参与者都会中断事务;
阶段三: doCommit
在这个阶段,会真正的进⾏事务提交,同样存在两种可能。
执⾏提交
1. 发送提交请求。假如协调者收到了所有参与者的 Ack 响应,那么将从预
提交转换到提交状态,并向所有参与者,发送 doCommit 请求;
2. 事务提交。参与者收到 doCommit 请求后,会正式执⾏事务提交操作,
并在完成提交操作后释放占⽤资源;
3. 反馈事务提交结果。参与者将在完成事务提交后,向协调者发送 Ack
息;
4. 完成事务。协调者接收到所有参与者的 Ack 消息后,完成事务。 中断事务
在该阶段,假设正常状态的协调者接收到任⼀个参与者发送的 No 响应,或
在超时时间内,仍旧没收到反馈消息,就会中断事务:
1. 发送中断请求。协调者向所有的参与者发送 abort 请求;
2. 事务回滚。参与者收到 abort 请求后,会利⽤阶段⼆中的 Undo 消息执⾏
事务回滚,并在完成回滚后释放占⽤资源;
3. 反馈事务回滚结果。参与者在完成回滚后向协调者发送 Ack 消息;
4. 中端事务。协调者接收到所有参与者反馈的 Ack 消息后,完成事务中
断。
2PC 3PC 的区别:
3PC 有效降低了 2PC 带来的参与者阻塞范围,并且能够在出现单点故障后
继续达成⼀致;
3PC 带来了新的问题,在参与者收到 preCommit 消息后,如果⽹络出现
分区,协调者和参与者⽆法进⾏后续的通信,这种情况下,参与者在等待
超时后,依旧会执⾏事务提交,这样会导致数据的不⼀致。
2PC 3PC 的区别:
三阶段提交协议在协调者和参与者中都引⼊ 超时机制 ,并且把两阶段提交
协议的第⼀个阶段拆分成了两步:询问,然后再锁资源,最后真正提交。
三阶段提交的三个阶段分别为: can_commit pre_commit do_commit

 

3PC 主要解决的单点故障问题:
相对于 2PC 3PC 主要解决的单点故障问题,并减少阻塞, 因为⼀旦参与
者⽆法及时收到来⾃协调者的信息之后,他会默认执⾏ commit 。⽽不会⼀
直持有事务资源并处于阻塞状态
但是这种机制也会导致数据⼀致性问题,因为,由于⽹络原因,协调者发
送的 abort 响应没有及时被参与者接收到,那么参与者在等待超时之后执⾏
commit 操作。这样就和其他接到 abort 命令并执⾏回滚的参与者之间存在
数据不⼀致的情况。
"3PC 相对于 2PC ⽽⾔到底优化了什么地⽅呢 ?"
相⽐较 2PC ⽽⾔, 3PC 对于协调者( Coordinator )和参与者( Partcipant
都设置了超时时间,⽽ 2PC 只有协调者才拥有超时机制。这解决了⼀个什
么问题呢?
这个优化点,主要是避免了参与者在⻓时间⽆法与协调者节点通讯(协调
者挂掉了)的情况下,⽆法释放资源的问题,因为参与者⾃身拥有超时机
制会在超时后,⾃动进⾏本地 commit 从⽽进⾏释放资源。⽽这种机制也侧
⾯降低了整个事务的阻塞时间和范围。
另外,通过 CanCommit PreCommit DoCommit 三个阶段的设计,相较
2PC ⽽⾔,多设置了⼀个缓冲阶段保证了在最后提交阶段之前各参与节
点的状态是⼀致的。 以上就是 3PC 相对于 2PC 的⼀个提⾼(相对缓解了 2PC 中的前两个问
题),但是 3PC 依然没有完全解决数据不⼀致的问题。假如在 DoCommit
过程,参与者 A ⽆法接收协调者的通信,那么参与者 A 会⾃动提交,但是提
交失败了,其他参与者成功了,此时数据就会不⼀致。
柔性事务的分类
在电商领域等互联⽹场景下,刚性事务在数据库性能和处理能⼒上都
暴露出了瓶颈。
柔性事务有两个特性:基本可⽤和柔性状态。
基本可⽤是指分布式系统出现故障的时候允许损失⼀部分的可⽤
性。
柔性状态是指允许系统存在中间状态,这个中间状态不会影响系统
整体的可⽤性,⽐如数据库读写分离的主从同步延迟等。柔性事务
的⼀致性指的是最终⼀致性。
柔性事务主要分为 补偿型 通知型
补偿型事务⼜分: TCC Saga 通知型事务分: MQ 事务消息、最⼤努⼒通知型。
补偿型事务都是同步的,通知型事务都是异步的。
通知型事务
通知型事务的主流实现是通过 MQ (消息队列)来通知其他事务参与者⾃⼰
事务的执⾏状态,引⼊ MQ 组件,有效的将事务参与者进⾏解耦,各参与者
都可以异步执⾏,所以通知型事务⼜被称为 异步事务
通知型事务主要适⽤于那些需要异步更新数据,并且对数据的实时性要求
较低的场景,主要包含 :
异步确保型事务 最⼤努⼒通知事务 两种。
异步确保型事务 :主要适⽤于内部系统的数据最终⼀致性保障,因为内
部相对⽐较可控,如订单和购物⻋、收货与清算、⽀付与结算等等场
景;
最⼤努⼒通知 :主要⽤于外部系统,因为外部的⽹络环境更加复杂和不
可信,所以只能尽最⼤努⼒去通知实现数据最终⼀致性,⽐如充值平台
与运营商、⽀付对接等等跨⽹络系统级别对接;
异步确保型事务
指将⼀系列同步的事务操作修改为基于消息队列异步执⾏的操作,来避免
分布式事务中同步阻塞带来的数据操作性能的下降。
MQ 事务消息⽅案
基于 MQ 的事务消息⽅案主要依靠 MQ 半消息机制 来实现投递消息和参与
者⾃身本地事务的⼀致性保障。半消息机制实现原理其实借鉴的 2PC 的思
路,是⼆阶段提交的⼴义拓展。
半消息 :在原有队列消息执⾏后的逻辑,如果后⾯的本地逻辑出错,
则不发送该消息,如果通过则告知 MQ 发送;
1. 事务发起⽅⾸先发送半消息到 MQ
2. MQ 通知发送⽅消息发送成功;
3. 在发送半消息成功后执⾏本地事务;
4. 根据本地事务执⾏结果返回 commit 或者是 rollback
5. 如果消息是 rollback, MQ 将丢弃该消息不投递;如果是 commit MQ
会消息发送给消息订阅⽅;
6. 订阅⽅根据消息执⾏本地事务;
7. 订阅⽅执⾏本地事务成功后再从 MQ 中将该消息标记为已消费;
8. 如果执⾏本地事务过程中,执⾏端挂掉,或者超时, MQ 服务器端将不
停的询问 producer 来获取事务状态;
9. Consumer 端的消费成功机制有 MQ 保证;
异步确保型事务使⽤示例
举个例⼦,假设存在业务规则:某笔订单成功后,为⽤户加⼀定的积分。
在这条规则⾥,管理订单数据源的服务为事务发起⽅,管理积分数据源的
服务为事务跟随者。 从这个过程可以看到,基于消息队列实现的事务存在以下操作:
订单服务创建订单,提交本地事务
订单服务发布⼀条消息
积分服务收到消息后加积分

我们可以看到它的整体流程是⽐较简单的,同时业务开发⼯作量也不⼤:
编写订单服务⾥订单创建的逻辑
编写积分服务⾥增加积分的逻辑
可以看到该事务形态过程简单,性能消耗⼩,发起⽅与跟随⽅之间的流量
峰⾕可以使⽤队列填平,同时业务开发⼯作量也基本与单机事务没有差
别,都不需要编写反向的业务逻辑过程
因此基于消息队列实现的事务是我们除了单机事务外最优先考虑使⽤的形
态。 基于阿⾥ RocketMQ 实现 MQ 异步确保型事务
有⼀些第三⽅的 MQ 是⽀持事务消息的,这些消息队列,⽀持半消息
机制,⽐如 RocketMQ ActiveMQ 。但是有⼀些常⽤的 MQ 也不⽀持
事务消息,⽐如 RabbitMQ Kafka 都不⽀持。
以阿⾥的 RocketMQ 中间件为例,其思路⼤致为:
1.producer( 本例中指 A 系统 ) 发送半消息到 broker ,这个半消息不是说消息
内容不完整, 它包含完整的消息内容, 在 producer 端和普通消息的发送逻
辑⼀致
2.broker 存储半消息,半消息存储逻辑与普通消息⼀致,只是属性有所不
同, topic 是固定的 RMQ_SYS_TRANS_HALF_TOPIC queueId 也是固定
0 ,这个 tiopic 中的消息对消费者是不可⻅的,所以⾥⾯的消息永远不会
被消费。这就保证了在半消息提交成功之前,消费者是消费不到这个半消
息的
3.broker 端半消息存储成功并返回后, A 系统执⾏本地事务,并根据本地事
务的执⾏结果来决定半消息的提交状态为提交或者回滚
4.A 系统发送结束半消息的请求,并带上提交状态 ( 提交 or 回滚 )
5.broker 端收到请求后,⾸先从 RMQ_SYS_TRANS_HALF_TOPIC
queue 中查出该消息,设置为完成状态。如果消息状态为提交,则把半消
息从 RMQ_SYS_TRANS_HALF_TOPIC 队列中复制到这个消息原始 topic
queue 中去 ( 之后这条消息就能被正常消费了 ) ;如果消息状态为回滚,则什
么也不做。 6.producer 发送的半消息结束请求是 oneway 的,也就是发送后就不管
了,只靠这个是⽆法保证半消息⼀定被提交的, rocketMq 提供了⼀个兜底
⽅案,这个⽅案叫消息反查机制, Broker 启动时,会启动⼀个
TransactionalMessageCheckService 任务,该任务会定时从半消息队列中
读出所有超时未完成的半消息,针对每条未完成的消息, Broker 会给对应
Producer 发送⼀个消息反查请求,根据反查结果来决定这个半消息是需
要提交还是回滚,或者后⾯继续来反查
7.consumer( 本例中指 B 系统 ) 消费消息,执⾏本地数据变更 ( ⾄于 B 是否能消
费成功,消费失败是否重试,这属于正常消息消费需要考虑的问题 )
rocketMq 中,不论是 producer 收到 broker 存储半消息成功返回后执⾏本
地事务,还是 broker producer 反查消息状态,都是通过 回调机制 完成,
我把 producer 端的代码贴出来你就明⽩了:

 

半消息发送时,会传⼊⼀个回调类 TransactionListener ,使⽤时必须实现其
中的两个⽅法, executeLocalTransaction ⽅法会在 broker 返回半消息存储
成功后执⾏,我们会在其中执⾏本地事务; checkLocalTransaction ⽅法会
broker producer 发起反查时执⾏,我们会在其中查询库表状态。两个
⽅法的返回值都是消息状态,就是告诉 broker 应该提交或者回滚半消息

 

发送消息⽅:
需要有⼀个消息表,记录着消息状态相关信息。
业务数据和消息表在同⼀个数据库,要保证它俩在同⼀个本地事务。直
接利⽤本地事务,将业务数据和事务消息直接写⼊数据库。
在本地事务中处理完业务数据和写消息表操作后,通过写消息到 MQ
息队列。使⽤专⻔的投递⼯作线程进⾏事务消息投递到 MQ ,根据投递 ACK 去删除事务消息表记录
消息会发到消息消费⽅,如果发送失败,即进⾏重试。
消息消费⽅:
处理消息队列中的消息,完成⾃⼰的业务逻辑。
如果本地事务处理成功,则表明已经处理成功了。
如果本地事务处理失败,那么就会重试执⾏。
如果是业务层⾯的失败,给消息⽣产⽅发送⼀个业务补偿消息,通知进
⾏回滚等操作。
⽣产⽅和消费⽅定时扫描本地消息表,把还没处理完成的消息或者失败的
消息再发送⼀遍。如果有靠谱的⾃动对账补账逻辑,这种⽅案还是⾮常实
⽤的。
本地消息表优缺点:
优点:
本地消息表建设成本⽐较低,实现了可靠消息的传递确保了分布式事务
的最终⼀致性。
⽆需提供回查⽅法,进⼀步减少的业务的侵⼊。
在某些场景下,还可以进⼀步利⽤注解等形式进⾏解耦,有可能实现⽆
业务代码侵⼊式的实现。
缺点:
本地消息表与业务耦合在⼀起,难于做成通⽤性,不可ᇿ⽴伸缩。
本地消息表是基于数据库来做的,⽽数据库是要读写磁盘 IO 的,因此在
⾼并发下是有性能瓶颈的 MQ 事务消息 VS 本地消息表
⼆者的共性:
1 、 事务消息都依赖 MQ 进⾏事务通知,所以都是异步的。
2 、 事务消息在投递⽅都是存在重复投递的可能,需要有配套的机制去降
低重复投递率,实现更友好的消息投递去重。
3 、 事务消息的消费⽅,因为投递重复的⽆法避免,因此需要进⾏消费去
重设计或者服务幂等设计。
⼆者的区别:
MQ 事务消息:
需要 MQ ⽀持半消息机制或者类似特性,在重复投递上具有⽐较好的去
重处理;
具有⽐较⼤的业务侵⼊性,需要业务⽅进⾏改造,提供对应的本地操作
成功的回查功能;
DB 本地消息表:
使⽤了数据库来存储事务消息,降低了对 MQ 的要求,但是增加了存储
成本;
事务消息使⽤了异步投递,增⼤了消息重复投递的可能性;

 

 
最⼤努⼒通知
最⼤努⼒通知⽅案的⽬标,就是发起通知⽅通过⼀定的机制,最⼤努⼒将
业务处理结果通知到接收⽅。
最⼤努⼒通知型的最终⼀致性:
本质是通过引⼊ 定期校验机制 实现最终⼀致性,对业务的侵⼊性较
低,适合于对最终⼀致性敏感度⽐较低、业务链路较短的场景。
最⼤努⼒通知事务 主要⽤于 外部系统 ,因为外部的⽹络环境更加复杂和不
可信,所以只能尽最⼤努⼒去通知实现数据最终⼀致性, ⽐如充值平台与
运营商、⽀付对接、商户通知等等跨平台、跨企业的系统间业务交互场
异步确保型事务 主要适⽤于 内部系统 的数据最终⼀致性保障,因为内部
相对⽐较可控,⽐如订单和购物⻋、收货与清算、⽀付与结算等等场景。 普通消息是⽆法解决本地
普通消息是⽆法解决本地事务执⾏和消息发送的⼀致性问题的。因为消息
发送是⼀个⽹络通信的过程,发送消息的过程就有可能出现发送失败、或
者超时的情况。超时有可能发送成功了,有可能发送失败了,消息的发送
⽅是⽆法确定的,所以此时消息发送⽅⽆论是提交事务还是回滚事务,都
有可能不⼀致性出现。
所以,通知型事务的难度在于: 投递消息和参与者本地事务的⼀致性保
因为核⼼要点⼀致,都是为了保证消息的⼀致性投递 ,所以,最⼤努⼒通
知事务在投递流程上跟异步确保型是⼀样的,因此也有 两个分⽀
基于 MQ ⾃身的事务消息⽅案
基于 DB 的本地事务消息表⽅案 MQ 事务消息⽅案
要实现最⼤努⼒通知,可以采⽤ MQ ACK 机制。
最⼤努⼒通知事务在投递之前,跟异步确保型流程都差不多,关键在于投
递后的处理。
因为异步确保型在于内部的事务处理,所以 MQ 和系统是直连并且⽆需严格
的权限、安全等⽅⾯的思路设计。最⼤努⼒通知事务在于第三⽅系统的对
接,所以最⼤努⼒通知事务有⼏个特性:
业务主动⽅在完成业务处理后,向业务被动⽅ ( 第三⽅系统 ) 发送通知消
息,允许存在消息丢失。
业务主动⽅提供递增多挡位时间间隔 (5min 10min 30min 1h
24h) ,⽤于失败重试调⽤业务被动⽅的接⼝;在通知 N 次之后就不再通
知,报警 + 记⽇志 + ⼈⼯介⼊。
业务被动⽅提供幂等的服务接⼝,防⽌通知重复消费。
业务主动⽅需要有定期校验机制,对业务数据进⾏兜底;防⽌业务被动
⽅⽆法履⾏责任时进⾏业务回滚,确保数据最终⼀致性。
1. 业务活动的主动⽅,在完成业务处理之后,向业务活动的被动⽅发送消 息,允许消息丢失。
2. 主动⽅可以设置时间阶梯型通知规则,在通知失败后按规则重复通知,
直到通知 N 次后不再通知。
3. 主动⽅提供校对查询接⼝给被动⽅按需校对查询,⽤于恢复丢失的业务
消息。
4. 业务活动的被动⽅如果正常接收了数据,就正常返回响应,并结束事
务。
5. 如果被动⽅没有正常接收,根据定时策略,向业务活动主动⽅查询,恢
复丢失的业务消息。
特点
1. ⽤到的服务模式:可查询操作、幂等操作;
2. 被动⽅的处理结果不影响主动⽅的处理结果;
3. 适⽤于对业务最终⼀致性的时间敏感度低的系统;
4. 适合跨企业的系统间的操作,或者企业内部⽐较ᇿ⽴的系统间的操作,
⽐如银⾏通知、商户通知等;
本地消息表⽅案
要实现最⼤努⼒通知,可以采⽤ 定期检查本地消息表的机制 。
发送消息⽅:
需要有⼀个消息表,记录着消息状态相关信息。
业务数据和消息表在同⼀个数据库,要保证它俩在同⼀个本地事务。直
接利⽤本地事务,将业务数据和事务消息直接写⼊数据库。
在本地事务中处理完业务数据和写消息表操作后,通过写消息到 MQ
息队列。使⽤专⻔的投递⼯作线程进⾏事务消息投递到 MQ ,根据投递
ACK 去删除事务消息表记录
消息会发到消息消费⽅,如果发送失败,即进⾏重试。
⽣产⽅定时扫描本地消息表,把还没处理完成的消息或者失败的消息再
发送⼀遍。如果有靠谱的⾃动对账补账逻辑,这种⽅案还是⾮常实⽤
的。
最⼤努⼒通知事务在于第三⽅系统的对接,所以最⼤努⼒通知事务有⼏个
特性:
业务主动⽅在完成业务处理后,向业务被动⽅ ( 第三⽅系统 ) 发送通知消
息,允许存在消息丢失。
业务主动⽅提供递增多挡位时间间隔 (5min 10min 30min 1h 24h) ,⽤于失败重试调⽤业务被动⽅的接⼝;在通知 N 次之后就不再通
知,报警 + 记⽇志 + ⼈⼯介⼊。
业务被动⽅提供幂等的服务接⼝,防⽌通知重复消费。
业务主动⽅需要有定期校验机制,对业务数据进⾏兜底;防⽌业务被动
⽅⽆法履⾏责任时进⾏业务回滚,确保数据最终⼀致性。
最⼤努⼒通知事务 VS 异步确保型事务
最⼤努⼒通知事务在我认知中,其实是基于异步确保型事务发展⽽来适⽤
于外部对接的⼀种业务实现。他们主要有的是业务差别,如下:
从参与者来说:最⼤努⼒通知事务适⽤于跨平台、跨企业的系统间业务交
互;异步确保型事务更适⽤于同⽹络体系的内部服务交付。
从消息层⾯说:最⼤努⼒通知事务需要主动推送并提供多档次时间的重试
机制来保证数据的通知;⽽异步确保型事务只需要消息消费者主动去消
费。
从数据层⾯说:最⼤努⼒通知事务还需额外的定期校验机制对数据进⾏兜
底,保证数据的最终⼀致性;⽽异步确保型事务只需保证消息的可靠投递
即可,⾃身⽆需对数据进⾏兜底处理。
通知型事务的问题
通知型事务,是⽆法解决本地事务执⾏和消息发送的⼀致性问题的。
因为消息发送是⼀个⽹络通信的过程,发送消息的过程就有可能出现发送
失败、或者超时的情况。超时有可能发送成功了,有可能发送失败了,消
息的发送⽅是⽆法确定的,所以此时消息发送⽅⽆论是提交事务还是回滚
事务,都有可能不⼀致性出现。 消息发送⼀致性
消息中间件在分布式系统中的核⼼作⽤就是异步通讯、应⽤解耦和并发缓
冲(也叫作流量削峰)。在分布式环境下,需要通过⽹络进⾏通讯,就引
⼊了数据传输的不确定性,也就是 CAP 理论中的分区容错性。
消息发送⼀致性是指 产⽣消息的业务动作与消息发送动作⼀致 ,也就是说
如果业务操作成功,那么由这个业务操作所产⽣的消息⼀定要发送出去,
否则就丢失。
常规 MQ 消息处理流程和特点
常规的 MQ 队列处理流程⽆法实现消息的⼀致性。所以,需要借助半消息、
本地消息表,保障⼀致性。
消息重复发送问题和业务接⼝幂等性设计
对于未确认的消息,采⽤按规则重新投递的⽅式进⾏处理。
对于以上流程,消息重复发送会导致业务处理接⼝出现重复调⽤的问题。
消息消费过程中消息重复发送的主要原因就是消费者成功接收处理完消息
后,消息中间件没有及时更新投递状态导致的。如果允许消息重复发送,
那么消费⽅应该实现业务接⼝的幂等性设计。 补偿型
但是基于消息实现的事务并不能解决所有的业务场景,例如以下场景:某
笔订单完成时,同时扣掉⽤户的现⾦。
这⾥事务发起⽅是管理订单库的服务,但对整个事务是否提交并不能只由
订单服务决定,因为还要确保⽤户有⾜够的钱,才能完成这笔交易,⽽这
个信息在管理现⾦的服务⾥。这⾥我们可以引⼊基于补偿实现的事务,其
流程如下:
创建订单数据,但暂不提交本地事务
订单服务发送远程调⽤到现⾦服务,以扣除对应的⾦额
上述步骤成功后提交订单库的事务
以上这个是正常成功的流程,异常流程需要回滚的话,将额外发送远程调
⽤到现⾦服务以加上之前扣掉的⾦额。
以上流程⽐基于消息队列实现的事务的流程要复杂,同时开发的⼯作量也
更多:
编写订单服务⾥创建订单的逻辑
编写现⾦服务⾥扣钱的逻辑
编写现⾦服务⾥补偿返还的逻辑
可以看到,该事务流程相对于基于消息实现的分布式事务更为复杂,需要
额外开发相关的业务回滚⽅法,也失去了服务间流量削峰填⾕的功能。但
其仅仅只⽐基于消息的事务复杂多⼀点,若不能使⽤基于消息队列的最终
⼀致性事务,那么可以优先考虑使⽤基于补偿的事务形态。 什么是补偿模式?
补偿模式使⽤⼀个额外的协调服务来协调各个需要保证⼀致性的业务
服务,协调服务按顺序调⽤各个业务微服务,如果某个业务服务调⽤
异常(包括业务异常和技术异常)就取消之前所有已经调⽤成功的业
务服务。
补偿模式⼤致有 TCC ,和 Saga 两种细分的⽅案
 
TCC 事务模型 什么是 TCC 事务模型
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 的⼯作流程
TCC(Try-Confirm-Cancel) 分布式事务模型相对于 XA 等传统模型,其特征
在于 它不依赖资源管理器 (RM) 对分布式事务的⽀持,⽽是通过对业务逻辑
的分解来实现分布式事务
TCC 模型认为对于业务系统中⼀个特定的业务逻辑,其对外提供服务时,
必须接受⼀些不确定性,即对业务逻辑初步操作的调⽤仅是⼀个临时性操
作,调⽤它的主业务服务保留了后续的取消权。如果主业务服务认为全局
事务应该回滚,它会要求取消之前的临时性操作,这就对应从业务服务的
取消操作。⽽当主业务服务认为全局事务应该提交时,它会放弃之前临时
性操作的取消权,这对应从业务服务的确认操作。每⼀个初步操作,最终
都会被确认或取消。
因此,针对⼀个具体的业务服务, TCC 分布式事务模型需要业务系统提供
三段业务逻辑:
初步操作 Try :完成所有业务检查,预留必须的业务资源。 确认操作 Confirm :真正执⾏的业务逻辑,不作任何业务检查,只使⽤ Try
阶段预留的业务资源。因此,只要 Try 操作成功, Confirm 必须能成功。另
外, Confirm 操作需满⾜幂等性,保证⼀笔分布式事务有且只能成功⼀
次。
取消操作 Cancel :释放 Try 阶段预留的业务资源。同样的, Cancel 操作也
需要满⾜幂等性。

 

Try 阶段 : 调⽤ Try 接⼝,尝试执⾏业务,完成所有业务检查,预留业务
资源。
Confirm Cancel 阶段 : 两者是互斥的,只能进⼊其中⼀个,并且都满
⾜幂等性,允许失败重试。
Confirm 操作 : 对业务系统做确认提交,确认执⾏业务操作,不做其他业
务检查,只使⽤ Try 阶段预留的业务资源。
Cancel 操作 : 在业务执⾏错误,需要回滚的状态下执⾏业务取消,释放
预留资源。
Try 阶段失败可以 Cancel ,如果 Confirm Cancel 阶段失败了怎么办?
TCC 中会添加事务⽇志,如果 Confirm 或者 Cancel 阶段出错,则会进⾏
重试,所以这两个阶段需要⽀持幂等;如果重试失败,则需要⼈⼯介⼊进
⾏恢复和处理等。 TCC 事务案例
然⽽基于补偿的事务形态也并⾮能实现所有的需求,如以下场景:某笔订
单完成时,同时扣掉⽤户的现⾦,但交易未完成,也未被取消时,不能让
客户看到钱变少了。
这时我们可以引⼊ TCC ,其流程如下:
订单服务创建订单
订单服务发送远程调⽤到现⾦服务,冻结客户的现⾦
提交订单服务数据
订单服务发送远程调⽤到现⾦服务,扣除客户冻结的现⾦
以上是正常完成的流程,若为异常流程,则需要发送远程调⽤请求到现⾦
服务,撤销冻结的⾦额。
以上流程⽐基于补偿实现的事务的流程要复杂,同时开发的⼯作量也更
多:
订单服务编写创建订单的逻辑
现⾦服务编写冻结现⾦的逻辑
现⾦服务编写扣除现⾦的逻辑
现⾦服务编写解冻现⾦的逻辑
TCC 实际上是最为复杂的⼀种情况,其能处理所有的业务场景,但⽆论出
于性能上的考虑,还是开发复杂度上的考虑,都应该尽量避免该类事务。 TCC 事务模型的要求:
1. 可查询操作:服务操作具有全局唯⼀的标识,操作唯⼀的确定的时间。
2. 幂等操作:重复调⽤多次产⽣的业务结果与调⽤⼀次产⽣的结果相同。
⼀是通过业务操作实现幂等性,⼆是系统缓存所有请求与处理的结果,
最后是检测到重复请求之后,⾃动返回之前的处理结果。
3. TCC 操作: Try 阶段,尝试执⾏业务,完成所有业务的检查,实现⼀致
性;预留必须的业务资源,实现准隔离性。 Confirm 阶段:真正的去执
⾏业务,不做任何检查,仅适⽤ Try 阶段预留的业务资源, Confirm 操作
还要满⾜幂等性。 Cancel 阶段:取消执⾏业务,释放 Try 阶段预留的业
务资源, Cancel 操作要满⾜幂等性。 TCC 2PC( 两阶段提交 ) 协议的区
别: TCC 位于业务服务层⽽不是资源层, TCC 没有单ᇿ准备阶段, Try
操作兼备资源操作与准备的能⼒, TCC Try 操作可以灵活的选择业务
资源,锁定粒度。 TCC 的开发成本⽐ 2PC ⾼。实际上 TCC 也属于两阶段
操作,但是 TCC 不等同于 2PC 操作。
4. 可补偿操作: Do 阶段:真正的执⾏业务处理,业务处理结果外部可
⻅。 Compensate 阶段:抵消或者部分撤销正向业务操作的业务结果,
补偿操作满⾜幂等性。约束:补偿操作在业务上可⾏,由于业务执⾏结
果未隔离或者补偿不完整带来的⻛险与成本可控。实际上, TCC
Confirm Cancel 操作可以看做是补偿操作。
TCC 事务模型 VS DTP 事务模型
⽐较⼀下 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 其实本质和 2PC 是差不多的:
T 就是 Try ,两个 C 分别是 Confirm Cancel
Try 就是尝试,请求链路中每个参与者依次执⾏ Try 逻辑,如果都成功,
就再执⾏ Confirm 逻辑,如果有失败,就执⾏ Cancel 逻辑。
TCC XA 两阶段提交有着异曲同⼯之妙,下图列出了⼆者之间的对⽐

 

 

1. 在阶段 1
XA 中,各个 RM 准备提交各⾃的事务分⽀,事实上就是准备提交资源
的更新操作 (insert delete update )
⽽在 TCC 中,是主业务活动请求 (try) 各个从业务服务预留资源。
1. 在阶段 2
XA 根据第⼀阶段每个 RM 是否都 prepare 成功,判断是要提交还是回
滚。如果都 prepare 成功,那么就 commit 每个事务分⽀,反之则 rollback
每个事务分⽀。
TCC 中,如果在第⼀阶段所有业务资源都预留成功,那么 confirm 各个
从业务服务,否则取消 (cancel) 所有从业务服务的资源预留请求。 TCC 2PC 不同的是:
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 的使⽤场景
TCC 是可以解决部分场景下的分布式事务的,但是,它的⼀个问题在于,
需要每个参与者都分别实现 Try Confirm Cancel 接⼝及逻辑,这对于业
务的侵⼊性是巨⼤的。
TCC ⽅案严重依赖回滚和补偿代码,最终的结果是:回滚代码逻辑复杂,
业务代码很难维护。所以, TCC ⽅案的使⽤场景较少,但是也有使⽤的场
景。 ⽐如说跟钱打交道的,⽀付、交易相关的场景,⼤家会⽤ TCC ⽅案,严格
证分布式事务要么全部成功,要么全部⾃动回滚 ,严格保证资⾦的正确
性,保证在资⾦上不会出现问题。

 

seata 简介
Seata , 官⽹ , github , 1 万多星
Seata 是⼀款开源的分布式事务解决⽅案,致⼒于在微服务架构下提供
⾼性能和简单易⽤的分布式事务服务。 Seata 将为⽤户提供了 AT
TCC SAGA XA 事务模式
Seata 开源之前, Seata 对应的内部版本在阿⾥经济体内部⼀直扮演
着分布式⼀致性中间件的⻆⾊,帮助经济体平稳的度过历年的双 11 ,对
BU 业务进⾏了有⼒的⽀撑。商业化产品 GTS 先后在阿⾥云、⾦融云
进⾏售卖
什么是 seata https://seata.io/zh-cn/docs/overview/what-is-seata.html
下载 https://seata.io/zh-cn/blog/download.html
官⽅例⼦ https://seata.io/zh-cn/docs/user/quickstart.html Seata 的三⼤模块
Seata AT 使⽤了增强型⼆阶段提交实现。
Seata 分三⼤模块 :
TC :事务协调者。负责我们的事务 ID 的⽣成,事务注册、提交、回滚
等。
TM :事务发起者。定义事务的边界,负责告知 TC ,分布式事务的开
始,提交,回滚。
RM :资源管理者。管理每个分⽀事务的资源,每⼀个 RM 都会作为⼀
个分⽀事务注册在 TC
Seata AT 模式中, TM RM 都作为 SDK 的⼀部分和业务服务在⼀起,
我们可以认为是 Client TC 是⼀个ᇿ⽴的服务,通过服务的注册、发现将
⾃⼰暴露给 Client 们。
Seata 中有三⼤模块中, TM RM 是作为 Seata 的客户端与业务系统集
成在⼀起, TC 作为 Seata 的服务端ᇿ⽴部署。
Seata 的分布式事务的执⾏流程
Seata 中,分布式事务的执⾏流程:
TM 开启分布式事务( TM TC 注册全局事务记录);
按业务场景,编排数据库、服务等事务内资源( RM TC 汇报资源准
备状态 );
TM 结束分布式事务,事务⼀阶段结束( TM 通知 TC 提交 / 回滚分布式
事务);
TC 汇总事务信息,决定分布式事务是提交还是回滚; TC 通知所有 RM 提交 / 回滚 资源,事务⼆阶段结束;
Seata TC TM RM 三个⻆⾊ , 是不是和 XA 模型很像 . 下图是 XA 模型的
事务⼤致流程。
X/Open DTP(Distributed Transaction Process) 模型⾥⾯,有三个⻆
⾊: AP: Application ,应⽤程序。也就是业务层。哪些操作属于⼀个事务,就
AP 定义的。
TM: Transaction Manager ,事务管理器。接收 AP 的事务请求,对全局事
务进⾏管理,管理事务分⽀状态,协调 RM 的处理,通知 RM 哪些操作属于
哪些全局事务以及事务分⽀等等。这个也是整个事务调度模型的核⼼部
分。
RM Resource Manager ,资源管理器。⼀般是数据库,也可以是其他的
资源管理器,如消息队列 ( JMS 数据源 ) ,⽂件系统等。
4 种分布式事务解决⽅案
Seata 会有 4 种分布式事务解决⽅案,分别是 AT 模式、 TCC 模式、 Saga
模式和 XA 模式。 Seata AT 模式
Seata AT 模式是最早⽀持的模式。 AT 模式是指 Automatic (Branch)
Transaction Mode ⾃动化分⽀事务。
Seata AT 模式是增强型 2pc 模式,或者说是增强型的 XA 模型。
总体来说, AT 模式,是 2pc 两阶段提交协议的演变,不同的地⽅, Seata
AT 模式不会⼀直锁表。
Seata AT 模式的使⽤前提
基于⽀持本地 ACID 事务的关系型数据库。
⽐如,在 MySQL 5.1 之前的版本中,默认的搜索引擎是 MyISAM
MySQL 5.5 之后的版本中,默认的搜索引擎变更为 InnoDB
MyISAM 存储引擎的特点是:表级锁、不⽀持事务和全⽂索引。
所以,基于 MyISAM 的表,就不⽀持 Seata AT 模式。
Java 应⽤,通过 JDBC 访问数据库。
Seata AT 模型图
两阶段提交协议的演变:
⼀阶段:业务数据和回滚⽇志记录在同⼀个本地事务中提交,释放本地
锁和连接资源。
⼆阶段:
提交异步化,⾮常快速地完成。 或回滚通过⼀阶段的回滚⽇志进⾏反向补偿
完整的 AT Seata 所制定的事务模式下的模型图:

Seata AT 模式的例⼦
我们⽤⼀个⽐较简单的业务场景来描述⼀下 Seata AT 模式的⼯作过程。
有个充值业务,现在有两个服务,⼀个负责管理⽤户的余额,另外⼀个负
责管理⽤户的积分。
当⽤户充值的时候,⾸先增加⽤户账户上的余额,然后增加⽤户的积
分。 Seata AT 分为两阶段,主要逻辑全部在第⼀阶段,第⼆阶段主要做回滚或
⽇志清理的⼯作。
第⼀阶段流程:
第⼀阶段流程如

 

1 )余额服务中的 TM ,向 TC 申请开启⼀个全局事务, TC 会返回⼀个全局的
事务 ID
2 )余额服务在执⾏本地业务之前, RM 会先向 TC 注册分⽀事务。 3 )余额服务依次⽣成 undo log 、执⾏本地事务、⽣成 redo log ,最后直接
提交本地事务。
4 )余额服务的 RM TC 汇报,事务状态是成功的。
5 )余额服务发起远程调⽤,把事务 ID 传给积分服务。
6 )积分服务在执⾏本地业务之前,也会先向 TC 注册分⽀事务。
7 )积分服务次⽣成 undo log 、执⾏本地事务、⽣成 redo log ,最后直接提
交本地事务。
8 )积分服务的 RM TC 汇报,事务状态是成功的。
9 )积分服务返回远程调⽤成功给余额服务。
10 )余额服务的 TM TC 申请全局事务的提交 / 回滚。
积分服务中也有 TM ,但是由于没有⽤到,因此直接可以忽略。
我们如果使⽤ Spring 框架的注解式事务,远程调⽤会在本地事务提交之前
发⽣。但是,先发起远程调⽤还是先提交本地事务,这个其实没有任何影
响。
第⼆阶段流程如:
第⼆阶段的逻辑就⽐较简单了。 Client TC 之间是有⻓连接的,如果是正常全局提交,则 TC 通知多个 RM
异步清理掉本地的 redo undo log 即可。如果是回滚,则 TC 通知每个 RM
滚数据即可。
这⾥就会引出⼀个问题,由于本地事务都是⾃⼰直接提交了,后⾯如何回
滚,由于我们在操作本地业务操作的前后,做记录了 undo redo log ,因
此可以通过 undo log 进⾏回滚。
由于 undo redo log 和业务操作在同⼀个事务中,因此肯定会同时成功或
同时失败。
但是还会存在⼀个问题,因为每个事务从本地提交到通知回滚这段时间
⾥,可能这条数据已经被别的事务修改,如果直接⽤ undo log 回滚,会导
致数据不⼀致的情况。
此时, RM 会⽤ redo log 进⾏校验,对⽐数据是否⼀样,从⽽得知数据是否
有别的事务修改过。注意: undo log 是被修改前的数据,可以⽤于回滚;
redo log 是被修改后的数据,⽤于回滚校验。
如果数据未被其他事务修改过,则可以直接回滚;如果是脏数据,再根据
不同策略处理。
Seata AT 模式在电商下单场景的使⽤
下⾯描述 Seata AT mode 的⼯作原理使⽤的电商下单场景的使⽤
如下图所示:

 

在上图中,协调者 shopping-service 先调⽤参与者 repo-service 扣减库
存,后调⽤参与者 order-service ⽣成订单。这个业务流使⽤ Seata in XA
mode 后的全局事务流程如下图所示:

 

上图描述的全局事务执⾏流程为:
1 shopping-service Seata 注册全局事务,并产⽣⼀个全局事务标识
XID
2 )将 repo-service.repo_db order-service.order_db 的本地事务执⾏到待
提交阶段,事务内容包含对 repo-service.repo_db order
service.order_db 进⾏的查询操作以及写每个库的 undo_log 记录
3 repo-service.repo_db order-service.order_db Seata 注册分⽀事
务,并将其纳⼊该 XID 对应的全局事务范围
4 )提交 repo-service.repo_db order-service.order_db 的本地事务 5 repo-service.repo_db order-service.order_db Seata 汇报分⽀事务
的提交状态
6 Seata 汇总所有的 DB 的分⽀事务的提交状态,决定全局事务是该提交
还是回滚
7 Seata 通知 repo-service.repo_db order-service.order_db 提交 / 回滚
本地事务,若需要回滚,采取的是补偿式⽅法
其中 1 2 3 4 5 )属于第⼀阶段, 6 7 )属于第⼆阶段。
电商业务场景中 Seata in AT mode ⼯作流程详述
在上⾯的电商业务场景中,购物服务调⽤库存服务扣减库存,调⽤订单服
务创建订单,显然这两个调⽤过程要放在⼀个事务⾥⾯。即:

 

AT 模式第⼀阶段的流程来看,分⽀的本地事务在第⼀阶段提交完成之
后,就会释放掉本地事务锁定的本地记录。这是 AT 模式和 XA 最⼤的不同
点,在 XA 事务的两阶段提交中,被锁定的记录直到第⼆阶段结束才会被
释放。所以 AT 模式减少了锁记录的时间,从⽽提⾼了分布式事务的处理效
率。 AT 模式之所以能够实现第⼀阶段完成就释放被锁定的记录,是因为
Seata 在每个服务的数据库中维护了⼀张 undo_log 表,其中记录了对
t_order / t_repo 进⾏操作前后记录的镜像数据,即便第⼆阶段发⽣异常,
只需回放每个服务的 undo_log 中的相应记录即可实现全局回滚。
undo_log 的表结构:
第⼀阶段结束之后, Seata 会接收到所有分⽀事务的提交状态,然后决定
是提交全局事务还是回滚全局事务。
1 )若所有分⽀事务本地提交均成功,则 Seata 决定全局提交。 Seata 将分
⽀提交的消息发送给各个分⽀事务,各个分⽀事务收到分⽀提交消息后,
会将消息放⼊⼀个缓冲队列,然后直接向 Seata 返回提交成功。之后,每
个本地事务会慢慢处理分⽀提交消息,处理的⽅式为:删除相应分⽀事务
undo_log 记录。之所以只需删除分⽀事务的 undo_log 记录,⽽不需要
再做其他提交操作,是因为提交操作已经在第⼀阶段完成了(这也是 AT
XA 不同的地⽅)。这个过程如下图所示:
分⽀事务之所以能够直接返回成功给 Seata ,是因为真正关键的提交操作
在第⼀阶段已经完成了,清除 undo_log ⽇志只是收尾⼯作,即便清除失败
了,也对整个分布式事务不产⽣实质影响。
2 )若任⼀分⽀事务本地提交失败,则 Seata 决定全局回滚,将分⽀事务回
滚消息发送给各个分⽀事务,由于在第⼀阶段各个服务的数据库上记录了
undo_log 记录,分⽀事务回滚操作只需根据 undo_log 记录进⾏补偿即
可。全局事务的回滚流程如下图所示:
这⾥对图中的 2 3 步做进⼀步的说明:
1 )由于上⽂给出了 undo_log 的表结构,所以可以通过 xid branch_id
来找到当前分⽀事务的所有 undo_log 记录;
2 )拿到当前分⽀事务的 undo_log 记录之后,⾸先要做数据校验,如果
afterImage 中的记录与当前的表记录不⼀致,说明从第⼀阶段完成到此刻
期间,有别的事务修改了这些记录,这会导致分⽀事务⽆法回滚,向
Seata 反馈回滚失败;如果 afterImage 中的记录与当前的表记录⼀致,说
明从第⼀阶段完成到此刻期间,没有别的事务修改这些记录,分⽀事务可
回滚,进⽽根据 beforeImage afterImage 计算出补偿 SQL ,执⾏补偿
SQL 进⾏回滚,然后删除相应 undo_log ,向 Seata 反馈回滚成功。 Seata 的数据隔离性
seata at 模式主要实现逻辑是数据源代理,⽽数据源代理将基于如 MySQL
Oracle 等关系事务型数据库实现,基于数据库的隔离级别为 read
committed 。换⽽⾔之,本地事务的⽀持是 seata 实现 at 模式的必要条件,
这也将限制 seata at 模式的使⽤场景。
写隔离 从前⾯的⼯作流程,我们可以很容易知道,Seata 的写隔离级别是全局ᇿ占
的。 ⾸先,我们理解⼀下写隔离的流程

 

如上所示,⼀个分布式事务的锁获取流程是这样的
1 )先获取到本地锁,这样你已经可以修改本地数据了,只是还不能本地事
务提交
2 )⽽后,能否提交就是看能否获得全局锁
3 )获得了全局锁,意味着可以修改了,那么提交本地事务,释放本地锁
4 )当分布式事务提交,释放全局锁。这样就可以让其它事务获取全局锁,
并提交它们对本地数据的修改了。
可以看到,这⾥有两个关键点
1 )本地锁获取之前,不会去争抢全局锁
2 )全局锁获取之前,不会提交本地锁
这就意味着,数据的修改将被互斥开来。也就不会造成写⼊脏数据。全局
锁可以让分布式修改中的写数据隔离。
写隔离的原则:
⼀阶段本地事务提交前,需要确保先拿到 全局锁
拿不到 全局锁 ,不能提交本地事务。
全局锁 的尝试被限制在⼀定范围内,超出范围将放弃,并回滚本地
事务,释放本地锁。
以⼀个示例来说明:
两个全局事务 tx1 tx2 ,分别对 a 表的 m 字段进⾏更新操作, m
初始值 1000
tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 =
900 。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。
tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 =
800 。本地事务提交前,尝试拿该记录的 全局锁 tx1 全局提交前,该记
录的全局锁被 tx1 持有, tx2 需要重试等待 全局锁
 
tx1 ⼆阶段全局提交,释放 全局锁 tx2 拿到 全局锁 提交本地事务。
如果 tx1 的⼆阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进⾏
反向补偿的更新操作,实现分⽀的回滚。

 

 
此时,如果 tx2 仍在等待该数据的 全局锁 ,同时持有本地锁,则 tx1 的分
⽀回滚会失败。分⽀的回滚会⼀直重试,直到 tx2 全局锁 等锁超时,放
全局锁 并回滚本地事务释放本地锁, tx1 的分⽀回滚最终成功。
因为整个过程 全局锁 tx1 结束前⼀直是被 tx1 持有的,所以不会发⽣
的问题。
读的隔离级别
在数据库本地事务隔离级别 读已提交( Read Committed 或以上的基础
上, Seata AT 模式)的默认全局隔离级别是 读未提交( Read
Uncommitted 如果应⽤在特定场景下,必需要求全局的 读已提交 ,⽬前 Seata 的⽅式是
通过 SELECT FOR UPDATE 语句的代理。
SELECT FOR UPDATE 语句的执⾏会申请 全局锁 ,如果 全局锁 被其他
事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执
⾏)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读
取的相关数据是 已提交 的,才返回。
出于总体性能上的考虑, Seata ⽬前的⽅案并没有对所有 SELECT 语句都
进⾏代理,仅针对 FOR UPDATE SELECT 语句。 Spring Cloud 集成 Seata AT 模式
AT 模式是指 Automatic (Branch) Transaction Mode ⾃动化分⽀事务, 使
AT 模式的前提是
基于⽀持本地 ACID 事务的关系型数据库。
Java 应⽤,通过 JDBC 访问数据库。
seata-at 的使⽤步骤
1 、引⼊ seata 框架,配置好 seata 基本配置,建⽴ undo_log
2 、消费者引⼊全局事务注解 @GlobalTransactional
3 、⽣产者引⼊全局事务注解 @GlobalTransactional
此处没有写完, 尼恩的博⽂,都是迭代模式,后续会持续
Seata TCC 模式
简介
TCC Seata AT 事务⼀样都是 两阶段事务 ,它与 AT 事务的主要区别为:
TCC 对业务代码侵⼊严重
每个阶段的数据操作都要⾃⼰进⾏编码来实现,事务框架⽆法⾃动处
理。 TCC 性能更⾼
不必对数据加 全局锁 ,允许多个事务同时操作数据。

 

Seata TCC 整体是 两阶段提交 的模型。⼀个分布式的全局事务,全局事
务是由若⼲分⽀事务组成的,分⽀事务要满⾜ 两阶段提交 的模型要求,即
需要每个分⽀事务都具备⾃⼰的:
⼀阶段 prepare ⾏为
⼆阶段 commit rollback ⾏为

根据两阶段⾏为模式的不同,我们将分⽀事务划分为 Automatic (Branch)
Transaction Mode TCC (Branch) Transaction Mode .
AT 模式( 参考链接 TBD )基于 ⽀持本地 ACID 事务 关系型数据库
⼀阶段 prepare ⾏为:在本地事务中,⼀并提交业务数据更新和相应回
滚⽇志记录。
⼆阶段 commit ⾏为:⻢上成功结束, ⾃动 异步批量清理回滚⽇志。
⼆阶段 rollback ⾏为:通过回滚⽇志, ⾃动 ⽣成补偿操作,完成数据
回滚。
相应的, TCC 模式,不依赖于底层数据资源的事务⽀持:
⼀阶段 prepare ⾏为:调⽤ ⾃定义 prepare 逻辑。
⼆阶段 commit ⾏为:调⽤ ⾃定义 commit 逻辑。
⼆阶段 rollback ⾏为:调⽤ ⾃定义 rollback 逻辑。 所谓 TCC 模式,是指⽀持把 ⾃定义 的分⽀事务纳⼊到全局事务的管理
中。
第⼀阶段 Try
以账户服务为例,当下订单时要扣减⽤户账户⾦额:

 

假如⽤户购买 100 元商品,要扣减 100 元。
TCC 事务⾸先对这 100 元的扣减⾦额进⾏预留,或者说是先冻结这 100 元:

 

第⼆阶段 Confirm
如果第⼀阶段能够顺利完成,那么说明 扣减⾦额 业务 ( 分⽀事务 ) 最终肯定
是可以成功的。当全局事务提交时, TC 会控制当前分⽀事务进⾏提交,如
果提交失败, TC 会反复尝试,直到提交成功为⽌。
当全局事务提交时,就可以使⽤冻结的⾦额来最终实现业务数据操作:

 

第⼆阶段 Cancel
如果全局事务回滚,就把冻结的⾦额进⾏解冻,恢复到以前的状态, TC
控制当前分⽀事务回滚,如果回滚失败, TC 会反复尝试,直到回滚完成为
⽌。

多个事务并发的情况
多个 TCC 全局事务允许并发,它们执⾏扣减⾦额时,只需要冻结各⾃的⾦
额即可:

SEATA Saga 模式

Saga 模式是 SEATA 提供的⻓事务解决⽅案,在 Saga 模式中,业务流程中
每个参与者都提交本地事务,当出现某⼀个参与者失败则补偿前⾯已经成
功的参与者,⼀阶段正向服务和⼆阶段补偿服务都由业务开发实现。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值