再谈事务与分布式事务

时隔两年,再从新学习一下事务以及分布式事务。

事务

什么是事务?

事务是恢复和并发控制的基本单位。

事务的ACID特性
  • Atomic(原子性):事务内操作要么全成功要么全失败;
  • Consistent(一致性):事务完成后所有数据的状态都是一致的(均发生变更);
  • Isolation(隔离性):事务之间互不影响;
  • Duration(持久性):事务的修改被持久化保存。

原子性

原子是化学中不可再分的最小单位。

事务中的原子性可以理解为事务操作的不可拆分,

也就是说,同一个事务内部的所有操作不可拆分,要么全部执行成功,要么全部执行失败。

原子性的另外一层含义是事务内的操作可回滚,也就是存在部分执行失败的情况下恢复数据。

隔离性

隔离是用于解决事务并发的资源竞争问题。

是用来解决隔离的唯一手段,可分为悲观锁乐观锁

  • 悲观锁:对事务对象加锁,实现顺序执行(Serializable隔离级别属于这种情况);
  • 乐观锁:基于多版本冲突检测,实现并发执行;

多版本数据隔离的实现又可划分:基于物理存储的实现基于内存存储的实现

  • 基于物理存储:多版本数据更新写入磁盘(持久化),支持异常恢复;
  • 基于内存存储:多版本数据更新仅存储在内存,性能好,异常无法恢复。

Undo Log是MySQL事务隔离性的关键。

持久化

持久化是数据一致性的最终保证。

数据库的持久化存在两层含义:数据落盘数据备份

  • 数据落盘:事务变更写入磁盘存储;
  • 数据备份:事务变更不仅进入本地磁盘存储,同时同步到数据库的副本;

数据落盘一般会包含两个阶段:写入缓存写入磁盘

缓存的使用可以带来性能的提高,但也会带来数据丢失的风险。

一致性

没有解决事务操作的原子性,就无法实现事务数据变更的一致性;

没有解决事务并发执行的问题,就无法实现事务操作的隔离,也无法实现事务数据变更的一致性;

没有合理的持久化策略,即使保证了原子性,但没有写入磁盘或无法从异常中恢复,也无法实现事务数据变更的一致性;

因此,原子性隔离性持久化是实现一致性的基础。

事务的隔离级别
  • 脏读:读取到未提交的数据。
  • 不可重复度:数据的修改。
  • 幻读:数据的增加。

数据库事务的隔离级别分为4种,由低到高分别为

  • Read uncommitted(读未提交),存在问题:脏读、不可重复读、幻读
  • Read committed(读已提交) ,存在问题:不可重复读、幻读
  • Repeatable read(可重复读),存在问题:幻读
  • Serializable(序列化)

Read committedRepeatable read隔离级别下,MySQL的InnoDB使用MVCC来解决不可重复读幻读的问题。

事务的分类

事务按照一致性的强弱可以划分为:传统事务柔性事务

柔性事务允许系统存在中间状态,这个中间状态又不会影响系统整体可用性。

柔性事务的实现方式包括:记录日志+补偿消息(多次重试,需要幂等处理)CAS(乐观锁)不断重试

分布式事务

分布式事务是指事务的参与者位于分布式系统的不同节点。

分布式事务主要的关注点在于一致性(分布式系统中不同节点的状态同步)

原子性隔离性持久性是由分布式系统中不同的事务参与节点来保证。

实现方式有

  • 2PC(两阶段提交)
  • 3PC(三阶段提交)
  • TCC(Try Confirm Cancel、补偿事务)
  • 本地消息表(BASE)
  • 事务消息(BASE)
XA

XA是由X/Open组织提出的分布式事务规范,是实现2PC3PC的基础。

组成

  • 事务管理器(TM):分布式事务的协调者,负责各个资源管理器的本地事务的提交与回滚;
  • 资源管理器(RM):分布式事务的参与者,管理参与者本地事务资源。
2PC

2PC(两阶段提交)是通过事务执行事务提交两个阶段来实现分布式事务。

2PC中必然存在一个协调者,用于协调分布式系统中事务的发起事务提交/回滚

2PC

步骤如下:

  • 阶段1(PreCommit)参与者执行事务操作,并返回执行结果到协调者
  • 阶段2(doCommit )协调者等待事务执行结果,如果结果均成功则提交事务,否则回滚事务(存在失败结果)。
