分布式事务

分布式事务

啥是事务

数据库事务(简称事务)是数据库执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

如商品订单逻辑:

  • 查询商品的库存
  • 扣减商品的库存
  • 生成订单

事务拥有以下四个特性,习惯上称为 ACID 特性:

  • 原子性(Atomicity):事务作为一个整体被执行,操作序列要么全部执行,要么全部不执行,记录之前的版本,允许回滚;
  • 一致性(Consistency):一致性是指事务使得系统从一个状态转换到另一个一致的状态,事务的一致性决定了一个系统设计和实现的复杂程度,也导致了事务的不同隔离级别。事务的开始和结束之间的中间状态不会被其他事物看到,例如商品库存扣减时,查询库存的结果应该是扣减事务提交之前的数据状态;
  • 隔离性(Isolusion):多个事务并发执行时,并发事务之间户互相影响的程度,它是事务一致性的延伸,通常通过适当破坏一致性来提升系统的并行度,它通常又分为以下几种:
    • 读未提交:A可以读到B未提交的事务操作;(所有并发事务问题都会发生)
    • 读已提交:A只能读到B已提交的事务操作;(解决脏读)
    • 可重复读:A不能读到B已提交的事务操作;(解决脏读、不可重复读,mysql默认隔离级别)
    • 序列化读:A等待B事务完成前阻塞;(解决脏读、不可重复读、幻读 [只针对数据新增])
  • 持久性(Durability):已被提交的事务对数据库的需改应该永久保存在数据库中,每一次的事务提交后的数据保证不丢失;


事务的传播属性

  • propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
  • propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
  • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。
  • propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
  • propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
  • propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。


啥是分布式事务

分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同的节点上,且属于不同的应用,分布式事务要么保证这些操作要么全部成功、要么全部失败。本质上说,分布式事务就是为了保证不同数据库的数据一致性


CAP定理

CAP定理又被称作布鲁尔定理,说的是对于所有的架构,只能同时满足CAP要求中的两个。

image-20200503210231505

  • C(一致性):对某个指定的客户端,读操作返回最新的写操作结果。对于数据分布在不同节点上的数据来说,如果某个节点更新了数据,在其他节点若能读取到这个最新的数据,那么就称该系统为强一致性;如果某个节点没有读取到,那就是分布式不一致。
  • A(可用性):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应),可用性的两个关键指标是 合理的时间 和 合理的响应。合理的时间是指请求不能被无线阻塞,应该在合理的时间内给出返回;合理的响应指的是系统应该明确返回结果并且结果是正确的,比如正常结果或提示“系统繁忙,请稍后操作”等。
  • P(网络分区容错性):当出现网络分区后,系统能够继续工作。打个比方,这里集群有多台机器,有机器网络出现了问题,但是这个集群仍然可以正常工作。

CAP定理可以分为下面三种情况:

  • CA系统:单机系统
  • CP系统:如dubbo框架,zk在选举时dubbo是不能对外提供服务的(!A)
  • AP系统:如spring cloud框架,通过熔断机制保证了A,但是不能很好保证C


分布式事务的解决方案

  • XA两端提交(强一致)
  • TCC三段提交(强一致)
  • 本地消息表(MQ+Table,最终一致)
  • 事务消息(RocketMQ [alibaba],最终一致性)
  • Seata(alibaba)
  • RabbitMQ的ACK机制实现分布式事务


两阶段提交(2PC)

X/Open组织(即现在的Open Group)定义了分布式事务处理模型,称之为XA协议

image-20200503224013592

优点:

  • 尽量保证了数据的强一致性(无法完全保证),适合对数据强一致性很高的关键领域

缺点:

  • 同步阻塞问题:执行过程中,所有节点都是实物阻塞型的,当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态;
  • 单点故障:由于协调者的重要性,一旦协调者出现故障,参与者会一直阻塞下去。尤其是在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定资源的状态中,而无法继续完成事务操作。(如果是协调者挂了,可以重新选举一个协调者,但无法解决因为协调者宕机导致的参与者还处于阻塞状态的问题)
  • 数据不一致:在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者这时候协调者发生了故障,这会导致只有一部分参与者接收到了commit请求,这就会导致一部分接收到commit请求的参与者提交了事务,而未收到commit请求的参数者不会提交事务,这样就存在数据不一致的情况。
  • 二阶段无法解决的问题:协调者在发出commit消息后宕机,而唯一接收这条消息的参与者同时也宕机了,那么即便协调者通过选举机制产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否已经提交。


三阶段提交(3PC)

image-20200503231839717

优点:相比于2PC,3PC主要解决的是单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息,他就会默认执行commit(超时机制),而不会一直持有事务资源并处于阻塞状态。

缺点:会导致数据一致性问题,由于网络等原因,协调者发送的中断响应没有被参与者收到,那么参与者在等待超时之后执行了commit操作,这样就和其他接收到中断命令并执行回滚的参与者之间存在数据不一致的情况。


