大家好,我是小米,一个 31 岁、热爱分享、沉迷写代码和撸串的大哥哥。这几天接了个项目,越做越觉得有意思,今天忍不住来跟大家唠唠。
项目的需求一上来,我差点以为自己在写一篇「分布式事务实践指南」,一边做一边想,这不就是分布式事务的标准案例吗?!
好了,话不多说,先给大家讲讲我是怎么接触到这个项目的,以及背后是个啥场景。
故事的开头 | 接到项目需求那一刻事情是这样的,上周五,刚刚撸完串,回到家正打算刷一集《老三国》,项目经理飞哥突然微信滴滴我。
飞哥:小米,有个新项目,你看看需求行不行,能不能接。
我一看,哟呵,还挺有意思。这个项目一共有三个主体系统:
门户系统:管理渠道积分额度,类似于一个“大管家”,给每个渠道发额度。
商城系统:负责订单下单、支付,用积分抵扣,最终结算。
各个渠道系统:每个渠道有自己的积分池,用户具体的积分额度和扣减都在这处理。
大致流程是这样:
商城在用户提交订单时,需要先去门户查一下每个渠道的剩余额度。
再去各个渠道查该用户的具体积分额度。
确认无误后,商城要分别调用每个渠道扣减积分接口。
最后,商城再调用门户扣减渠道额度接口。
当时我一看,直觉就来了——这不就是典型的分布式事务吗?
如果你和我一样干开发,听到这里,心里八成和我一样,立刻就冒出一堆问号:
万一某个渠道扣减失败了咋办?
万一门户扣减的时候出错,渠道扣掉了,门户没扣成功,咋兜底?
万一网络不稳定,中间接口超时怎么办?
是的!这就是分布式事务的魅力——好用但是难搞!
项目拆解 | 三个主体、四个关键点为了理清楚这件事,我特意画了个小流程图,方便给自己捋捋清楚:
商城 → 门户:查渠道可用额度
商城 → 各渠道:查用户可用积分
商城 → 各渠道:扣减用户积分
商城 → 门户:扣减渠道额度
这中间,涉及到了两个资源维度:
渠道维度(门户控制的总额度)
用户维度(各渠道系统控制的个人积分)
然后事务性要求是:
要么全成功,扣减生效;
要么全部回滚,保持原样。
听起来是不是很眼熟?对,这就是咱们分布式事务老朋友要登场的时候了。
为什么是分布式事务?我当时第一反应就是:
啊这,普通事务搞不定啊!
因为涉及到:
多个不同的服务、系统(门户、多个渠道)
多个独立数据库/服务接口(不同渠道积分存储在不同系统)
非同库、非同源,没法搞本地事务。
所以,我们需要一个「跨服务的事务一致性机制」,也就是分布式事务。
常见场景有:
电商秒杀
金融转账
积分兑换
复杂结算
我们这个项目,正好符合“积分兑换+结算”的经典分布式事务模式。
技术方案怎么选?这个项目刚开始的时候,领导说用传统 2PC(两阶段提交),我直接摇头:
不行,分布式 2PC 太重,性能差,而且很容易死锁,万一有个超时,锁在那好几个小时,运营不得给我打电话骂街?
后来我主推了几个思路:
方案一:可靠消息最终一致性(基于消息队列)
商城扣减用户积分、渠道额度前,先发“预扣减”消息到 MQ。
各个渠道/门户异步消费消息,执行扣减。
执行成功后,返回确认消息。
如果执行失败,自动补偿/人工干预。
优点:
高性能
解耦
不阻塞主业务流程
缺点:
最终一致性,不能做到强一致。
方案二:TCC(Try-Confirm-Cancel)模式
Try:预留资源(比如锁住额度)
Confirm:正式扣减
Cancel:释放资源
优点:
实时性好,强一致
控制粒度细
缺点:
接口复杂,开发成本高
每个渠道都要实现三套接口(Try、Confirm、Cancel)
方案三:Seata 分布式事务框架
如果对方渠道也支持 Seata,咱直接用 Seata 的 AT/TCC/XA 模式,省事儿。
不过这个方案被砍掉了—— 因为:
渠道是第三方,改造不了他们的接口。
没法嵌入 Seata。
所以,最终我们拍板定了: 可靠消息+补偿机制
先做到最终一致,再做补偿兜底。
最终设计方案整个设计是这样:
商城系统:
提交订单,发 MQ 消息(包含订单号、扣减明细)
积分扣减、渠道额度扣减 → 异步执行
各渠道系统:
监听 MQ,执行积分扣减,返回执行结果
门户系统:
监听 MQ,执行额度扣减,返回执行结果
失败:
消息重试 + 死信队列
手动补偿
这里我们用的是 RocketMQ,选它主要是因为:
高吞吐
顺序消息支持好
死信队列机制完善
同时,所有的扣减请求都要幂等,怎么做? 扣减接口做幂等控制,订单号+渠道+用户 唯一标识
如果收到重复的请求,直接返回成功。
扣减顺序为什么要这样设计?这里其实很讲究:
先扣渠道,再扣门户:
避免门户额度扣了,渠道扣不动。
门户额度最后扣:
保证各渠道扣减成功,门户额度再扣,避免资源浪费。
就像点外卖:
确认好菜都能做,店铺才结算价格。
如果顺序反了,扣了额度,菜做不了,那就麻烦了。
做完的几个小经验写到这儿,我觉得这项目让我收获了不少,有几个点,跟大家分享下:
分布式事务不是一定要“强一致”
很多时候,最终一致+补偿机制更灵活可靠。
设计幂等永远是首要
即使消息多发、重复扣减,只要幂等,系统就稳。
MQ 死信队列一定要配好
异常情况一定要兜底,不然数据就会脏。
尽量避免全链路事务锁
性能杀手!能异步、能拆分就拆分。
END写这篇文章,其实更多是记录下我最近做这个分布式事务项目的一些思考和总结。希望能给正好在做类似项目或者对分布式事务感兴趣的朋友一些参考。
如果你们感兴趣,我后面还可以专门写一篇:
可靠消息一致性方案
TCC 落地实战
RocketMQ 死信队列实操
如果你觉得文章对你有点用,别忘了点个【在看】和【转发】,让更多朋友看到,谢谢啦~