分布式事务
分布式事务
事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。需要保证这些分支事务要么全部成功,要么全部失败

XA/2PC二阶段提交
定义了(全局)事务管理器(TM)和(局部)资源管理器(RM)的接口。mysql、oracle、sqlserver支持。由一个或多个资源管理器RM(数据库)、一个事务管理器TM和一个应用程序Application组成。分为两阶段:
第一阶段prepare:所有的参与者RM锁住资源准备执行事务并告诉TM已ready准备就绪
第二阶段commit/rollback:当TM确认所有RM都ready后,向所有RM发送commit命令。任何一个RM ready失败,向所有RM发送rollback命令,若在commit过程中出现异常时,则在节点重启后,根据XA recover进行 commit补偿保证数据一致性
保证强一致性。实现逻辑简单。
单点故障:协调者崩溃可能导致事务卡死。
同步阻塞:参与者在等待协调者指令时会被阻塞。
seata、Sharding Sphere、Spring JTA + Atomikos
适合单体应用跨多个库的分布式事务,因为严重依赖于数据库处理事务,效率低不适合高并发
微服务要求每个服务只能操作自己的数据库。操作其他服务的数据库必须通过调用接口实现
3PC三阶段提交
分为三个阶段:
CanCommit准备阶段。协调者发送CanCommit请求给所有参与者,参与者能提交就返回Yes,否则返回No
PreCommit预提交阶段。协调者发送PreCommit请求给所有参与者。参与者执行事务的准备操作,并记录预提交状态(例如写入日志)。参与者返回“ACK”表示已准备好。如果所有参与者都返回“ACK”,事务进入第三阶段。否则,协调者要求所有参与者中止事务。
DoCommit提交阶段。协调者发送DoCommit请求给所有参与者真正提交事务。提交完成后,参与者释放资源并向协调者确认提交完成。如果协调者在此阶段检测到任何问题则回滚事务
缓解了两阶段提交中单体故障和同步阻塞的问题,因为加入了超时机制作用于预提交和提交阶段。如果参与者等待预提交请求超时则中止事务。如果等待提交请求超时,参与者根据PreCommit是否完成确定是否提交事务
仍然不能完全避免不一致性。2PC、3PC都不能保证的数据100%一致。
实现复杂度高。
TCC(Try-Confirm-Cancel)
把锁的粒度交给业务处理,每个子事务都要实现Try-Confirm/Cancel接口。本质是2PC。分为3个阶段
Try阶段:对各个服务的资源做检测以及对资源进行锁定或者预留。
Confirm阶段:确认执行真正执行业务,只使用Try阶段预留的业务资源,要求幂等,失败后要重试
Cancel阶段:取消执行,释放Try阶段预留的业务资源。要求幂等。业务方法执行出错的补偿,执行成功的回滚
通常Try冻结金额,Confirm扣款,Cancel解冻金额
比2PC并发度高,无长期资源锁定
精细化控制事务逻辑,适应复杂业务需求。
灵活性高,可以设计业务级补偿操作。
一致性较好,不会发生SAGA已扣款最后又转账失败的情况
开发成本高:需要额外实现 Try/Confirm/Cancel 操作逻辑。要根据网络、系统故障等失败原因实现不同的回滚策略
适用范围受限:需要业务支持资源预留和回滚。适用于订单类,对中间状态有约束的业务
在需要锁定前置资源的场景只能用XA或TCC。如下单场景,在订单创建之前,需要扣除优惠券、钱包余额、积分等不得不进行前置多资源锁定
ByteTCC,TCC-transaction,Himly。java的seata

SAGA
将长事务拆分为多个本地短事务sub-transaction Ti直接提交到库,每个Ti都有对应的补偿动作Ci用于撤销Ti的操作。如果正常结束按子事务序列 T1, T2, …, Tn,如果某个步骤失败,则根据相反顺序一次调用补偿操作。(序列 T1, T2, …, Tj, Cj, …, C2, C1, 0 < j < n)
两种恢复策略:
向后恢复:如果任一子事务失败,补偿所有已完成的事务,
向前恢复:重试失败的事务,适合子事务最终会成功或补偿事务难以定义或不可能的场景。
如何解决没有Prepare阶段导致的事务间不能保证隔离性(当多个事务操作同一资源时更新丢失、脏数据读取等)的问题?需要在业务层控制并发。如应用层加锁预先冻结资源
并发度高,无长期锁定资源
适用于长事务,对中间结果不敏感的业务场景适用
需要定义正常操作以及补偿操作,开发量大
一致性较弱,对于转账,可能发生A用户已扣款,最后转账又失败的情况
go DTM,java seata、Apache Service Comb的Saga引擎、Sharding Sphere的Saga
本地消息表
ebay,在消息发送方的数据库中添加一个记录着消息状态的消息表,保证业务表与消息表在同一个事务,用定时任务轮询查询状态为未同步的消息表,发送到MQ,失败则重试
消息消费方处理消息同时保证不处理重复消息。本地事务处理失败则重试。如果业务失败,给消息生产方发送业务补偿消息让其回滚
实现了最终一致性。使用简单。间隔由定时任务的间隔时间决定
消息表会耦合到业务系统中。每个本地消息表都要轮询。消费者的逻辑如果无法重试成功则要回滚机制
适用于可异步执行的业务,且后续操作无需回滚的业务
可靠消息最终一致性(消息队列事务)
RocketMQ4.3+支持事务消息
消息发送方先发送prepared消息到mq,如果发送失败则取消操作;成功则接着执行本地事务并给mq发送确认消息,如果失败就告诉mq回滚消息
消息接收方接收到确认消息,然后执行本地事务
MQ定时轮询所有prepared消息回调的接口,可以查数据库看本地事务已经提交/回滚/异常,回复确认/回滚/重试。避免本地事务执行成功了,而确认消息却发送失败了
如果消息接收方的事务失败则不断重试直到成功,如果不行则回滚并通知消息发送方回滚;或者发送报警由人工来手工回滚和补偿
异步解耦,性能高。
消息队列自带重试机制,保证消息可靠性。
数据最终一致性,无法保证实时一致性。
需要依赖消息队列的可靠性。
参考RocketMQ,DTM,RabbitMQ
最大努力通知(本地事务 + 事件通知)
发送方提供接口,让接受通知方能够通过接口查询业务处理结果
发送方消息队列ACK机制,消息队列按照间隔1min、5min、10min、30min、1h、2h、5h、10h的方式,逐步拉大通知间隔 ,直到达到通知要求的时间窗口上限后不再通知
以支付通知为例,业务系统调用支付平台进行支付,操作支付后支付平台会同步通知业务系统支付操作是否成功,不成功则会一直异步重试直到超过最大通知次数,业务系统自行调用支付平台接口查询支付操作是否成功
适用于对最终一致性实时性要求没那么高的业务、业务通知类型。比如支付通知。短信通知。
高性能
异步解耦
支持最终一致性
不适用于需要强一致性的场景,如金融转账、账户余额扣减等高敏感业务。
需要引入重试、日志记录、失败监控、告警等机制,增加了系统的运维和开发复杂度。
一致性依赖下游系统:下游系统需要能够正确处理重复通知(幂等性问题),否则可能导致数据重复或错误。下游系统需要提供补偿机制,来弥补通知失败导致的逻辑缺陷。
本地消息表、事务消息与最大努力通知区别?
可靠消息一致性,发送方保证将消息发出去,并且将消息发到接收通知方,消息的可靠性由发送方保证
最大努力通知,发起方尽最大的努力将结果通知为接收方,接收不到时需要接收方主动调用通知方的接口查询业务处理结果,通知的可靠性由接收方保证