✅P288_商城业务-分布式事务-分布式事务常见解决方案
2PC 模式
数据库支持的 2PC【2 phase commit 二阶提交】,又叫做 XA Transactions。
MySQL 从 5.5 版本开始支持,SQL Server 2005 开始支持,Oracle 7 开始支持。
其中,XA 是一个两阶段提交协议,该协议分为以下两个阶段:
- 第一阶段:事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交
- 第二阶段:事务协调器要求每个数据库提交数据。其中,如果有任何一个数据库否决此次提交,那么所有数据库都会被要求回滚它们在此事务中的那部分信息。
- XA 协议比较简单,而且一旦商业数据库实现了 XA 协议,使用分布式事务的成本也比较低。
- XA 性能不理想,特别是在交易下单链路,往往并发量很高,XA 无法满足高并发场景
- XA 目前在商业数据库支持的比较理想,在 mysql 数据库中支持的不太理想,mysql 的XA 实现,没有记录 prepare 阶段日志,主备切换回导致主库与备库数据不一致。
- 许多 nosql 也没有支持 XA,这让 XA 的应用场景变得非常狭隘。
- 也有 3PC,引入了超时机制(无论协调者还是参与者,在向对方发送请求后,若长时间未收到回应则做出相应处理)
柔性事务-TCC 事务补偿型方案
刚性事务:遵循 ACID 原则,强一致性。
柔性事务:遵循 BASE 理论,最终一致性;
与刚性事务不同,柔性事务允许一定时间内,不同节点的数据不一致,但要求最终一致。
一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
二阶段 commit 行为:调用 自定义 的 commit 逻辑。
二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。
所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。TCC是Try-Confirm-Cancel的缩写,它是一种分布式事务解决方案,采用了基于业务逻辑的补偿机制,将整个分布式事务分解为若干个子事务,每个子事务都有一个try、confirm和cancel三个操作,通过这些操作来实现分布式事务的执行和回滚
具体来说,TCC事务包括以下三个步骤:
- Try:在try阶段,参与者尝试执行本地事务,并对全局事务预留资源。如果try阶段执行成功,参与者会返回一个成功标识,否则会返回一个失败标识。
- Confirm:如果所有参与者的try阶段都执行成功,则协调者通知所有参与者提交事务,那么就要执行confirm阶段,这时候参与者将在本地提交事务,并释放全局事务的资源。
- Cancel:如果任何一个参与者在try阶段执行失败,则协调者通知所有参与者回滚事务。那么就要执行cancel阶段,还有就是,如果某个参与者在try阶段执行成功,但是在confirm阶段执行失败,则需要执行cancel操作,将之前预留的资源释放掉。
以下是一个简单的TCC事务的例子,假设有一个转账服务,需要从A账户中转移到B账户中100元、C账户中200元:
- Try阶段:转账服务首先尝试将A账户的金额冻结300元。
- Confirm阶段::如果所有的try操作都执行成功,转账服务将尝试执行解冻并转账,将金额转到B账户和C账户中。
- Cancel阶段:如果try过程中,某个转账事务执行失败。那么将执行解冻,将300元解冻。如果在confirm过程中,A->C的转账成功,但是A->B的转账失败,则再操作一次C->A的转账,将钱退回去。
TCC这种事务方案有以下优缺点:
优点:
- 灵活性:TCC适用于不同类型的业务场景,例如账户转账、库存扣减等,能够根据业务逻辑实现精细的事务控制。
- 高可用性:TCC使用分布式锁来保证分布式事务的一致性,即使其中一个节点出现故障,也不会影响整个系统的运行。
- 可扩展性:TCC采用分阶段提交的方式,支持横向扩展,可以适应更多的并发访问和业务场景。
- 性能:TCC相对于2PC来说,具有更好的性能表现
缺点:
- 实现复杂:TCC需要实现Try、Confirm和Cancel三个操作,每个操作都需要实现正确的业务逻辑和补偿机制,代码实现比较复杂。
- 存在悬挂事务问题:TCC的实现方式存在悬挂事务的问题,即在执行过程中可能会有部分子事务成功,而其他子事务失败,导致整个事务无法回滚或提交。
- 空回滚问题:TCC中的Try过程中,有的参与者成功了,有的参与者失败了,这时候就需要所有参与者都执行Cancel,这时候,对于那些没有Try成功的参与者来说,本次回滚就是一次空回滚。需要在业务中做好对空回滚的识别和处理,否则就会出现异常报错的情况,甚至可能导致Cancel一直失败,最终导致整个分布式事务失败。
- 业务代码侵入性:TCC需要将事务操作拆分为Try、Confirm和Cancel三个步骤,对业务代码有一定的侵入性,需要针对不同的业务场景进行实现。
但是其实,在实际的应用过程中,很多公司用的TCC并不是完全严格的按照上述的过程的。有的时候是这么用的:Try的时候预占用资源,如果成功了,就执行Confirm,失败了就执行Cancel,但是如果Confirm失败了或者Cancel失败了,则进行重试,直至成功。
之所以可以这么做,主要是因为在Try的过程中已经锁定了资源,那么在Confirm的时候,大概率是可以成功,而如果Confirm失败就执行Cancel,就会导致可能只是因为网络原因导致的时候就使得整个事务都Cancel了,而且这时候如果Cancel再失败怎么办呢?整个方案就会变得更加复杂了。
柔性事务-最大努力通知型方案
按规律进行通知,不保证数据一定能通知成功,但会提供可查询操作接口进行核对。这种方案主要用在与第三方系统通讯时,比如:调用微信或支付宝支付后的支付结果通知。这种方案也是结合 MQ 进行实现,例如:通过 MQ 发送 http 请求,设置最大通知次数。达到通知次数后即不再通知。
案例:银行通知、商户通知等(各大交易业务平台间的商户通知:多次通知、查询校对、对账文件),支付宝的支付成功异步回调
柔性事务-可靠消息+最终一致性方案(异步确保型)
实现:业务处理服务在业务事务提交之前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不是真正的发送。业务处理服务在业务事务提交之后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才会真正发送。
防止消息丢失:
/**
* 1、做好消息确认机制(pulisher,consumer【手动 ack】)
* 2、每一个发送的消息都在数据库做好记录。定期将失败的消息再次发送一遍
*/
CREATE TABLE `mq_message` (
`message_id` char(32) NOT NULL,
`content` text,
`to_exchane` varchar(255) DEFAULT NULL,
`routing_key` varchar(255) DEFAULT NULL,
`class_type` varchar(255) DEFAULT NULL,
`message_status` int(1) DEFAULT '0' COMMENT '0-新建 1-已发送 2-错误抵达 3-已抵达',
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
扩展知识
TCC和2PC有什么区别?
首先,二者的实现机制不同,2PC使用协调者
和参与者
的方式来实现分布式事务,而TCC采用分阶段提交的方式.
处理方式不同,2PC采用预写式日志的方式,在提交和回滚阶段需要协调者和参与者之间进行多次网络通信,整个事务处理过程较为复杂。TCC则只需要在Try、Confirm和Cancel阶段执行相应的业务逻辑。
异常处理不同,2PC需要处理网络、节点故障等异常情况,可能会导致整个事务无法提交或回滚,处理异常情况的复杂度较高。而TCC只需要处理业务异常情况,异常处理相对简单。
适用场景不同,2PC适用于对事务一致性要求较高的场景,例如银行转账等,需要保证数据致性和完整性。而TCC适用于对事务一致性要求不那么高的场景,例如电商库存扣减等,需要保证数据最终一致性即可。
最初TCC的设计是强一致性,基本上一次事务执行完之后,数据是一致的,要么都commit,要么都cancel。
但是其实在实际使用过程中,可能会采用最终一致性的思想,比如commit失败之后,进行异步重试让他尝试成功,而不是立刻cancel
Cancel失败了怎么办?
在TCC中,try和confirm失败了都要调cancel,那么cancel失败了怎么办呢?一般有以下几种处理手段:
- 记录日志&发送报警:将错误信息记录下来,方便后续分析和处理。并及时通知相关人员进行处理
- 自动重试:在一定程度上,可以通过自动重试的方式尝试多次执行Cancel操作,直到成功为止。
- 人工干预:如果重试多次还是不成功,可以报警,然后进行人工干预,可以尝试手动执行Cancel操作或者进行数据修复等。
如何选择分布式事务?
在选择一个分布式事务方案的时候,需要考虑很多因素,结合自己的业务来做考量选择。一般来说可以有以下几种选择方式:
实现成本:根据项目开发和维护的难度、成本等方面来选择合适的分布式事务方案。这几种方案中,TCC和2PC的实现成本最高,业务侵入性也比较大。
另外,事务消息、本地消息表和最大努力通知都依赖消息中间件,所以如果已有业务已经接入了消息中间件的话,那么使用成本还算可控,否则就需要考虑消息中间件部署、维护和接入成本。而且同样是消息中间件,也不是所有的都支持事务消息,这个也是需要考量的一个重要因素。
一致性要求:在一致性方面,2PC和TCC属于是可以保证强一致性的,而其他的几种方案是最终一致性的方案
根据业务情况,比如下单环节中,库存扣减和订单创建可以用强一致性来保证。而订单创建和积分扣减就可以用最终一致性即可。而对于一些非核心链路的操作,如核对等,可以用最大努力通知即可。
可用性要求:根据CAP理论,可用性和一致性是没办法同时保证的,所以对于需要保证高可用的业务,建议使用最大努力通知等最终一致性方案;对于可用性要求不高,但是需要保证高一致性的业务,可使用2PC等方案。