MQ实现分布式事务最终一致性
2021-02-25 08:01:01 # 微服务

前言

今日闲暇之时,忽心血来潮想一睹 dotnetcore/CAP 的风采,个人感觉再去见 dotnetcore/CAP 之前先了解消息队列实现分布式事务的最终一致性解决方案,有助于更好的阅读和理解源码。

举个栗子

我们以订单创建为例,涉及到两个服务 订单服务库存服务 ,场景就是 订单创建成功,发送消息告诉库存系统减掉库存
订单系统先创建订单,发送消息给下游处理;如果订单创建成功,然而消息没有发送出去,那么下游所有系统都无法感知到这个事件,会出现脏数据;
该方案存在一下的问题:

通过上面的场景描述,我们可以很容易的写出如下代码:

先执行本地业务操作,再发送消息。

1
2
3
4
5
6
7
8
// 开始事务
try {
// 1.执行数据库操作(订单创建)
// 2.提交事务
}catch (Exception e){
// 3.回滚事务
}
// 4.发送 mq 消息

这个方案存是存在如下问题:

  • 如果订单创建成功(本地事务),然而消息没有发送出去,那么下游所有系统都无法感知到这个事件,会出现脏数据。
  • 如果先发送订单消息,再创建订单;那么就有可能消息发送成功,但是在订单创建的时候却失败了,此时下游系统却认为这个订单已经创建,也会出现脏数据。

基于上面的方案我们又可以改进得到如下的代码:

将本地业务和发送消息放到一个事务里面执行。

1
2
3
4
5
6
7
8
// 开始事务
try {
// 1.执行数据库操作
// 2.发送 mq 消息
// 3.提交事务
}catch (Exception e){
// 4.回滚事务
}

上面代码看起来确实没什么问题,消息发送失败,回滚事务。
但是实际上第二步有可能存在消息已经发送到 MQ 服务端,但是由于网络问题未及时收到 MQ 的响应消息,从而导致消息发送端认为消息发送失败。
这就会导致订单事务回滚了,但是手续费系统却能消费消息,两边数据库又不一致了。
熟悉 MQ 的同学,可能会想到,消息发送失败,可以重试啊。
是的,我们可以增加重试次数,重新发送消息。但是这里我们需要注意,由于消息发送耦合在事务中,过多的重试会拉长数据库事务执行时间,事务处理时间过长,导致事务中锁的持有时间变长,影响整体的数据库吞吐量。实际业务中,不太建议将消息发送耦合在数据库事务中。

如何来解决以上的问题呢?

本地消息表(ebay)方案

本地消息表这个方案最初是ebay提出的 ebay的完整方案:链接