事务的ACID原则:
1. 原子性:事务中的所有操作要么同时成功,要么同时失败。
2. 隔离性:对同一资源操作的事务不能同时发生。
3. 一致性:要保证数据库内部完整性约束,声明性约束。
4. 持久性:对数据库做的一切修改将永久保存,不过是否出现故障。
在分布式系统下,一个业务跨越多个服务或数据源,每个服务都是一个分支事务,要保证所有分支事务最终状态一致,这样的事务就是分布式事务。
分布式事务问题产生的原因就是每一个 服务都是 独立的,每一个服务的 事务也都是 独立地。
分布式系统有三个指标:
1.Consistency(一致性):
用户访问分布式系统的任意节点,得到的数据必须一致。
2.Availability(可用性):
用户访问集群中的任意健康节点,必须能得到响应,而不是超时或拒绝。
Partition tolerance(分区容错性):
Partition(分区):因为网络故障或其他原因导致分布式系统中的部分节点与其他节点失去连接,形成独立分区。
tolerance(容错):在集群出现分区时,整个系统也要持续对外提供服务。
但是分布式系统无法同时满足这三个指标,这个结论叫做CAP定理。
总结:
分布式系统节点通过网络连接,一定会出现分区问题(P),当出现分区问题时,系统的一致性(C)和可用性(A)就无法同时满足。
拓展:
elasticsearch集群是CP还是AP?
当网络出现故障时,有节点和其他节点断开连接的时候,我们的es集群处于一个警告的状态,那个出现了故障的节点过了一段时间以后就会从集群中剔除,而这个节点上原来的数据分片会分散到其他健康的节点上去,保证数据的一致,因此是低可用性,高一致性,属于CP。
是对CAP的一种解决思路,包括三个思想
Basically Available(基本可用):分布式系统在出现故障时,允许损失部
分可用性,即保证核心可用。
Soft State(软状态):在一定时间内,允许出现中间状态,比如临时的不
一致状态。
Eventually Consistent(最终一致性):虽然无法保证强一致性,但是在软
状态结束后,最终达到数据一致。
分布式事务最大的问题是各个子事务的一致性问题,因此可以借鉴CAP定理和BASE理论:
AP模式:各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致。
CP模式:各个子事务执行后互相等待,同时提交,同时回滚,达成强一致。但事务等待过程中,处于弱可用状态。
解决分布式事务,各个子系统之间必须能感知到彼此的事务状态,才能保证状态一致:
需要一个事务协调者来协调每一个事务的参与者(子系统事务)。这里的子系统事务,称为分支事务;有关联的各个分支事务在一起称为全局事务。
总结:
全局事务:整个分布式事务
分支事务:分布式事务中包含的每个子系统的事务
最终一致思想:各分支事务分别执行并提交,如果有不一致的情况,再想办法恢复数据。
强一致思想:各个分支事务执行完业务不要提交,等待彼此结果。而后统一提交或回滚。
开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案。 http://seata.io/
Seata事务管理中有三个重要的角色
TC-事务协调者 |
维护全局和分支事务的状态,协调全局事务提交或回滚。 |
TM-事务管理器 |
定义全局事务的范围,开始全局事务,提交或回滚全局事务。 |
RM-资源管理器 |
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。 |
对于一个分布式事务,他一定有一个入口方法,在这个入口方法中会去调用其他的微服务,每
调用一个微服务,就是一个分支事务,调了多少个,全局事务里就包含多少个分支事务,因此在
这个入口方法里就定义了全局事务的范围。
TM就会去监控这个入口的方法(代理这个入口方法),这样TM就知道了事务里总共有多少个分支,整个范围就确定下来了。
1.当入口方法被执行时,TM会首先拦截这个方法的执行,会去向TC发起一个请求,去注册这个全局事务。
2.然后TM去执行这个入口方法的业务逻辑去调用每个微服务,到了微服务里面,分支事务就要开始执行了。
3.RM(管理分支事务资源)会代理分支业务,然后在分支业务执行的时候先拦截下来,去TC注册当前分支事务,然后RM去执行业务SQL(只执行不提交).
4.执行完后RM报告分支事务状态给TC,这样TC就知道了每个分支事务的执行情况了.
5.等TM这边入口方法全部执行完毕,就可以去提交事务给TC了,然后TC去检查分支事务状态,如果发现每个分支事务都成功了就会告诉RM提交,如果有失败的就告诉RM回滚。
XA模式: 强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入。
TCC模式:最终一致的分阶段事务模式,有业务侵入。
AT模式 :最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
SAGA模式:长事务模式,有业务侵入
1.解压seata-server-1.4.2
2.修改registry.conf配置文件,写选择的服务注册中心和服务配置中心
registry {
type = "nacos"
nacos {
application = "seata-tc-server"
serverAddr = "127.0.0.1:8848"
group = "DEFAULT_GROUP"
namespace = ""
cluster = "WH"
username = "nacos"
password = "nacos"
}
}
config {
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
}
3.引入seata相关依赖
1.4.2
com.alibaba.cloud
spring-cloud-starter-alibaba-seata
io.seata
seata-spring-boot-starter
io.seata
seata-spring-boot-starter
${seata.version}
4.配置application.yml,让微服务通过注册中心找到seata-tc-server
seata: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
# 参考tc服务自己的registry.conf中的配置,
# 包括:地址、namespace、group、application-name 、cluster
registry:
type: nacos
nacos: # tc
server-addr: 127.0.0.1:8848
namespace: ""
group: DEFAULT_GROUP
application: seata-tc-server # tc服务在nacos中的服务名称
username: nacos
password: nacos
tx-service-group: seata-demo # 事务组,根据这个获取tc服务的cluster名称
service:
vgroup-mapping: #事务组与cluster的映射关系
seata-demo: WH
XA规范是X/O组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA规范描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对XA规范提供了支持。
(mysql和Oracle都实现了这个标准)
RM一阶段的工作:
1.注册分支事务到TC
2.执行分支业务sql但不提交
3.报告执行状态到TC
TC二阶段的工作
1.TC检测个分支事务执行状态
a.如果都成功,通知所有RM提交事务
b.如果都失败,通知所有RM回滚事务
RM二阶段的工作
接收TC指令,提交或回滚事务。
优点:
1.强一致性。(一阶段事务只执行不提交,到了二阶段每个事务都执行完了才会一起提交或回滚,每个事务都是ACID的,并且大家互相等待,因此整个事务都是ACID的。)
2.主流很多数据库都已经实现了这个模式,数据库内部已经提供好了对应的接口,比较容易
实现分布式事务的效果,并且没有代码侵入
缺点:
1.事务只执行不提交等待的过程中需要占用数据库锁,如果这个业务耗时较长,整个过程中 大家都不能提交干等着,占用着数据库资源,占用着数据库锁,别人都没有办法访问,造成资源的浪费。性能比较差,可用性降低。
2.依赖数据库底层实现,用起来简单,但是如果数据库不支持,就没法做了。
1.修改application.yml文件(每个参与事务的微服务),开启XA模式
data-source-proxy-mode: XA #开启数据源代理的XA模式
2.给发起全局事务的入口方法添加@GlobalTransactional注解
@Override
@GlobalTransactional
public Long create(Order order) {
// 创建订单
orderMapper.insert(order);
try {
// 扣用户余额
accountClient.deduct(order.getUserId(), order.getMoney());
// 扣库存
storageClient.deduct(order.getCommodityCode(), order.getCount());
} catch (FeignException e) {
log.error("下单失败,原因:{}", e.contentUTF8(), e);
throw new RuntimeException(e.contentUTF8(), e);
}
return order.getId();
}
同样是分阶段提交的事务模型,不过却 弥补了XA模型中资源锁定周期过长的缺陷。
1.TM开启全局事务,完成事务注册,
2.调用每一个分支,每一个分支的RM都去注册分支事务,并且执行本地业务sql直接提交,执行业务sql时会由RM拦截并形成快照(undolog)记录更新前后快照,
3.然后报告自己的事务状态,分支都执行完后,TM通知TC提交或回滚。TC检查分支事务状态,如果都成功了就异步通知RM删除log,如果有事务失败了就通知RM基于log去做数据的恢复然后删除log。
阶段一RM的工作:
1.注册分支事务
2.记录undo-log(数据快照)
3.执行业务sql并提交
4.报告事务状态
阶段二提交时RM的工作:
删除undo-log即可
阶段二回滚时RM的工作:
根据undo-log恢复数据到更新前再删除
注意:
XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
XA模式依赖数据库机制实现回滚,AT模式利用数据快照实现数据回滚。
XA模式强一致,AT模式最终一致。
问题:AT模式性能得到了提升,在一阶段的时候事务会直接提交,因此资源锁定周期较短,所以性能更好。但是提前释放了资源,没有锁,导致在并发访问的情况下会存在一些安全问题。
AT模式的脏写问题:
事务开启后先获取数据库锁,RM拦截业务执行获取DB锁并保存快照(用于将来的数据恢复)。然后执行业务sql,提交事务释放锁,一阶段结束要去执行二阶段了,但是在同一时刻由于是并发,另一个事务也来执行这个业务,等第一个事务释放了锁,这个事务立马拿到了锁并保存快照,执行业务sql,
提交事务释放锁,这时才轮到第一个事务的第二阶段拿到锁, 假如回滚,根据之前快照恢复数据,这样的话第二个事务的修改就没用了,这个叫丢失更新。原因就是事务没有做到隔离,阶段一释放了锁,其他的事务插入了进来,然后阶段二再去执行时,就出现了脏数据。 阶段一和阶段二应该需要整体处于一个锁定状态就不会出现这个问题了。
解决方式:
全局锁(由TC记录当前正在操作某行数据的事务,该事务持有全局锁,具备执行权。)
AT模式的写隔离:(全局锁)
事务1获取锁保存快照,执行业务sql后, 尝试获取一个全局锁,再提交事务释放锁;然后事务2获得锁保存快照,执行业务sql后,也要去尝试获取一个全局锁, 但是表中已经有人获取了所以无法获取,然后就卡在这里了不断重试获取全局锁,但是事务1需要等待自己的二阶段结束才会去释放全局锁,而事务一的二阶段需要先获取锁才能执行,需要等待事务2释放锁。结果就造成了事务1在等待事务2释放DB锁,事务2在等待事务1释放全局锁,两事务互相等待产生了 死锁。
所以 seata对获取全局锁做了一个限制重试默认30次,间隔10ms,也就是最多等待300ms,而DB锁的等待往往比较长,所以会先释放全局锁,事务2任务超时回滚并释放DB锁。然后事务1的二阶段就可以成功拿到锁了。
AT模式的全局锁由TC去记录,光是记录一下谁在访问。而 XA模式的锁是执行完业务不提交数据库的锁,数据库的锁不释放任何人都无法访问这行数据(增删改查),但TC的锁只是记录操作这张表的这行数据的全局事务由Seata来管理, 如果有不是seata管理的事务去操作这行数据不受影响 。全局锁锁的是这行数据的余额,那么除余额以外的其他字段都可以修改。锁定的资源范围小很多。 但是极端情况下,这个事务在修改money的过程中,其他不属于seata管理的事务也来修改money,因为不需要获取全局锁,也有可能出现脏写问题。
(这种情况出现的可能性非常低)
1.全局事务的执行大多数情况下是成功的二阶段是提交而非回滚。
2.分布式事务本身业务耗时就比较长,因此它的并发本身就比较低,并发比较低的情况下两个事务如此巧合的可能性也比较低。
3.更何况我们在做业务的时候应该尽可能地去避免多个事务在去操作了同一字段。这种可能性在业务上应该要想办法去避免,如果真的出现了把另外一个事务也交给seata去管理。
解决方法: seata的内部保存快照保存了两个快照,一个更新前的(before-image),一个更新后的(after-image)。
当要数据恢复时,不仅要拿到更新前的快照(用于恢复),也要拿到更新后的快照(判断和当前数据库的数据是否一致),如果一致就说明在二阶段和一阶段之间没人做过动作,如果不一致说明有人做了手脚,那么seata就不能修改,无法恢复。记录异常,发送警告,人工介入。
优点:
1.一阶段完成直接提交事务,释放数据库资源,性能比较好
2.利用全局锁实现读写隔离
3.没有代码侵入,框架自动完成回滚和提交
缺点:
1.两个阶段之间属于软状态,属于最终一致
2.框架的快照功能会影响性能,但比XA模式要好得多
快照和全局锁都是保存在数据库中的。
1.导入lock_table数据表到TC服务关联的数据库,undo_log表导入到微服务关联的数据库。
2.实现AT模式:将数据源代理的模式改成AT模式后重启即可。
1.数据表
/*
Navicat Premium Data Transfer
Source Server : local
Source Server Type : MySQL
Source Server Version : 50622
Source Host : localhost:3306
Source Schema : seata_demo
Target Server Type : MySQL
Target Server Version : 50622
File Encoding : 65001
Date: 20/06/2021 12:39:03
*/
use seata_demo;
use seata;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'global transaction id',
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` longblob NOT NULL COMMENT 'rollback info',
`log_status` int(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` datetime(6) NOT NULL COMMENT 'create datetime',
`log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Compact;
-- ----------------------------
-- Records of undo_log
-- ----------------------------
-- ----------------------------
-- Table structure for lock_table
-- ----------------------------
DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table` (
`row_key` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`xid` varchar(96) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`branch_id` bigint(20) NOT NULL,
`resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`table_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`pk` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`gmt_create` datetime NULL DEFAULT NULL,
`gmt_modified` datetime NULL DEFAULT NULL,
PRIMARY KEY (`row_key`) USING BTREE,
INDEX `idx_branch_id`(`branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
2.修改yml
data-source-proxy-mode: AT #开启数据源代理的AT模式
与AT模式非常相似,每个阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法。
Try: 资源的检测和预留
Confirm: 完成资源操作业务;要求Try成功Confirm一定要能成功。
Cancel: 预留资源释放,可以理解为try的反向操作。
TCC模式在第一阶段完成资源的预留了以后,无论是提交和回滚都是在操作自己预留的部分。
TCC和AT共同点(最终一致):TCC和AT模式在第一阶段都是各自执行事务和提交,释放数据库锁,性能上都非常好,但是会出现一些事务失败的情况导致事务不一致,只有在二阶段完成了提交或回滚才会保证数据的一致性。
隔离性:从隔离性上来讲AT需要加全局锁来实现隔离,但是在TCC模式下不需要隔离:因为事务只会操作自己预留的部分,事务和事务之间没有影响。由于不用加锁就实现了隔离,性能比AT模式又好了很多
TM开启全局事务并注册到TC,TM通知每一个分支事务去执行,分支事务被RM拦截去TC注册分支事务,而后去执行Sql,第一阶段是做try预留资源直接提交,然后去报告事务的状态给TC,之后二阶段TM去通知TC事务结束了,TC去检查各个分支事务的状态,如果都成功就提交Confirm,如果有失败的就回滚cancel。
TCC模式的每个阶端:
Try: 资源的检查和预留
Confirm: 业务执行和提交
Cancel: 预留资源的释放
优点:
1.一阶段完成直接提交事务,释放数据库资源,性能好
2.相比AT模型,无需生成快照,无需使用全局锁,性能最强
3.不依赖数据库事务,而是依赖补偿操作,可以用于非事务性数据库redis
缺点:
1.有代码侵入,需要人为编写try,Confirm和Cancel接口,太麻烦
2.软状态,事务是最终一致
3.需要考虑Confirm和Cancel的失败情况,做好幂等处理
注意:并不是所有的事务都适合用TCC来实现。
分布式事务是可以AT模式和TCC模式混合使用的。(都属于seata内部的实现,而且都是分阶段提交,一阶段直接提交和二阶段提交或回滚)
需要保证:
1.保证confirm,cancel接口的幂等性(幂等性:一个业务接口调用一次和调用多次做好达成的效果是一致的,不会因为重复调用而出问题。)
2.允许空回滚
(当某分支事务的try阶段堵塞时可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,也不能报错因为seata会认为cancel出问题了会重试,这就是空回滚)
3.拒绝业务悬挂
(对于已经空回滚了的业务,如果后来又继续执行了try,会导致数据库中数据冻结了,可是事务却已经结束了不会再执行confirm或cancel,相当于只执行了一半。这就是业务悬挂。应该阻止执行空回滚后的try操作,避免悬挂。)
1.为了实现空回滚,防止业务悬挂,以及幂等性要求。我们必须在完成业务操作的同时,记录当前事务id和执行状态,为此设计一张表account_freeze表。
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for account_freeze_tbl
-- ----------------------------
DROP TABLE IF EXISTS `account_freeze_tbl`;
CREATE TABLE `account_freeze_tbl` (
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`freeze_money` int(11) UNSIGNED NULL DEFAULT 0,
`state` int(1) NULL DEFAULT NULL COMMENT '事务状态,0:try,1:confirm,2:cancel',
PRIMARY KEY (`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;
-- ----------------------------
-- Records of account_freeze_tbl
-- ----------------------------
SET FOREIGN_KEY_CHECKS = 1;
2.TCC的Try,Confirm,Cancel方法都需要在接口中基于注解来声明。
@LocalTCC
public interface AccountTCCService {
/**
* Try逻辑,@TwoPhaseBusinessAction中的name属性要与当前方法名一致,用于指定Try逻辑对应的方法
* **/
@TwoPhaseBusinessAction(name = "deduct",commitMethod = "confirm",rollbackMethod = "cancel")
void deduct(@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "money") int money);
/**
* 二阶段confirm确认方法、可以另命名,但要保证与commitMethod一致
* @param ctx 上下文,可以传递try方法的参数
* @return boolean 执行是否成功 */
boolean confirm(BusinessActionContext ctx);
/**
* 二阶段回滚方法,要保证与rollbackMethod一致
* */
boolean cancel(BusinessActionContext ctx);
}
3.Try业务:
1.记录冻结金额和事务状态到account_freeze表。
2.扣减account表可用金额。
@Override
@Transactional
public void deduct(String userId, int money) {
//0.获取事务id
String xid = RootContext.getXID();
//拒绝业务悬挂:判断freeze中是否有冻结记录,如果有,就是执行过cancel了,拒绝此业务。
AccountFreeze ordFreeze = freezeMapper.selectById(xid);
if (ordFreeze != null){
return;
}
//1.扣减可用余额
accountMapper.deduct(userId,money);
//记录冻结金额,事务状态
AccountFreeze freeze = new AccountFreeze();
freeze.setXid(xid);
freeze.setUserId(userId);
freeze.setFreezeMoney(money);
freeze.setState(AccountFreeze.State.TRY);
freezeMapper.insert(freeze);
}
4.Confirm业务:
1.删除对应冻结记录。
@Override
public boolean confirm(BusinessActionContext ctx) {
//1.获取事务id
String xid = ctx.getXid();
//2.根据id删除冻结记录
int count = freezeMapper.deleteById(xid);
return count==1;
}
5.Cancel业务:
1.修改冻结金额为0,设置state为cancel。
2.修改account表,恢复可用金额。
@Override
public boolean cancel(BusinessActionContext ctx) {
//0.查询冻结金额
String xid = ctx.getXid();
String userId = (String) ctx.getActionContext("userId");
AccountFreeze freeze = freezeMapper.selectById(xid);
//空回滚的判断,判断freeze是否为null,为null证明try没执行,需要空回滚。
if (freeze==null){
//证明try没有执行需要空回滚
AccountFreeze accountFreeze = new AccountFreeze();
accountFreeze.setXid(xid);
accountFreeze.setUserId(userId);
accountFreeze.setFreezeMoney(0);
accountFreeze.setState(AccountFreeze.State.CANCEL);
freezeMapper.insert(accountFreeze);
return true;
}
//幂等判断
if (freeze.getState()==AccountFreeze.State.CANCEL){
//已经处理过一次cancel了,无需处理
return true;
}
//1.恢复可用余额
accountMapper.refund(freeze.getUserId(),freeze.getFreezeMoney());
//2.修改状态和冻结余额
freeze.setFreezeMoney(0);
freeze.setState(AccountFreeze.State.CANCEL);
int count = freezeMapper.updateById(freeze);
return count==1;
}
如何判断是否空回滚:
1.cancel业务中,根据account_freeze表中对应的数据判断是否执行了try,如果没有执行,空回滚。
如何避免业务悬挂:
1.try业务中,根据account_freeze表中的对应数据判断是否执行了Cancel,如果执行了就拒绝try业务。
seata提供的长事务解决方案。分为两个阶段:
一阶段:直接提交本地事务
二阶段:成功则什么都不做;失败则通过编写补偿业务来回滚
但是既没有做资源的预留,又没有全局锁:没有隔离性,事务与事务之间可能存在脏写。
优点:
1.事务参与者可以基于事件驱动实现异步调用,吞吐高。
2.一阶段直接提交事务,无锁,性能好
3.不用编写TCC中的三个阶段,实现简单
缺点:
1.软状态持续事件不确定,时效性差。
2.没有锁,没有事务隔离,会有脏写。
XA |
AT |
TCC |
SAGA |
|
一致性 |
强一致 |
弱一致 |
弱一致 |
最终一致 |
隔离性 |
完全隔离 |
基于全局锁隔离 |
基于资源预留隔离 |
无隔离 |
代码侵入 |
无 |
无 |
有,要编写三个接口 |
有,要编写状态机和补偿业务 |
性能 |
差 |
好 |
非常好 |
非常好 |
场景 |
对一致性、隔离性有高要求的业务 |
基于关系型数据库的大多数分布式事务场景都可以 |
1.对性能要求较高的事务。 2.有非关系型数据库要参与的事务 |
1.业务流程长、业务流程多 2.参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口 |