一句话:
分布式事务发起方和联动方割裂开来,通过记录跟分析前一个事务的运行状态,决定下一步的行为是回
滚还是开启下游事务
具体点
"割裂":
割裂意味着每个事务是独立提交的,这是最终一致模型。从代码上来开,也不再是同一个方法内,
通过调用开启另外的事务了。即原来的业务逻辑:
func(){
执行本地事务A
执行远程事务B
}
割裂后业务逻辑:
func(){
执行本地事务A
在事务A提交的同时记录事务信息
}
"记录跟分析前一个事务的运行状态":
先谈谈如何记录事务的运行状态信息。我们最容易想到的方案就是在同一个本地事务内,提交的同
时插入一条事务状态信息数据即可。这样,只要我们保证了事务提交,必然有相关信息数据持久,
对一个分布式事务而言,我们可用唯一的xid字段去标记,如此,无论这一组次的子事务状态数据
是否在同一个库,我们都能轻易的找到他们。
另外,在事务提交的同时,如果能够发送一条可靠消息,那么也能达到持久的目的,这是消息驱动
采用的方式,但在后面会说到,发送一条可靠消息,可能并不是那么容易。
"去决定下一步的行为":
我们不妨从分布式事务的源头来看,发起方的本地事务提交了,假如相关的带有唯一标志xid的事
务信息被插入了数据库,这个时候,如果我们有一个独立的观察者,发现了这样的一条事务信息,
若检查事务信息的状态是本地提交,那么观察者会根据相关的业务信息去开启下游事务逻辑,直至
整个业务逻辑完成,或执行各自的回滚逻辑,这便是消息驱动的核心原理。
思考一下,独立的观察者的实现方案。
1。可以通过异步的定时任务去捞取信息
2。可以通过订阅binlog去实现事务信息的消费
3。借助于消息中间件MQ,这是本章将介绍的内容
我们采用MQ的模式,于是整个分布式事务的逻辑变成了这样:
提交上游事务 -> 发送了一条可靠消息 -> 下游事务消费消息 -> 回滚或者继续驱动
更进一步
消息驱动的链路逻辑清楚了,但是这条链路上的问题依旧很多,这里我们进入更深一步的讨论。
1。如何在上游事务提交的同时发送一条可靠消息。
从单个事务的生命周期来看,我们无外乎在提交前跟提交后发送消息而已,假设先在MQ正常的情况
下讨论
a.如果在提交前发送消息,本地事务异常失败了,消息却被消费了,明显产生了问题。
b.如果在提交后发送消息,jvm挂了或者网络问题,引发消息发送失败,这又产生了问题。
其实解决也不复杂,我们只需要在提交前发送一种不被消费的消息,在提交后再修改这条消息的状
态让它具备被消费的能力即可,这种功能在RocketMq被叫做事务消息,没有被确认状态的消息称为
半消息。
回头看
问题a 被解决了,没有被确认的消息叫做半消息,不能被消费,我们借助于发送消息的ack机制,
保证事务提交前发送成功。
在看问题b 半消息发送了,本地事务提交后jvm挂掉了,怎么办? 聪明的你已经想到了,那就是和
本地事务一同插入的,本地事务表信息!
我们提供给mq一个回查机制,当半消息迟迟得不到确认,那么去回查,发现相关xid标记的本地事
务已经插入了且状态是本地已经提交了,那么自然而然,这条半消息状态可以修改为已完成,可以
被消费了。
否则呢?否则这条消息就应该状态修改为回滚!让相关的消费服务执行回滚逻辑!
2。如何保证能够正确的消费掉这条事物消息
消费方需要做到接口等幂,若多次尝试消费失败,最终到达死信队列。
简单总结
对比TCC方案,很明显,我们不要去做最难的业务资源预留的设计。
借助于中间件,提升了维护难度,还是买人家的服务来用吧。
网友评论