码上拾光

延迟队列加本地任务,我为什么没有上分布式事务框架

· #架构#分布式事务#Java

电商项目里有两处典型的”跨服务一致性”:下单时锁优惠券和库存,超时后要释放;发放流量包,支付成功后要到账。当时评估过分布式事务框架,最后两处都没用,用的是 MQ 延迟队列加本地任务表。这篇把理由写下来。

场景

下单链路:验价 → 锁优惠券 → 锁库存 → 创建订单 → 发起支付。支付有超时,超时未支付要把券和库存放回去。

三个服务,两条失败路径(下单中途失败、支付超时),都要保证券和库存最终被释放。

为什么不上框架

框架能做到强一致,但有三个成本我不想付:

  1. 链路上每个服务都要接入,包括那些本来很简单的服务。
  2. 锁持有时间长。全局事务期间资源被占着,下单是高并发接口,这个代价在压测里很明显。
  3. 故障面变大。协调器成了新的单点,它出问题整条链路都停。

而业务上,券和库存”晚几秒释放”完全可以接受。这就是典型的”要最终一致,不要强一致”。

做法

两个部件:

延迟队列。 用 RabbitMQ 的 x-message-ttl 加死信交换机实现。下单成功后发一条延迟消息,TTL 就是支付超时时间。到期后消息进死信队列,消费者检查订单状态:已支付就忽略,未支付就关单并释放。

本地任务表。 锁券、锁库存的时候,在对应服务本地写一条任务记录(状态:锁定)。释放消费者来了先查这张表,按记录释放,释放后改状态。任务表是幂等的依据——消息重复投递、消费者重启,都不会释放两次。

下单 ──► 锁券(写任务表) ──► 锁库存(写任务表) ──► 建订单 ──► 发延迟消息
                                                              │ TTL 到期

                                                    查订单状态 → 未支付

                                              按任务表释放券、释放库存、关单

边界情况

压测结果

去掉全局事务之后,领券接口单机压到了每秒一万次请求,之前带框架时远没到这个数。这不是框架不好,是这个场景不需要它。

后来

流量包发放也用了同样的结构:支付回调发消息,消费者按任务表异步到账。两个场景验证下来,我对”最终一致”的信心比以前足很多。分布式事务框架不是不能用,而是要先问一句:这个场景真的需要强一致吗?大多数时候,答案是不需要。


← 回到文章列表