事务是运行在数据库的逻辑单元,工作单元内的一系列sql命令具有原子性的操作特点。
数据库事务满足的要素:ACID
- Atomic原子性:事务必须是原子的工作单元(对数据库的操作要么全部执行成功,要么全部不执行);
- Consistent一致性:事务完成时,必须使所有数据都保持一致状态(假如有5个账户,每个账户是金额是100元,总共500元。如果这几个账户之间同时发生多次转账,无论在什么样的并发的情况下,账户的总额依然是500,不会说因为数据的并发总额产生不一致的情况);
- Isolation隔离性:并发事务所作的修改必须和其他事务所做的修改是隔离的(在并发的情况下或者多个进程/线程对同一个数据进行处理的情况下,我们必须要保证这些修改和其他线程的修改是隔离的);
- Duration永久性:事务完成之后,对系统的影响是永久性的;
- 其中原子性和隔离性是导致数据库执行的代价所要高于非事务操作代价的点;
- 隔离性其实是通过锁去实现的;
- 原子性/一致性/持久性是通过数据库里面的记录,就是数据库里面的相关的事务日志来实现的,这个过程就涉及到I/O操作,这就是为什么说事务会有一些性能上的问题;
MySQL里事务处理过程
redo和undo文件
redo:记录事务修改以后的数据;
undo:记录事务修改之前的原始数据;
这个日志是用来保证你在做事务操作的时候你可以做回滚也可以做提交。
其实在MySQL里面做事务提交commit之前,它会做几步操作:
- 记录redo和undo log文件,确保日志在磁盘上的持久化
- 更新数据记录
- 提交事务,利用redo里面的日志写进commit记录
- 事务完成以后会清理undo文件以及释放锁(因为隔离性),并且刷新redo日志,确保redo保持在磁盘上
分布式事务
- 数据库分库分表:拆分以后就涉及到一个操作会往多个库里面做一个更新操作;
- SOA化,服务化工程
比如说本身有一个单机的电商系统,现在对整个系统按照领域模型做了一个功能性拆分,每一个都有对应的独立的数据库;
分布式事务产生的背景:
假设在这个交易平台我们去做一个下单的操作,就涉及到扣库存和更新订单状态,这样我们需要去操作库存中心和订单中心,他们的数据库是分开的,在这种情况下你要去保证这个事务性的话,这种情况就属于分布式事务(跨库处理)。
【分布式拆分:本身一个war包里面包含很多很多功能,包括用户、库存、订单等等,但是随着业务的发展,这个时候可能需要做一些拆分,一方面是因为你这个war包越来越大、功能业务点越来越多,这个时候里面的很多功能会有一些冗余和交叉,这个时候你改动一个点可能会对其他的模块产生影响,这个时候会带来一个业务爆发式增长对一个需求更改的一个麻烦点;第二个是业务代码的一个混乱;第三个你的一个发布周期很慢,因为本身在电商平台的话一天可能做几个活动,所以你可能几乎每天发布几个小版本,可能三天一个大版本两天一个小版本,这样的一个版本迭代。而在这种你大型的war包的情况下发布一个版本会涉及到太多太多的功能点、回归测试、各个点的一个影响的测试等等每个地方都要去测试,这个周期就是半个月或者一个月进行发布一次,就是等等这些问题导致我们要去对这个系统进行拆分,拆分以后就是每个模块成为一个独立的工程】
分布式事务产生的原因
- 数据库分库分表
- 服务soa化
- 在分布式系统中,每一个机器节点虽然都能明确的知道自己执行的事务是成功还是失败,但是却无法知道其他分布式节点的事务执行情况。因此,当一个事务要跨越多个分布式节点的时候(比如,下单流程,下单系统和库存系统可能就是分别部署在不同的分布式节点中),为了保证该事务可以满足ACID,就要引入一个协调者(Cooradinator)。其他的节点被称为参与者(Participant)。协调者负责调度参与者的行为,并最终决定这些参与者是否要把事务进行提交。
分布式产生之后的解决方案:
X/OpenDTP事务模型
X/Open是一个组织机构,定义出的一套分布式事务标准,定义了规范化的API接口。具体的实现是由各个厂商去实现的。
2PC(two-phase-commit)协议:用来保证分布式事务的完整性
J2EE遵行了X/OpenDTP规范,设计并实现了Java里面的分布式事务编程接口规范-JTA
XA是X/OpenDTP定义的中间件与数据库之间的接口规范,XA接口函数由数据库厂商提供
X/OpenDTP角色
- AP application 应用节点,负责触发数据库指令
- RM resource manager 资源管理器(数据库)
- TM transaction manager 事务管理器,事务协调者
通过AP触发给TM,TM再去触发RM执行,RM负责执行给我们的XA指令;每个RM只负责执行自己的一个指令;可以认为RM就是一个数据库,数据库上面实现了XA的接口规范,TM相当于负责整个事务的一个协调
基于X/Open的一个非常重要的一个角色2PC
2PC(two-phase-commit)
在X/Open模型里面一个分布式事务所涉及到的sql逻辑都执行完成并到了我门的RM最后提交事务的关键时刻,为了避免分布式事务的不可靠(CAP理论)因素导致事务提交意外失败,那么TM就实施了两步走的方案,也就是2PC(二阶段事务提交)
CAP
C(Consistency)A(Availability)/P(Partition-tolerance)没办法同时满足,最多只能同时满足两个
C(一致性 Consistency): 所有节点上的数据,时刻保持一致
可用性(Availability):每个请求都能够收到一个响应,无论响应成功或者失败
分区容错 (Partition-tolerance):表示系统出现脑裂以后,可能导致某些server与集群中的其他机器失去联系
CP / AP
CAP理论仅适用于原子读写的Nosql场景,不适用于数据库系统
阶段一:提交事务请求
1.TM向所有AP发送事务内容,询问是否可以执行事务的提交操作,并等待各个AP的响应
2.执行事务各个AP节点执行事务操作,将undo和redo信息记录到事务日志中,尽量把提交过程中所消耗时间的操作和准备都提前完成后确保后续事务提交的成功率
3.各个AP向TM反馈事务询问的响应
各个AP成功执行了事务操作,那么反馈给TM yes的response;如果AP没有成功执行事务就反馈TM no的response
阶段二:执行事务提交
1.执行提交事务
预提交prepare
响应
都返回yes-->提交commit
假设一个事务的提交过程总共需要30s, 其中prepare操作需要28(事务日志落地磁盘及各种io操作),而真正commit只需要2s那么,commit阶段发生错误的概率和prepare相比, 2/28 (<10%) .只要第一个阶段成功,那么commit阶段出现失败的概率就非常小,大大增加了分布式事务的成功概率
2.中断事务提交
TM预提交prepare
参与者响应
参与者如果有一个返回no-->回滚,中断事务,发送abort请求,按照undo日志进行回滚,回滚完之后释放事务资源\锁,释放完之后返回ack给TM,这个时候所有事务统一回滚
2PC存在的问题:
- 数据一致性问题
-
问题产生原因:在第二个阶段参与者和协调者都挂了的时候,挂了的参与者AP在挂之前执行了一个操作,但是由于他挂了,没人知道他执行了什么操作,在这种情况下协调者可以通过某种方式(leader选举)选举出来,新选举出来的协调者如果他想负责去执行commit还是abort ,这个时候新选举出来的协调者和没有挂掉的参与者必须要保证数据一致性;在这种情况下假设没有挂掉的参与者都执行了commit ,而挂掉的参与者还没有恢复的时候他可能是执行了一个回滚. 也就是说协调者恢复之后都没有挂掉的参与者实行的是参与者,而挂掉的参与者实际在挂掉之前执行的是回滚操作,这种情况下仍然会导致数据的不一致性.
-
假设在第一个阶段协调者和参与者都挂了,这个时候会新选举出来一个协调者,新选举出来的协调者会询问各个参与者的情况再决定是执行commit还是rollback,因为还没有执行commit,所以这个时候还不会出现数据不一致性问题.
-
- 同步阻塞
当协调者发出请求后,参与者会给出响应,在参与者给出响应之后,要等待协调者给出指令,如果在这个时候协调者挂掉,那么参与者就会一直等待协调者,这个就是同步阻塞.
2pc的数据一致性问题
- 数据不一致。在二阶段提交中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
- 同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
- 二阶段无法解决的问题:协调者在发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交
- 单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。
3PC(three phase commit):
阶段一:canCommit: 协调者向各个AP发送你是不是可以执行,可以就返回yes,不可以就返回no,只要收到任何一个no,这个时候事务就会中断
阶段二:preCommit: 根据第一个阶段来决定是否可以执行这个操作,可以的话协调者向各个AP(参与者)发送preCommit,响应yes或者no
阶段三:doCommit: 根据响应判断是commit还是rollback
改进点
- 增加了超时机制
- 第二阶段,如果协调者超时没有接受到参与者的反馈,则自动认为失败,发送abort命令
- 第三阶段,如果参与者超时没有接受到协调者的反馈,则自动认为成功开始提交事务(基于概率)
3PC的问题:
相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。
通过2pc的方式去完成分布式事务,虽然通过这种方式能够达到预期的效果,但是我们在现实中很少会用到2pc方式的提交的XA事务,有几个原因
- 互联网电商应用的快速发展,对事务和数据的绝对一致性要求并没有传统企业应用那么高
- XA事务的介入增加了TM中间件,使得系统复杂化
- XA事务的性能不高,因为TM要等待RM回应,所以为了确保事务尽量成功提交,等待超时的时间通常比较长,比如30s到几分钟,如果RM出现故障或者响应比较慢,则整个事务的性能严重下降