事务从本质上讲就是:逻辑上的一组操作,组成这组操作的各个逻辑单元在不同的服务甚至服务器上,保证它们要成功就都成功,要失败就都失败。
事务的四大特性
提到事务就不得不提事务的四大特性(基本特征) ACID:
并发事务可能会带来的问题
我们实践出真知:举个例子,mysql(5.6.16)
首先创建一张表:
CREATE TABLE `test_account` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`money` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `test_account` VALUES (1, '张三', 1000);
INSERT INTO `test_account` VALUES (2, '李四', 1000);
INSERT INTO `test_account` VALUES (3, '王五', 1000);
查看当前数据库隔离级别(详见下文):
select @@tx_isolation;
# 结果:READ-COMMITTED
# 设置事务的隔离级别
# set tx_isolation='隔离级别';
# 我们简单做下脏读的复现,开启俩个事务,第一个窗口做数据修改,但不提交:
start transaction;
UPDATE test_account set money = money - 100 WHERE id = 1;
# 第二个窗口做数据查询,利用查询到的值去做数据处理:
start transaction;
SELECT money from test_account WHERE id = 1;
结果:1000
那么这时读到的数据是不准确的,这就是脏读
我们解决并发读的问题可以设置隔离级别解决问题:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交 (Read uncommitted) | √ | √ | √ |
读已提交 (Read committed) | × | √ | √ |
可重复读 (Repeatable read) | × | × | √ |
可串行化 (Serializable ) | × | × | × |
Spring传播行为
Spring传播行为 | 介绍 |
REQUIRED | 支持当前事务,如果不存在,就新建一个 |
SUPPORTS | 支持当前事务,如果不存在,就不使用事务 |
MANDATORY | 支持当前事务,如果不存在,抛出异常 |
REQUIRES_NEW | 如果有事务存在,挂起当前事务,创建一个新的事务 |
NOT_SUPPORTED | 以非事务方式运行,如果有事务存在,挂起当前事务 |
NEVER | 以非事务方式运行,如果有事务存在,抛出异常 |
NESTED | 如果当前事务存在,则嵌套事务执行(嵌套式事务) |
事务的传播行为不是jdbc规范中的定义。传播行为主要针对实际开发中的问题
为什么要有分布式事务?
本地事务只能解决同一工程中的事务问题,而现在的场景更加复杂,关系到多个服务,怎么保证要么都成功,要么都失败?
分布式系统异常除了本地事务那些异常之外,还有:机器宕机、网络异常、消息丢失、消息乱序、数据错误、不可靠的TCP、存储数据丢失等,这时候就需要引入分布式事务。
分布式事务出现的场景
分布式事务基础
数据库的 ACID 四大特性,已经无法满足我们分布式事务,这个时候有很多大佬提出一些新的理论。
CAP:
分布式存储系统的CAP原理(分布式系统的三个指标):
CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们无法避免的。所以我们只能在一致性和可用性之间进行权衡,没有系统能同时保证这三点。要么选择CP、要么选择AP。
BASE:
BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。接下来看看BASE中的三要素:
BASE模型是传统ACID模型的反面,不同于ACID,BASE强调牺牲高一致性,从而获得可用性,数据允许在一段时间内的不一致,只要保证最终一致就可以了。
分布式事务解决方案
主流的解决方案如下:
两阶段提交(2PC):
2PC即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段(Prepare phase)、提交阶段(commit phase),2是指两个阶段,P是指准备阶段,C是指提交阶段。
第一阶段:事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交. 第二阶段:事务协调器要求每个数据库提交数据。 其中,如果有任何一个数据库否决此次提交,那么所有数据库都会被要求回滚它们在此事务中的那部分信息。
XA 是一个两阶段提交协议,又叫做 XA Transactions。 目前主流数据库均支持2PC,XA协议。
XA目前在商业数据库支持的比较理想,在mysql数据库中支持的不太理想,mysql的XA实现,没有记录prepare阶段日志,主备切换会导致主库与备库数据不一致。许多nosql也没有支持XA,这让XA的应用场景变得非常狭隘。
TCC补偿式事务:
TCC补偿式事务是一种编程式分布式事务。
TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。TCC模式要求从服务提供三个接口:Try、Confirm、Cancel。
整个TCC业务分成两个阶段完成:
第一阶段:主业务服务分别调用所有从业务的try操作,并在活动管理器中登记所有从业务服务。当所有从业务服务的try操作都调用成功或者某个从业务服务的try操作失败,进入第二阶段。
第二阶段:活动管理器根据第一阶段的执行结果来执行confirm或cancel操作。如果第一阶段所有try操作都成功,则活动管理器调用所有从业务活动的confirm操作。否则调用所有从业务服务的cancel操作。
消息事务+最终一致性:
基于消息中间件的两阶段提交往往用在高并发场景下,将一个分布式事务拆成一个消息事务(A系统的本地操作+发消息)+B系统的本地操作,其中B系统的操作由消息驱动,只要消息事务成功,那么A操作一定成功,消息也一定发出来了,这时候B会收到消息去执行本地操作,如果本地操作失败,消息会重投,直到B操作成功,这样就变相地实现了A与B的分布式事务。
但是A和B并不是严格一致的,而是最终一致的,我们在这里牺牲了一致性,换来了性能的大幅度提升。当然,这种玩法也是有风险的,如果B一直执行不成功,那么一致性会被破坏,具体业务具体分析
总结:
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
结构
Seata有3个基本组件:
生命周期(重点)
引入seata并使用
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。