一:什么是分布式事务?
分布式事务是指在分布式系统中涉及到多个数据库或多个应用程序之间的事务处理,这些数据库或应用程序可能分布在不同的物理节点上,甚至可能位于不同的地理位置。在分布式事务中,需要确保所有参与者的事务操作都能够保持一致性,即所有参与者的事务要么全部提交成功,要么全部回滚。
二:常见的分布式事务方案有哪些?
分布式事务的目的是保证分布式系统中的多个参与者的数据能够保证一致性。在一次写操作过程中要么都成功,要么都失败。至于这个一致性到底是怎样的一致性,是强一致性、还是最终一致性,不同的分布式事务方案其实达到的效果并不相同。
如果想要实现强一致性,那么一定要引入一个协调者,这类方案包含基于XA规范的二阶段及三阶段提交、以及支持2阶段提交的第三方框架,如Seata,还有TCC也是一种强一致性的方案。
如果想要实现最终一致性,常见的本地消息表、事务消息、最大努力通知都能实现。
三:什么是2阶段提交及存在的问题?
第一阶段:准备阶段,参与者将操作成败通知协调者。
第二阶段:提交阶段,协调者根据所有参与者的反馈结果决定各参与者是否提交操作还是中止操作。
问题:
- 同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的
- 单点故障。一旦协调者发生故障,参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。
- 数据不一致性。在提交阶段中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,只有一部分参与者接受到了commit请求,其他未接到commit请求的机器则无法执行事务提交,于是整个分布式系统便出现了数据不一致性的现象。
四:什么是3阶段提交及存在的问题?
3阶段提交把2阶段提交的准备阶段一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。同时在协调者和参与者中都引入超时机制。
存在问题:在doCommit阶段,如果参与者无法及时接收到协调者的doCommit,会在等待超时之后,继续进行事务的提交。这样就和其他接到doCommit命令的参与者之间存在数据不一致的情况。
五:什么是TCC,和2PC有什么区别?
TCC是基于业务逻辑的补偿机制,将整个分布式事务分解为若干个子事务,每个子事务都有一个try、confirm和cancel三个操作,通过这些操作来实现分布式事务的执行和回滚
具体来说,TCC事务包括以下三个步骤:
- Try:在try阶段,参与者尝试执行本地事务,并对全局事务预留资源。
- Confirm:如果所有参与者的try阶段都执行成功,协调者通知所有参与者提交事务,执行confirm阶段,这时候参与者将在本地提交事务,并释放全局事务的资源。
- Cancel:如果任何一个参与者在try阶段执行失败,则协调者通知所有参与者回滚事务。那么就要执行cancel阶段,将之前预留的资源释放掉。
TCC和2PC两者区别:
(1) 二者的实现机制不同,2PC使用协调者和参与者的方式来实现分布式事务,而TCC采用分阶段提交的方式。
(2)处理方式不同,2PC采用预写式日志的方式,在提交和回滚阶段需要协调者和参与者之间进行多次网络通信。TCC则只需要在Try、Confirm和Cancel阶段执行相应的业务逻辑。
(3)适用场景不同,2PC适用于对事务一致性要求较高的场景,例如银行转账等。而TCC适用于对事务一致性要求不那么高的场景,例如电商库存扣减等。
六:TCC的空回滚和悬挂是什么?如何解决?
空回滚问题:TCC中的Try过程中,有的参与者成功了,有的参与者失败了,这时候需要所有参与者都执行Cancel,对于那些没有Try成功的参与者来说,本次回滚就是一次空回滚。
悬挂事务问题:一次分布式事务,先发生了Try,因为有的节点失败,又发生了Cancel,而下游的某个节点因为网络延迟导致先接到了Cancel,在空回滚完成后,又接到了Try的请求,然后执行了,导致这个节点Try占用的资源无法释放,就会导致事务悬挂。
解决以上两个问题,引入分布式事务记录表。
空回滚解决:当一个参与者接到一次Cancel请求的时候,先去表中根据tx_id查询是否有try的记录,如果没有,则进行一次空回滚即可。并在表中创建一条记录,状态标记为cancel。
事务悬挂解决:当一个参与者接到一次Try请求的时候,先去表中根据tx _id查询是否有记录,如果存在,并且记录的状态是cancel,则拒绝本次try请求。
七:如何基于MQ实现分布式事务
在MQ的分布式事务方案中,从大的方面来说可以分为两种:
- 可靠消息最终一致性:依赖可靠的消息,来实现一种最终一致性的模型,想要做到让这个消息可靠,一般由两种做法,本地消息表和事务消息,本地消息表的方案是基于本地事务+重试,来保证MQ消息一定可以发出去。事务消息是基于MQ的事务消息机制,把一条消息拆成两个half消息,通过2阶段的方式+回调反查来保证消息一定能发出去。
- 最大努力通知:使用消息,但是这个消息并不要求一定可靠。一般只依赖重试机制,来做最大努力的通知事务参与者
八:如何基于本地消息表实现分布式事务?
这个是将分布式事务拆分为本地事务和消息事务两个部分,本地事务在本地数据库中进行提交或回滚,而消息事务则将消息写入消息中间件中,以实现消息的可靠投递和顺序性。
九:基于本地消息表实现分布式事务代码实践
比如交易下单环节,保证订单系统和用户积分系统之间的最终一致。用户下单后,订单需要创建成功,用户积分也要增加成功,如果有一个失败了,这都是没满足一致性。
具体实现:
-
在订单服务的数据库中创建一张本地消息表,表就用来记录本地消息的
-
编写代码,需要把本地消息通过MQ发出去。这里就可以有很多办法了,一种是异步扫表,还可以直接同步发消息,也可以借助Spring Event来异步处理,都是可以的
@Autowired TransactionTemplate transactionTemplate; public void order(OrderDTO orderbTo){ boolean transactionSuccess = transactionTemplate.execute(new TransactionCallbach @Override public Boolean doInTransaction(TransactionStatus status){ try { orderServive.createOrder(orderDTO); messageService.createMessage(orderDTO); //以上执行如果未抛异常,则成功,返回true return true;//表示事务执行成功 } catchKException e){ //如果发生异常,则标记事务为回滚 status.setRollbackonly(); return false;// 表示事务执行失败 ); if (transactionsuccess){ //事务执行成功,可以执行 mqService.send(orderDTO) mqService.send(orderDTo); messageService.updateSuccess(orderDTo); } else { //事务执行失败的处理逻辑 //可以抛出异常或记录日志等 }
-
如果MQ发送消息失败,下一次再通过定时任务扫表把需要处理的事件查出来重发就行了。所以本地消息表中还需要有一个定时任务
十:RocketMQ的事务消息是如何实现的?
- 发送半消息:应用程序向RocketMQ Broker发送一条半消息,该消息在Broker端的事务消息日志中被标记为“prepared”状态。
- 执行本地事务:RocketMQ会通知应用程序执行本地事务。如果本地事务执行成功,应用程序通知RocketMQ Broker提交该事务消息。
- 提交事务消息:RocketMQ收到提交消息以后,会将该消息的状态从“prepared”改为“committed”,并使该消息可以被消费者消费。
- 回滚事务消息:如果本地事务执行失败,应用程序通知RocketMQ Broker回滚该事务消息,RocketMQ将该消息的状态从“prepared”改为“rollback”,并将该消息从事务消息日志中删除。从而保证该消息不会被消费者消费。
十一:什么是最大努力通知?
所谓最大努力通知,就是不保证100%通知到。这种分布式事务的方案,通常也是借助异步消息进行通知的。发送者将消息发送给消息队列,接收者从消息队列中消费消息。在这个过程中,如果出现了网络通信故障或者消息队列发生了故障,就有可能导致消息传递失败,即消息被丢失。因此,最大努力通知无法保证每个接收者都能成功接收到消息,但是可以尽最大努力去通知。