分布式事务解决方案:最佳实践 —— 一个被裁外包仔的深夜血泪复盘

#郭洋
2025-12-13 05:53
阅读 419

去年十月,我坐在上海浦东一间月租3500的合租房里,盯着屏幕上不断报错的日志,手指在键盘上微微发抖。就在三天前,我刚被那家曾经“福报满满”的大厂优化了——HR说这是“组织架构调整”,但我知道,其实就是“你走吧,钱给够”。

失业后,我咬牙接了个外包项目:帮一家做跨境生鲜电商的小公司重构订单系统。甲方老板姓王,一口东北腔,第一次视频会议就说:“兄弟,这单做好了,后面还有!咱不差钱!”(后来才知道他差得挺狠,尾款拖了两个月。)

项目背景:React + Java + Python 的“三体”组合

这个系统是个典型的微服务架构:

  • 前端:React 写的管理后台(UI 是外包给另一个自由职业者做的,配色像90年代网吧)
  • 订单服务:Spring Boot(Java),负责下单、支付状态更新
  • 库存服务:Python + FastAPI,管仓库里的牛油果和车厘子
  • 积分服务:也是 Java,用户下单送积分

问题来了:用户下单时,要同时扣库存、创建订单、加积分。三个服务,跨语言、跨数据库,还不能出错。一旦库存扣了但订单没生成,用户白拿水果;订单生成了但积分没加,客服电话能打爆。

这就是典型的分布式事务场景。


初期方案:天真如我,直接 try-catch?

第一版代码,我写得贼自信:

// Java 订单服务
public void createOrder(OrderDTO dto) {
    inventoryClient.decreaseStock(dto.getProductId(), dto.getQuantity()); // 调 Python 库存
    orderRepo.save(dto); // 本地事务
    pointService.addPoints(dto.getUserId(), 100); // 调 Java 积分
}

结果上线第三天,库存服务因为 Redis 连接池爆了,返回 500。订单创建成功,但库存没扣——用户付了钱,仓库却没发货。王老板凌晨两点打电话吼我:“兄弟,这车厘子是智利空运的!一单亏80!”

我当时瘫在椅子上,心想:完了,这外包费怕是要搭进去了。


痛定思痛:引入 Saga 模式

翻遍了阿里、腾讯的技术博客,又啃了《数据密集型应用系统设计》,我决定用 Saga 模式——本质就是“每个操作配一个补偿操作”,失败就一路回滚。

具体拆解:

  1. 创建订单(状态:待支付)→ 失败?直接删订单
  2. 扣库存 → 失败?调 increaseStock 补偿
  3. 加积分 → 失败?调 deductPoints 补偿

但怎么保证“补偿一定能执行”?总不能靠人肉盯监控吧?

关键一招:本地消息表 + 定时对账

我在订单服务里加了一张 outbox_message 表,每次操作成功就往里面塞一条消息,比如:

{ "type": "DECREASE_STOCK", "payload": { "productId": 123, "qty": 2 }, "status": "PENDING" }

然后起个定时任务(每30秒扫一次),把 PENDING 的消息发出去。如果库存服务挂了,消息会一直重试,直到成功或人工介入。

为什么不用 Kafka 或 RocketMQ?
因为甲方预算只够买一台 4C8G 的云服务器!消息队列?那是奢侈品。


前端也得配合:React 的 loading 状态别乱跳

有意思的是,前端也有坑。之前 React 页面一提交就立刻跳转到“支付成功”,但后端可能还在 Saga 流程中。用户以为成功了,其实库存还没锁。

我和前端小哥(远程协作,微信沟通)商量后,在提交按钮加了三层状态:

  • 提交中(禁用按钮 + spinner)
  • 后端处理中(显示“正在为您锁定库存…”)
  • 最终成功/失败

他还偷偷加了个 Easter Egg:如果流程超过10秒,按钮会变成“老板,再等等,牛油果快冻住了!”——王老板居然觉得这很可爱。


Python 服务的坑:异步 vs 同步

库存服务是 Python 写的,我一开始用了 async def decrease_stock,想着提升吞吐。结果和 Java 的 Feign Client 对接时,HTTP 超时设置没对齐,经常“假失败”——其实库存扣了,但 Java 没收到响应,触发了补偿,反而多加了库存!

最后忍痛改成同步接口,虽然性能差点,但稳了。分布式系统里,一致性比吞吐量重要一百倍——这是我用三晚失眠换来的教训。


成本 vs 可靠性:外包狗的生存哲学

说实话,如果这是大厂项目,我会直接上 Seata 或 TCC。但外包项目?甲方老板听到“中间件”三个字眼睛就瞪圆:“又要买服务器?”

所以我的最佳实践其实是:

在预算允许的范围内,用最土但最可控的方式解决问题。

本地消息表虽然原始,但数据库你总有吧?定时任务你总会写吧?不需要运维、不需要新组件,出了问题你自己就能查。


回老家?还是继续漂?

上周五晚上,老婆视频问我:“深圳那边有个外包岗,月薪18k,要不要试试?”
我说:“再看看,现在这单尾款快结了,净赚3万5。而且我发现,自己干虽然累,但技术深度比在大厂拧螺丝强多了。”

她沉默了一会,说:“那你别熬太晚,胃药在抽屉第二格。”

我看着屏幕上跑通的 Saga 流程日志,突然觉得,分布式事务和人生有点像:没有完美的 ACID,只有不断补偿、不断重试的最终一致性。


给同行的几点建议(血泪总结)

  1. 别迷信理论:Saga、TCC、2PC 各有适用场景。小项目别硬上 Seata,搞不定还得背锅。
  2. 日志要打透:每个步骤、每个补偿操作,都要有 trace_id 串联。不然半夜报警你根本不知道哪环崩了。
  3. 前端也是防线:别让用户以为“点了就成功了”。状态反馈要诚实。
  4. 预留人工干预入口:再完美的自动化,也会遇到“智利车厘子断供”这种黑天鹅。后台要有手动触发补偿的按钮。
  5. 保护好自己:合同写清楚“因第三方服务不可用导致的问题不担责”——别问我怎么知道的。

现在,我开始认真考虑回老家了。成都房租2000,生活节奏慢,远程接单也能活。技术这东西,不管在哪,只要能解决问题,就有饭吃。

分布式事务教会我的,不仅是如何让三个服务协同工作,更是如何在不确定的世界里,守住自己的“一致性”。

共勉,各位在外漂泊的码农兄弟。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