一、分布式事务
1 简介
分布式事务用于在
分布式系统中保证不同节点之间的数据一致性,分布式事务是指不是在单个服务或单个数据库架构下,产生的事务,例如:
-
跨数据源的分布式事务
-
跨服务的分布式事务
-
综合情况
2 问题引入
在数据库水平拆分、服务垂直拆分之后,一个业务操作通常要跨多个数据库、服务才能完成。例如电商行业中比较常见的下单付款案例,包括下面几个行为:
-
创建新订单
-
扣减商品库存
-
从用户账户余额扣除金额
完成上面的操作需要访问三个不同的微服务和三个不同的数据库。
订单的创建、库存的扣减、账户扣款在每一个服务和数据库内是一个本地事务,可以保证ACID原则。
但是当我们把三件事情看做一个"业务",要满足保证“业务”的原子性,要么所有操作全部成功,要么全部失败,不允许出现部分成功部分失败的现象,这就是分布式系统下的事务了。
此时ACID难以满足,这是分布式事务要解决的问题。
二、 XA分布式事务协议
1. 两阶段提交(2PC)
在XA协议中包含着两个角色:事务协调者和事务参与者。让我们来看一看他们之间的交互流程:
1.1 两阶段提交(2PC)成功情况下的流程
第一阶段:
在XA分布式事务的第一阶段,作为事务协调者的节点会首先向所有的参与者节点发送Prepare请求。
在接到Prepare请求之后,每一个参与者节点会各自执行与事务有关的数据更新,写入Undo Log和Redo Log。如果参与者执行成功,暂时不提交事务,而是向事务协调节点返回“完成”消息。
当事务协调者接到了所有参与者的返回消息,整个分布式事务将会进入第二阶段。
第二阶段:
在XA分布式事务的第二阶段,如果事务协调节点在之前所收到都是正向返回,那么它将会向所有事务参与者发出Commit请求。
接到Commit请求之后,事务参与者节点会各自进行本地的事务提交,并释放锁资源。当本地事务完成提交后,将会向事务协调者返回“完成”消息。
当事务协调者接收到所有事务参与者的“完成”反馈,整个分布式事务完成。
1.2 两阶段提交(2PC)失败情况下的流程
第一阶段发生异常
在XA的第一阶段,如果某个事务参与者反馈失败消息,说明该节点的本地事务执行不成功,必须回滚。
于是在第二阶段,事务协调节点向所有的事务参与者发送Abort请求而不是commit请求。接收到Abort请求之后,各个事务参与者节点需要在本地进行事务的回滚操作,回滚操作依照Undo Log来进行。流程如下:
第一阶段:
第二阶段:
1.3. XA两阶段提交的不足
XA两阶段提交究竟有哪些不足呢?
1.性能问题
XA协议遵循强一致性。在事务执行过程中,各个节点占用着数据库资源,只有当所有节点准备完毕,事务协调者才会通知提交,参与者提交后释放资源。
这样的过程有着非常明显的性能问题,即事务的执行时间会比较长。
2.协调者单点故障问题
事务协调者是整个XA模型的核心,一旦事务协调者节点挂掉,参与者收不到提交或是回滚通知,参与者会一直处于中间状态无法完成事务。
3.丢失消息导致的不一致问题。
在XA协议的第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就导致了节点之间数据的不一致。
4.没有完善的容错机制。
太过保守 ,任意一个节点失败就会导致整个事务失败,没有完善的容错机制。
2. 三阶段提交(3PC)
XA三阶段提交在两阶段提交的基础上增加了CanCommit阶段,并且引入了超时机制。
一旦事物参与者迟迟没有接到协调者的commit请求,会自动进行本地commit。这样有效解决了协调者单点故障的问题。
三阶段提交协议是2PC的改进版本,将2PC的提交事务阶段一分为二,这样就变成了三阶段:CanCommit,PreCommit,DoCommit三个阶段。
2.1 三阶段提交流程
三阶段提交流程如下:
- CanCommit阶段
1.事务询问: 协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。
2.响应反馈: 参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No注意:如果区间双方任何一方发生超时,则abort.
- PreCommit阶段(如果成功会进行资源的锁定)
协调者根据参与者的反应情况来决定是否可以进行事务的PreCommit操作。根据响应情况,有以下两种可能。
第一种:假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行
1.发送预提交请求: 协调者向参与者发送PreCommit请求,并进入Prepared阶段。
2.事务预提交 :参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。
3.响应反馈: 如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令:提交(Commit)或中止(abort)。
第二种:假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。当然如果协调者没有收到预提交请求也会abort。
1.发送中断请求 协调者向所有参与者发送abort请求。
2.中断事务 参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。
- doCommit阶段
该阶段进行真正的事务提交。也可以分为以下两种情况。
第一种:执行提交
1.发送提交请求: 协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。
2.事务提交: 参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
3.响应反馈: 事务提交完之后,向协调者发送Ack响应。
4.完成事务: 协调者接收到所有参与者的ack响应之后,完成事务。
第二种:中断事务
协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。
利用参与者二阶段记录的undo信息来执行事务回滚,并向协调者发送ACK消息,协调者收到ACK消息后执行事务中断。
1.发送中断请求 协调者向所有参与者发送abort请求
2.事务回滚 参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。
3.反馈结果 参与者完成事务回滚之后,向协调者发送ACK消息
4.中断事务 协调者接收到参与者反馈的ACK消息之后,执行事务的中断
2.2 三阶段提交的不足
1.一致性问题未解决
一致性问题没有解决的原因:
由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间短时间内存在数据不一致的情况。
2.性能问题未完全解决
事务开启后,要等到commit区间还是占用很长的时间。但对比2PC已经有了较大的性能提升
通过以上分析发现,2PC和3PC都无法彻底解决分布式的一致性问题,接下来会分析最为行之有效的Paxos算法