分布式事务案例
- 库存服务锁定成功,但是在通知订单服务的过程中,网络出现异常,怎么处理?
- 库存服务锁定失败,通知订单服务,回滚数据。
- 库存服务锁定库存成功,用户服务扣减积分失败,怎么回滚?
如何保证微服务之间事务的一致性?
本地事务
- 事务的基本性质
数据库事务的几个特性:原子性(Atomicity)、一致性(Consistency)、隔离性或独立性(Isolation)和持久性(Durability),简称ACID。- 原子性:一系列的操作整体不可拆分,要不同时成功,要么同时失败。
- 一致性:数据在事务的前后,整体一致。
- 隔离性:事务之间相互隔离,不影响。
- 持久性:一旦事务成功,数据一定会持久化到数据库。
- 事务的隔离级别
-
读未提交 READ UNCOMMITED
该隔离级别的事务会读到其他未提交的事务的数据,此现象也称为脏读
。 -
读已提交
一个事务可以读取到另一个已提交的事务,多次读取会造成不一样的结果,此现象称为不可重复读。 oracle和SQL server默认的事务隔离级别
修改记录,会造成不可重复读
。因为重复读取多次,有可能结果不一致。针对update 和 delete 操作。 -
可重复读
该隔离级别是MySQL默认的隔离级别,在同一个事务里,select 的结果是事务开始时间点的状态,因此,同样的select操作读 到的结果会是一致的,mysql不存在幻读问题,因为mysql mvvc机制保证了不会出现幻读问题。
幻读
:一个事务里,两次读取,结果集不一致。针对insert操作。
一个事务里第一次个第二次读取之间,另一个事务插入了数据。 -
序列化
-
- 事务的传播行为
- PROPAGATION_REQUIRED: 如果当前没有事务,就创建一个新事务;如果当前存在事务,就加入该事务,该设置是最常用的设置
- PROPAGATION_SUPPORTS: 支持当前事务,如果当前存在事务,就加入该事务。如果当前不存在事务,就以非事务执行。
- PROPAGATION_MANDATORY: 支持当前事务,如果当前存在事务,就加入该事务。如果当前不存在事务, 就抛出异常。
- PROPAGATION_REQUIRES_NEW:创建新事务,无论当前是否存在事务,都创建新事务。
- PROPAGATION_NOT_SUPPORTED: 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER: 以非事务方式执行,如果当前存在失去,则抛出异常
- PROPAGATION_NESTED: 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
- Spring Boot 中,同一个对象内的事务方法互相调用,事务的设置会失效。
在方法 a() 中调用方法 b() 和 方法 c(),这样会导致方法 b() 和 方法 c()设置的事务属性失效。原因是:事务是用代理对象来控制的。而上面代码中的调用方式,显然是this调用了b()方法和c()方法,而不是代理对象调用的,因此失效了。@Service class TestService @Transactional(timeout = 30) public void a() { b(); c(); } @Transactional(propagation = Propagation.REQUIRED, timeout = 2) public void b() { ... } @Transactional(propagation = Propagation.REQUIRES_NEW, timeout = 20) public void c() { ... } }
解决方法
:- 引入 aop场景启动器
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
- 启动类上开启 aop 动态代理,并对外暴露动态代理对象
@EnableAspectJAutoProxy(exposeProxy = true)
- 代码修改如下:
@Service class TestService @Transactional(timeout = 30) public void a() { TestService o = (TestService)AopContext.currentProxy(); o.b(); o.c(); } @Transactional(propagation = Propagation.REQUIRED, timeout = 2) public void b() { ... } @Transactional(propagation = Propagation.REQUIRES_NEW, timeout = 20) public void c() { ... } }
- 引入 aop场景启动器
分布式事务
- 为什么会有分布式事务?
分布式系统中经常出现机器宕机、网络异常,消息丢失、消息乱序、数据错误、不可靠TCP、存储数据丢失…为了在某些业务场景下出现这些异常,可以保证数据完整性,因此出现分布式事务。 - CAP定理与BASE理论
- CAP定理,指在分布式系统中,以下三要素最多同时只能实现两点,不可能三者兼顾。
1.1 一致性(Consistency)在分布式系统中,同一份数据的所有副本,在同一时刻都有一样的值。
1.2 可用性(Availability)在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。
1.3 分区容错性(Partition tolerance)分布式的存储系统会有很多的节点,这些节点都是通过网络进行通信。而网络是不可靠的,当节点和节点之间的通信出现了问题,此时,就称当前的分布式存储系统出现了分区。分区容错就是,当出现分区问题时,集群还是应该可以用的。
- BASE理论,CAP的一致性是强一致性,分布式系统无法做到强一致性,但是可以做到弱一致性或最终一致性。
2.1 基本可用(Basically Available)基本可用是指分布式系统在出现故障的时候,运行损失部分可用性(如响应时间、功能上的可用)。需要指出的是,基本可用绝不等价于系统不可用。
2.2 软状态(Soft State)指允许系统存在中间状态,而该中间状态不会影响系统的整体可用性。即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
2.3 最终一致性(Eventual Consistency)指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。
2.4 对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性
。如果能容忍后续的部分或者全部访问不到,则是弱一致性
。如果经过一段时间后要求能访问到更新后的数据,则是最终一致性
。
- CAP定理,指在分布式系统中,以下三要素最多同时只能实现两点,不可能三者兼顾。
分布式事务的 4 种解决方案
- 2PC模式
- 第一阶段:事务管理器要求涉及到事务的数据库预提交(precommit)此操作,并反映是否可用提交。
- 第二阶段:事务管理器要求数据库提交数据。其中,如果又任何一个数据库否决此次提交,那么所有数据库都会被要求回滚它们在此事务中提交的信息。
- 柔性事务 - TCC事务补偿性方案
所谓TCC 模式,是指支持把自定义的分支事务纳入到全局事务的管理中。
TCC 是 Try,Confirm,Cancel两个单词的缩写,其业务含义如下:
Try:主要对业务系统进行检测以及预留业务资源
Confirm:执行业务提交,try阶段完成后,执行此阶段。默认如果try阶段执行成功,此阶段就一定能成功。
Cancel:执行业务回滚,try阶段执行失败,执行此阶段。对 try 阶段锁定的资源释放。
- 柔性事务 - 最大努力通知型方案
- 柔性事务 - 可靠消息 + 最终一致性方案(异步确保型)
转载
4.1 MQ发送方发送远程事务消息到MQ Server。
4.2 MQ Server给予响应, 表明事务消息已成功到达MQ Server。
4.3 MQ发送方Commit本地事务。
4.4 若本地事务Commit成功, 则通知MQ Server允许对应事务消息被消费; 若本地事务失败, 则通知MQ Server对应事务消息应被 丢弃。
4.5 若MQ发送方超时未对MQ Server作出本地事务执行状态的反馈, 那么需要MQ Servfer向MQ发送方主动回查事务状态, 以决定事务消息是否能被消费。
4.6 当得知本地事务执行成功时, MQ Server允许MQ订阅方消费本条事务消息。
4.7 需要额外说明的一点, 就是事务消息投递到MQ订阅方后, 并不一定能够成功执行. 需要MQ订阅方主动给予消费反馈(ack)。
4.8 如果MQ订阅方执行远程事务成功, 则给予消费成功的ack, 那么MQ Server可以安全将事务消息移除。
4.9 如果执行失败, MQ Server需要对消息重新投递, 直至消费成功。