存在的问题

同步阻塞 & 单点问题*

在进入阶段2前协调者发生异常,没有其他节点可以代替协调者的工作,从而造成协调者的单点问题

与此同时,事务参与者由于没有协调者的下一步通知,将会一直阻塞直到协调者的恢复,从而形成同步阻塞问题

数据不一致

阶段2中存在部分分布式节点执行失败的情况,将会导致不同事务参与者数据不一致的情况。

3PC

3PC(三阶段提交)是通过事务请求事务执行事务提交三个阶段来实现分布式事务。

3PC

步骤如下:

  • 阶段1(CanCommit)询问参与者是否可以事务执行,主要用于检查参与者是否异常(宕机、通信异常);
  • 阶段2(PreCommit):同2PC,但协调者增加等待超时机制,如果存在超时的参与者则默认参与者执行失败(触发回滚);
  • 阶段3(doCommit ):同2PC,但参与者增加等待超时机制,如果存在超时的协调者则默认协调者发出提交事务

2PC不同之处

  • PreCommit拆分为CanCommitPreCommit,增加一步可达性判断;
  • 协调者参与者均增加等待超时机制,实现事务的回滚和自动提交,解决了同步阻塞单点问题
存在的问题

数据不一致

阶段3中,协调者在下发回滚命令的过程中出现异常,部分参与者发生等待超时,参与者会自动提交(与协调者命令不同)。

BASE

BASE允许系统出现短暂性不可用或不一致的状态,只要能够在一定时间范围内最终达到可用或者一致状态即可。

BASE是由基本可用(Base Availability)软状态(Soft-state)最终一致性(Eventual Consistency

TCC

TCC(Try Confirm Cancel)又称补偿事务,是2PC的一种实现,

其核心思想是:针对每个操作都要注册一个与其对应的确认和补偿操作

TCC

步骤如下:

  • Try: 业务检查(保证事务的一致性)、资源预留(保证事务的隔离性);
  • Confirm: 执行操作,执行真正的业务逻辑;
  • Cancel: 预留资源取消。

TCC与XA的不同之处

  • 锁粒度不同:资源锁定具体由业务决定;
  • 最终一致性:通过ConfirmCancel实现最终的一致性;
  • 业务耦合严重ConfirmCancel操作与具体业务强关联;

由于Confirm操作属于事务提交,因此,TCC中一般不提供回滚操作

Cancel属于锁定资源的释放,例如,订单预占库存的释放。

本地消息表

本地消息表是属于BASE理论的一种分布式事务的实现。

本地消息表的主要思想是将分布式事务拆分成本地事务进行处理,分布式之间的通过本地的事务消息表来异步通信来实现。

特点

  • 最终一致性:通过保证事务消息的写入事务消息的投递消费来实现;
  • 业务幂等:由于存在异常重试机制来保证事务的一致性,因此业务必须保证幂等;
  • 异步回滚:业务需要支持类似TCC的Cancel操作,用于处理接收到的事务回滚消息;
  • 业务耦合严重:与TCC相同的业务耦合情况。
事务消息

事务消息本地消息表的一种实现,解决了业务耦合严重的问题。

事务消息是对本地消息表的封装,构建一套完整的事务消息中间件,保证消息的写入与消费来实现分布式事务。

事务消息

例如,RocketMQ实现了事务消息,可用于分布式事务。

特点

  • 最终一致性:同本地消息表
  • 业务耦合小:事务消息数据独立存储于中间件中;

使用

首先,分布式事务产生的原因在于应用拆分(微服务)

原本本地资源管理器就可以实现的事务,被拆分到多个应用,此时为了保证位于多个应用的事务操作就会想到使用分布式事务。

但是,真的需要分布式事务么?

分布式事务不仅会造成资源锁定,事务操作链路长,排查问题复杂。

应用的拆分往往都是基于业务(领域)边界的拆分,服务之间的状态也没必要真的实现强一致性。

最终一致性往往是业务架构设计中需要考虑的,因此,业务中常用的分布式事务是类似于RocketMQ这种中间件来实现。

按照领域拆分不同的领域事件消息,通过可靠的消息写入与消费来实现业务的最终一致性。

扩展阅读

数据库事务原子性、一致性是怎样实现的?8