什么是分布式事务
随着微服务架构的普及,一个大型业务系统往往由若干个子系统构成,而这些子系统又拥有各自独立的数据库。往往一个业务流程需要由多个子系统共同完成,而且这些操作可能需要在一个事务中完成。在微服务系统中,这些业务场景是普遍存在的。此时,我们就需要在数据库之上实现支持跨数据库的事务支持,这也就是大家常说的“分布式事务”。
解决方案
概述
基于可靠消息服务的分布式事务,这种实现分布式事务的方式需要通过消息中间件来实现。常见的一个例子就是支付宝账户和余额宝账户之间转账,需要将操作放在同一个事务中处理。要借助消息队列来处理此问题,简单地讲, 就是在支付宝账户扣钱的同时发送一条让余额宝账户加钱的消息到消息队列,余额宝系统一旦接收到该消息就操作数据库在自己的账户中加钱。下面来介绍基于消息中间件来实现这种分布式事务。
流程
在A系统操作账户前,首先向消息中间件发送一条消息
消息中间件收到后将该条消息持久化,但并不投递。此时下游系统仍然不知道该条消息的存在。
消息中间件持久化成功后,便向A系统返回一个确认应答;
A系统收到确认应答后,则可以开始处理系统账户操作;
A系统处理完成后,向消息中间件发送Commit请求。该请求发送完成后,对A系统而言,该事务的处理过程就结束了,此时它可以处理别的任务了。
但commit消息可能会在传输途中丢失,从而消息中间件并不会B系统投递这条消息,从而系统就会出现不一致性。这个问题由消息中间件的事务回查机制完成,下文会介绍。
消息中间件收到Commit指令后,便向B系统投递该消息,从而触发账户操作的执行;
当B系统操作执行完成后,向消息中间件返回一个确认应答,告诉消息中间件该消息已经成功消费,此时,这个分布式事务完成。
超时询问机制
若A系统在处理任务时失败,那么就会向消息中间件发送Rollback请求。和发送Commit请求一样,发完之后便可以认为回滚已经完成,它便可以去做其他的事情。
消息中间件收到回滚请求后,直接将该消息丢弃,从而不会触发B系统的任务。
但在实际系统中,Commit和Rollback指令都有可能在传输途中丢失。当出现这种情况的时候,消息中间件需要引入超时询问机制。
上游A系统需提供一个事务询问的接口,供消息中间件调用。当消息中间件收到一条事务型消息后便开始计时,如果到了超时时间也没收到A系统发来的Commit或Rollback指令的话,就会主动调用A系统提供的事务询问接口询问该系统目前的状态。
投递流程
当上游系统执行完任务并向消息中间件提交了Commit指令后,便可以处理其他任务了,此时它可以认为事务已经完成,接下来消息中间件一定会保证消息被下游系统成功消费掉!那么这是怎么做到的呢?这由消息中间件的投递流程来保证。
如果消息在投递过程中丢失,或消息的确认应答在返回途中丢失,那么消息中间件在等待确认应答超时之后就会重新投递,直到下游消费者返回消费成功响应为止。中间件可以设置消息重试的次数和时间间隔,比如:当第一次投递失败后,每隔五分钟重试一次,一共重试3次。如果重试3次之后仍然投递失败,那么这条消息就需要人工干预。
和TCC方案对比
TCC是业务层面的两阶段提交协议,实时性要求比较高,数据必须可靠。事务操作涉及系统间的多次通信、协调,性能相对较差,不适合在生产环境下有高井发和高性能要求的场景。
基于可靠消息则是最终一致性,可以异步,但数据绝对不能丢。优势是消息独立存储,业务系统和消息系统耦合性低,性能较高,适合在生产环境下有高井发和高性能要求的场景。
代码实现
该实例是用rocketmq来实现。
首先通过sendMessageInTransaction()方法发送事务消息,先向Broker发送一条预处理消息
此时需要监听mq消息,发送成功则会回调executeLocalTransaction方法,本地事务处理完成则会将消息状态改为可消费
还需要实现事务状态的回查
消费者消费消息