在一个项目中的事务,我们可以使用 Spring 的 @Transactional 来控制。
但它只支持一个 JVM,在分布式环境中,涉及到了多个系统,部署在不同的 JVM 中,就不能用 @Transactional 来控制事务了。
方案一(使用消息保证最终一致性)
以经典的转账问题为例,小明有两张银行卡,分别是 银行A 和 银行B 的,现在从 A卡 转 1000 给 B卡,分两步:A卡 余额减少、B卡 余额增加。两家银行的系统肯定不在一台服务器上,这时就用到了分布式事务。
① 银行A 给 RocketMQ 发送消息,状态为待执行。
② 银行A 处理业务。(余额减去 1000)
③ 银行A 给 RocketMQ 发送结果消息,如果 银行A 很久没有发送结果,RocketMQ 会轮询所有待执行的消息,调用 银行A 的接口,查看结果。(成功:RocketMQ 修改消息状态、失败:RocketMQ 删除消息)
④ RocketMQ 给 银行B 提供消息。
⑤ 银行B 处理业务。(余额加上 1000,如果 银行B 处理失败,会不断的去 RocketMQ 取消息并执行,所以要保证业务处理的幂等性)
方案二(尽最大努力去通知)
这个方案适用于成功最好,不成功也没有严重后果的事务。如:下单后的短信通知。
① 下单系统 处理业务。(下单)
② 如果下单成功,下单系统 给 MQ 发送消息。
③ RocketMQ 给 通知服务 提供消息。
④ 通知服务调用 短信系统 的接口。(如果失败了,通知服务会重试 N 次,N 可配置)
方案三(TCC)
这是一种强一致性方案,有补偿机制,适用于需要严格保证事务一致性的业务,如:银行。
- T(Try):检测并锁定资源。
- C(Confirm):执行。
- C(Cancel):回滚。
还是以转账问题为例,从 A卡 转 1000 给 B卡,分两步:A卡 余额减少、B卡 余额增加。
- Try:冻结 银行A 和 银行B 的账户。
- Confirm:银行A 和 银行B 处理业务。(A卡 余额减去 1000,B卡 余额加上 1000)
- Cancel:如果任何一方失败,就回滚。(需要实现回滚的业务代码)