MQ事务消息

有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于二阶段提交,但是市面上一些主流的MQ是不支持事务消息的,比如RabbitMQ和kafka都不支持(他们都基于ACK机制)。

以阿里的RocketMQ中间件为例,流程为:

  1. 发送一个事务消息,这个时候,RocketMQ将消息的状态标记为Prepared,注意此时这条消息消费者是无法消费到的;
  2. 执行业务代码逻辑;
  3. 确认发送消息,RocketMQ将消息状态置为可消费,这个时候消费者才能真正消费到这条消息;
  4. 如果步骤3的确认消息发送失败,RocketMQ会定期扫描消息集群中的事务消息,如果发现了Prepared消息,它会向消息发送端(生产者)确认。RocketMQ会根据发送端设置的策略来决定回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或者同时失败;

image-20200504111346723

Seata

seata简介

2019年1月,阿里巴巴中间件团队发起了开源项目Fescar(Fast & Easy Commit and Rollback),和社区一起共建开源分布式事务解决方案。Fescar的愿景是让分布式事务的使用能够像本地事务的使用一样,简单和高效,并逐步解决开发者们遇到的分布式事务方面的所有难题。

Fescar开源以后,蚂蚁金服参与Fescar社区参与共建,并在Fescar 0.4.0 版本中贡献了TCC模式。

为了打造更中立、更开放、生态更加丰富的分布式事务开源社区,经过社区核心成员的投票,大家决定对Fescar进行品牌升级,更名为Seata,意为 Simple Extensible Autonomous Transaction Architecture,是一套一站式分布式事务解决方案。

Seata融合了阿里巴巴和蚂蚁金服在分布式事务技术上的积累,并沉淀了新零售、云计算和新金融等场景下的丰富的实践经验。


核心组件

  • Transaction Manager(TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;(告知全局所有事务的参与者,到底进行什么样的操作)

  • Transaction Coordinator(TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;(存储了所有服务的本地事务状态)

  • Resource Manager(RM):控制分支事务,负责分支注册,状态汇报,并接收事务协调器的指令,驱动分支事务(本地事务)的提交和回滚;


整体工作流程

  1. TM向TC发送申请开启一个全局事务,去哪聚事务创建成功并生成一个全局唯一的事务ID(XID),XID在微服务调用链路的上下文中进行传播;
  2. RM向TC注册分支事务,接着执行这个分支事务并提交事务(重点是RM在此阶段就已经执行了本地事务的提交/回滚),最后将执行结果汇报给TC;
  3. TM根据TC中所有分支事务的执行情况,发起全局提交或者回滚决议;
  4. TC协调XID下管辖的全部分支事务完成提交或者回滚请求;

Typical Process


使用方式

  1. @GlobalTransactional
  2. 数据源 DataSource 换成 Seata提供的 DatasourceProxy


Seata支持的模式

支持两种常见的分布式事务实现方案,AT 和 TCC

image-20200504114853604


AT模式

第一阶段:本地数据备份阶段

Seata AT模式是基于XA事务演进而来的一个分布式事务中间件,XA是基于数据库实现的分布式事务协议,本质和两阶段提交一样,需要数据库支持,Mysql5.6以上版本支持XA协议,其他数据库Oracle、DB2也实现了XA接口。

AT模式分为两个阶段,如下:

  1. Seata的JDBC数据源代理通过对业务SQL的解析,把业务数据在变化前后的数据镜像组织成回滚日志(XID、分支事务ID、变化前的数据、变化后的数据]);
  2. 将回滚日志存入一张日志表 UNDO_LOG(需要手动创建),并对UNDO_LOG这张表中的数据形成行锁(for update)
  3. 若锁定失败,说明有其他事务在操作这条记录,它会在一定时间内重试,重试失败则回滚本地事务,并向TC汇报本地事务执行失败;


第二阶段:全局事务提交或回滚阶段

全局提交:

  1. 所有分支事务此时已经完成提交,所有分支事务提交都正常;
  2. TM从TC获知后决议执行全局提交,TC异步通知所有RM释放UNDO_LOG表中的行锁,同时清理掉UNDO_LOG表中刚才释放锁的那条数据;

全局回滚:

  1. 若任何一个RM阶段事务提交失败,通知TC提交失败;
  2. TM从TC获知后决议执行全局回滚,TC向所有RM发送回滚请求;
  3. RM通过XID和BranchId找到相应的回滚日志记录,通过回滚记录生成反向的更新SQL并执行,以完成分支的回滚,同时释放锁,清除UNDO_LOG表中释放锁的那条数据;


TCC模式

Seata也针对TCC做了适配兼容,基本思路就是使用侵入业务的补偿以及事务管理器的协调来达到全局事务一起提交或回滚的目的。

image-20200504122539887