整理一下之前对于分布式事务解决方案的知识点,包括,传统数据库事务,X/OpenDTP模型,2PC,3PC,柔性事务,基于MQ的最终一致性事务等原理,我们先来看一下传统数据库的事务的实现方式
传统数据库事务
传统数据库事务要满足几个要求:ACID
- Atomic(原子性) :事务必须是原子工作单元
- Consistent(一致性) :事务完成时,必须使所有数据都保持一致状态
- Isolation(隔离性) :并发事务所做的修改必须和其他事务所做的修改是隔离的
- Duration(持久性) :事务完成之后,对系统的影响是永久的
Mysql中里事务处理过程如下
- 记录redo和undo log 文件,确保日志在磁盘上持久化
- 更新数据记录
- 提交事务,redo写入commit记录中
- 清除undo文件,释放锁
其中写入redo和undo文件,需要的IO操作以及,在操作之前对数据的锁,都是事务效率造成低下的原因,真正commit的操作并不会占用太长的时间
分布式事务产生原因
数据库的分库分表: 当数据库单表一年产生的数据超过1000W,那么就要考虑分库分表,原来的一个数据库变成了多个数据库。这时候,如果一个操作既访问01库,又访问02库,而且要保证数据的一致性,那么就要用到分布式事务
-
服务的SOA化: 将一个大型的引用,按照模块进行拆分,导致底层数据的拆分
分布式事务的一些概念
X/Open DTP
X/Open DTP(X/Open Distributed Transaction Processing Reference Model) 是X/Open 这个组织定义的一套分布式事务的标准,也就是了定义了规范和API接口,由这个厂商进行具体的实现。
X/Open DTP 定义了三个组件:
- AP(Application Program):也就是应用程序,可以理解为使用DTP的程序
- RM(Resource Manager):资源管理器,这里可以理解为一个DBMS系统,或者消息服务器管理系统,应用程序通过资源管理器对资源进行控制。资源必须实现XA定义的接口
- TM(Transaction Manager):事务管理器,负责协调和管理事务,提供给AP应用程序编程接口以及管理资源管理器
XA协议
XA就是X/Open DTP定义的交易中间件与数据库之间的接口规范(即接口函数),其中,AP 可以和TM 以及 RM 通信,TM 和 RM 互相之间可以通信,DTP模型里面定义了XA接口,TM 和 RM 通过XA接口进行双向通信,例如:TM通知RM提交事务或者回滚事务,RM把提交结果通知给TM。AP和RM之间则通过RM提供的Native API 进行资源控制,这个没有进行约API和规范,各个厂商自己实现自己的资源控制,比如Oracle自己的数据库驱动程序。
基于XA协议的二阶段提交(2PC)
阶段一:提交事务准备阶段(perpare阶段)
- TM向所有的RM发送事务内容,询问是否珂执行事务的提交操作,并等待各个RM的响应
- 各个AP节点执行事务操作,将undo和redo信息记录到事务日志中,尽量把提交过程中所消耗的操作和准备都提前完成确保后续事务提交的成功率
- 各个RM向TM反馈事务准备完成的响应(返回准备成功,或者准备失败)
阶段二:事务提交(commit阶段)
- TM向所有RM发送事务提交
- 如果有RM返回事务提交失败,则TM调用其他的RM回滚机制进行回滚
优点:假设一个事务的提交过程总共需要30S,其中prepare操作需要28s(事务日志落地磁盘及各种IO操作),而真正commit只需要2s,那么,commit阶段发成错误的概率和perpare相比,2/28(<10%),只要第一个阶段成功,那么commit阶段出现失败的概率就非常小,大大增加了,分布式事务的成功概率
- 缺点1:同步阻塞问题,在事务执行过程中,所有参与节点都是事务阻塞型的。参与者占有公共资源时,其他第三方节点访问公共资源则会处于阻塞。
- 缺点2:单点问题,在2PC中由协调者进行协调,一旦协调者发生故障,参与者会阻塞。尤其在第二阶段
- 缺点3:协调者发出commit消息,并且只有部分参与者收到消息,此时协调者和收到消息的参与者发生宕机。那么即使协调者通过 选举协议 产生了新的协调者,这条事务的状态也是不确定的,集群中不能判断出事务是否被已经提交
3PC
相比2PC,3PC引入超时机制,并把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段
相对于2PC,3PC主要解决单点问题,减少阻塞,因为一旦参与者无法“及时”收到来自协调者的信息之后(超时),他会默认执行commit,而不会一直持有事务资源并处于阻塞状态。
柔性事务
所谓柔性事务是相对强制锁表的刚性事务而言。流程入下:服务器A的事务如果执行顺利,那么事务A就先行提交,如果事务B也执行顺利,则事务B也提交,整个事务就算完成。但是如果事务B执行失败,事务B本身回滚,这时事务A已经被提交,所以需要执行一个补偿操作,将已经提交的事务A执行的操作作反操作,恢复到未执行前事务A的状态。
缺点是业务侵入性太强,还要补偿操作,缺乏普遍性,没法大规模推广。
基于MQ的消息事务,最终一致性
在系统之间引入MQ消息中间件,由于MQ的不及时性质,所以对于要求实时性比较高的系统不适用
- 第一步:
- 执行本地事务(新增订单,更新库存,增加消息记录,状态为,待发送)
- 执行失败,数据库回滚,不会造成数据不一致
- 第二步:消息发送值MQ
- 发送失败,本地重复发送,重试5次,
- 重试5次之后,回滚本地事物
- 第三步:MQ消息确认(将消息状态改为,发送成功-待销费)
- 消息确认失败,定时任务,根据消息的创建时间,每两分钟获取未完成的订单重新发送MQ
- 第四步:消费消息发送
- 发送失败,MQ消息自带重试机制,放置队列末尾重试5次
- 第五步:商城系统(扣除积分)
- 本地事物失败,事物回滚,将消息重新放到,MQ中
- 第六步:
- 消息确认(异步调用商城系统接口,将消息信息状态改成已完成,或者商城系统通过查询接口查询积分系统)
- 异步调用接口失败,使用定时任务重新发送消息(此时需要使用幂等防止重复数据)
J2EE 项目在使用分布式事务开源框架,atomiks