前面说了分布式事务的产生原因,那么如果出现了分布式事务,应该如何解决,以下从理论上梳理三种解决分布式事务的方案。
两阶段提交
为了保障各节点的数据一致性,需要以中心化为基础,增加一个协调者,协调者会与各节点进行交互,同时协调者负责协调各节点的数据,保证各节点的数据一致性。
prepare阶段
prepare阶段是向各节点发送执行SQL请求,各节点收到该请求后执行相关的业务逻辑,但是执行后不提交,可以理解为本地事务执行但是没有commit。
然后将执行情况返回给协调者,协调者收到所有节点返回的执行情况进行统计,当所有的节点都执行成功后协调者下发commit通知,如果有任何节点返回失败则下发rollback通知。
提交阶段
提交阶段中协调者会根据所有节点反馈的阶段下发任务,如果所有的节点都返回成功,那么协调者向各节点下发commit请求,各节点收到commit请求后提交本地事务。如果有任何一个节点没有成功,那么向各节点下发rollback请求,各节点rollback本地事务。
缺点
超时问题
协调者向各节点发送通知时,超过一定时间节点没有返回执行结果,那么就会触发超时,协调者默认节点执行失败,触发rollback。
同步阻塞问题
两阶段提交中准备阶段会向各节点发送执行事务但不提交的请求,因此各节点为了保证数据的一致性会锁定资源,其它请求访问该节点的部分数据被同步阻塞。
单点故障问题
在两阶段提交中如果协调者出现异常,那么就会导致整个事务无法进行,并且各节点也会被阻塞。
数据不一致问题
两阶段提交的commit阶段,协调者发出了通知但是由于网络问题导致其中一些节点没有收到通知,那么就会导致阻塞时间范围内的数据不一致。
三阶段提交
三阶段提交在两阶段的基础上做了一定的优化,优化点主要是超时问题和单点故障问题。
三阶段提交的实现是基于两阶段提交的prepare阶段做了优化,将两阶段的prepare阶段拆分为两个阶段执行。
具体的阶段如下:
can-commit
该阶段协调者会向各节点发送一个询问请求,询问各节点是否可以执行正常操作,如果所有节点都返回正常,则继续下一阶段,否则直接停止。
该阶段的作用主要为了向各节点确认节点的健康状态,或者检测协调者和各节点的通信情况,如果在该节点出现协调者与任何节点通信无法连接,或者超时问题,那么在该阶段就直接停止事务的执行。提前避免了数据的不一致。
pre-commit
当can-commit执行通过后,执行该阶段,该阶段是预提交阶段,该阶段中协调者向各节点发送事务执行但不提交请求,所有节点收到通知后执行事务操作,但是不提交本地事务,然后将执行结果返回给协调者。
协调者收到所有节点执行的结果,如果所有节点都执行成功,则执行下一阶段,否则执行rollback。
do-commit
当所有节点都执行成功事务后,协调者下发提交事务通知给所有的节点,所有节点收到该通知后提交本地事务。
该阶段中如果协调者的通知没有成功下发到所有的节点,那么各节点会存在一个超时机制,超过规定的时间依然会提交本地事务。保证事务的一致性。
TCC模式
Try:锁定资源
锁定资源阶段,该阶段会生成业务数据,但是该业务数据数据一个中间态的数据,比如分布式事务在订单服务应该更新状态为已下单,但是在该阶段并不会直接更新为已下单,而是更新为处理中状态,预先锁定这个订单资源,这样其它的线程想要操作该资源时无法操作。
锁定资源后各节点会将结果反馈给事务协调者,事务协调者根据反馈结果确定是下发Commit还是Cancel。
Commmit:提交事务最终结果
Commit阶段是通知各节点更新锁定的资源为目标状态,将前面提到的处理中状态更新为已下单状态。
Cancel:取消事务中间结果
在Try阶段如果有任何节点没有锁定资源成功,那么事务协调者会下发Cancel通知,通知各节点rollback已经锁定的资源数据。
该方式实现的分布式事务对于业务侵入性较高,每个节点服务都需要实现各自的一套try、commit、cancel逻辑,并且commit和cancel都要保证幂等。
并且对于TCC的事务协调者部分,协调器也要实现日志记录等功能,也会存在一定的性能损耗
引用
拉钩教育-分布式技术原理与实战45讲