事务
事务(Transaction),一般是指要做的或所做的事情。 在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
计算机术语中的事务一般就是指数据库事务。简单来说就是一个大活动由多个小活动组成,这些小活动要么 全部 成功,要么 全不 成功。 通过事务的特性我们可以更加认识这一点。
事务特性
事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为 ACID特性。
- 原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
- 一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
- 隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
举个例子理解下。
arduino
begin transaction
1. 添加用户张三
2. 添加张三的权限
end transaction
创建用户张三是所要做的事情,添加用户张三 和 添加张三的权限 是两个小活动。这两个小活动全部成功执行,那么创建用户张三这件事情也就执行成功,如果 添加用户张三 和 添加张三的权限 这两个小活动中有一个不成功,那么创建用户张三这件事情也就失败了,这两个小活动也就要都失败。
分布式事务
互联网的高速发展,应用复杂程度越来越高,规模越来越大,早期的单体应用已经演化成分布式应用。多个独立部署在不同计算机上的服务通过网络来共同完成一项活动。刚刚说的一个个 "小活动",也就分布在了不同的计算机之上,通过网络协作共同完成一件事情,这就是 分布式事务 了。
arduino
begin transaction
1. 添加用户张三
2. 添加张三的权限
end transaction
现在我们把用户服务和权限服务部署到了不同的计算机上,通过网络调用来协作,事情是不是变得不一样了呢?
假如添加用户张三成功,添加张三的权限因为网络原因没有即使返回或者返回错误(其实是数据插入成功),这时事务回滚。最终结果是用户未添加,但是权限添加成功,这并没有达到我们所预期的效果。所以分布式情况下事务的处理和单体应用的本地事务处理方式有所区别,分布式事务需要有对应的解决方案来达成我们所预期要的效果。
XA协议
XA协议 由Tuxedo首先提出的,并交给X/Open组织,作为资源管理器(数据库)与事务管理器的接口标准。
Oracle、Informix、DB2和Sybase等各大数据库厂家都提供对XA的支持。XA协议采用两阶段提交方式来管理分布式事务。
XA接口提供资源管理器与事务管理器之间进行通信的标准接口。
XA中有这几个概念
- 事务管理器 (Transaction Manager):简称TM,是一个系统组件,负责协调和管理事务的执行。为系统的可靠运行提供了重要保障。一般包含在应用程序中。
- 资源管理器 (Resource Manager):简称RM,是负责管理分布式事务中各个本地资源的组件。资源管理器由数据库实现,例如Oracle、DB2等数据库都实现了XA规范的接口。
当然还有应该有一个概念就是我们编写的应用程序(Application Program),简称AP。
TM,RM,AP共同组成了X/Open组织定义的一套分布式事务的标准 DTP模型 。
2PC
XA协议采用两阶段提交方式来管理分布式事务,简称 2PC,在此协议中,一个或多个资源管理器的活动均由一个称为事务协调器的单独软件组件来控制。2PC的步骤如下:
- 应用程序调用事务协调器中的提交方法。
- 事务协调器将联络事务中涉及的每个资源管理器,并通知它们准备提交事务(这是第一阶段的开始)。
- 为了以肯定的方式响应准备阶段,资源管理器必须将自己置于以下状态:确保能在被要求提交事务时提交事务,或在被要求回滚事务时回滚事务。大多数资源管理器会将包含其计划更改的日记文件(或等效文件)写入持久存储区中。如果资源管理器无法准备事务,它会以一个否定响应来回应事务协调器。
- 事务协调器收集来自资源管理器的所有响应。
- 在第二阶段,事务协调器将事务的结果通知给每个资源管理器。如果任一资源管理器做出否定响应,则事务协调器会将一个回滚命令发送给事务中涉及的所有资源管理器。如果资源管理器都做出肯定响应,则事务协调器会指示所有的资源管理器提交事务。一旦通知资源管理器提交,此后的事务就不能失败了。通过以肯定的方式响 应第一阶段,每个资源管理器均已确保,如果以后通知它提交事务,则事务不会失败。
Mysql中的XA协议实现
以Mysql(InnoDB引擎)对XA协议的实现我们来看看什么是两阶段提交。
sql
XA START 'createuser';
-- SQL语句
-- SQL语句
-- SQL语句
XA END 'createuser';
XA PREPARE 'createuser';
XA COMMIT 'createuser';
- XA START :开启一个XA事务,并把它置于 ACTIVE 状态,可以指定跟一个 XID,通常由 TM 生成。
- XA END :结束一个XA事务,将事务置于 IDLE 状态。
- XA PREPARE :准备一个XA事务,把事务放入 PREPARED 状态,这时 XA RECOVER 命令可以列出所有处于 PREPARED 状态的XA事务。
- XA COMMIT / XA ROLLBACK :提交/回滚 一个XA事务。
可以看到跟我们的正常 Mysql 事务仅仅只是多了一个 PREPARE 准备阶段。2PC 中 PREPARE 就是第一阶段,COMMIT 就是第二阶段。
试想一下分布式事务情况下,如果使用一阶段事务,同时提交多个事务,如果一个事务 COMMIT 成功,一个事务 COMMIT 失败,那么事务的特性将得不到保证。2PC 加了一个 准备 阶段,所有事务都准备完成后,就差最后 COMMIT 这一步,将避免这种问题的出现。
XA缺陷
哦豁,乍一看确实不错,但是深入思考下 2PC 有没有什么问题呢?
2PC 是一个 强一致性 的同步阻塞协议,需要锁定事务执行过程中的资源,也就是所谓的刚性事务。这也就导致了一些缺陷的出现。
- 在高并发场景下性能并非很好。适合执行比较快比较短的事务。
- 可能会造成死锁。锁定数据,如果后续没有提交事务的命令或者回滚事务的命令(比如TM宕机,或网络问题等),数据库就会一直锁定数据,造成数据库死锁的问题。
3PC
三阶段提交就是针对二阶段提交缺陷的改进版本,三阶段提交协议在协调者和参与者都引入了超时机制。
3PC 将 2PC 的准备阶段拆分成了两个步骤 CanCommit 和 PreCommit。
3PC 执行过程
- CanCommit:和 2PC 的准备阶段很像,询问是否可以执行事务提交操作,各参与者根据自身情况回复预估值,如果预估自己能够正常执行事务就返回确定信息,并进入预备状态,否则返回否定信息。
- PreCommit:协调者根据第一阶段的询问结果采取相应操作。如果所有参与者都返回确定信息,则协调者会向所有参与者发送 pre commit 请求。如果只有一个或多个参与者返回否定信息,或者协调者等待超时,则会中断事务。
- DoCommit:各参与者收到 do commit 请求后,会正式执行事务提交操作,并在完成提交后释放占用资源。
三阶段引入超时机制解决了堵塞问题,但多了一次请求,性能上根据不同场景可能会比二阶段还差,超时机制还可能会出现幂等性问题造成数据不一致。
TCC
TCC 事务机制相比于 2PC、3PC,不会锁定整个资源,而是通过引入补偿机制,将资源转换为业务逻辑形式,锁的粒度变小。
TCC 可以理解为在应用层面的 2PC。
TCC 实现分布式事务有三个步骤,分别是:
- try:这个阶段对各个服务的资源做检测以及对资源进行锁定或者预留。
- confirm:真正的执行业务逻辑,try 阶段已经做了检查。因此直接执行不做任何的检查,执行过程中会使用到 try 阶段预留的资源。需要考虑幂等性问题,失败后可能需要进行重试。
- cancel:try 执行失败会进入 cancel 阶段进行补偿。回滚操作,对 try 阶段使用的资源进行释放。需要考虑幂等性问题,失败后需要进行重试。
TCC优劣势
TCC 模式事务之间没有阻塞,性能相比 2PC 将会有所提升。
事务的成败完全靠开发人员编写业务代码实现,所以编码的好坏直接影响 TCC 事务的执行。
confirm 和cancel 阶段的幂等性也需要着重关注,事务的一致性需要保证。
TCC 需要植入业务代码,并且这部分的代码很难复用,加大了开发难度和时间。
总结
并没有完美的解决方案,能够解决掉所有业务问题。在实际业务使用中,需要根据不同业务场景的特性来选择合适的分布式解决方案。