请写一篇关于【分布式事务解决方案:最佳实践】的技术文章
作者注:本文写于2024年5月的一个深夜,天通苑某出租屋内,两个娃刚睡着。老婆在隔壁房间刷《庆余年2》,我偷偷摸出键盘——别问,问就是“奶爸的夜修时间”。坐标北京,房租3500(合租次卧),月薪22k(税后不到16k),白天在一家勉强能活下来的SaaS公司做Java后端,晚上是哄睡特种兵。
一、凌晨2点,我差点删了整个项目
去年十月一个周五晚上,我正抱着老二拍嗝,手机突然震动——钉钉弹出一条告警:“订单创建成功,但库存未扣减”。
心一下沉到脚底板。
这可不是小问题。我们那个Java项目刚上线三个月,用户量刚破十万,老板天天在群里喊“要稳!要稳!”。结果现在,用户付了钱,仓库却没发货——典型的分布式事务一致性崩盘现场。
我赶紧把娃塞给老婆:“你先哄睡,我得上线看看。”
老婆翻了个白眼:“又是bug?上周不是说系统很稳吗?”
我说:“这次是‘跨服务数据不一致’,听起来高大上,其实就是俩微服务打架,谁也不认账。”
坐到电脑前,已经是凌晨1:47。打开IDEA,盯着那几行代码,手都在抖。脑子里全是面试官曾问过的问题:“你们怎么保证分布式事务的一致性?” 当时我嘴硬答“用Seata”,可现在……Seata配置错了,TCC回滚没触发,消息队列积压了三千条死信。
那一刻,真的想删库跑路——虽然我只是个租住在天通苑、连车都没有的普通程序员。
二、从“我以为我会”到“我其实不会”
其实这事怪不得别人,全怪我自己飘了。
年初跳槽时,HR问我:“有分布式系统经验吗?”
我说:“当然有!做过微服务拆分,用过Spring Cloud Alibaba,还研究过CAP理论。”
HR眼睛一亮:“那薪资可以谈。”
于是月薪从15k涨到22k——多出来的7k,全靠一张嘴撑着。
但现实狠狠打了脸。我们的项目架构说白了就是:
- 订单服务(Java + Spring Boot)
- 库存服务(也是Java + Spring Boot)
- 支付回调走RocketMQ
看似标准,实则脆弱得像纸糊的。一开始为了赶上线,我和同事偷懒用了本地事务 + 异步消息的方案——订单入库成功后发个MQ消息去扣库存。
问题在哪?
如果消息发送失败,或者库存服务挂了,订单就“悬空”了。用户以为买成了,其实仓库根本没动。
更惨的是,我们连幂等性都没做好。有一次MQ重试,同一个订单被扣了三次库存。运营小姐姐直接冲进技术部:“你们是不是在薅用户羊毛?”
那天晚上,我蹲在天通苑地铁站外啃煎饼果子,边吃边想:分布式事务,真不是背几个名词就能搞定的。
三、痛定思痛:我开始认真搞“一致性”
不能再糊弄了。我给自己定了个规矩:每晚娃睡后,至少学一小时,雷打不动。
于是,接下来一个月,我的“夜修课表”是这样的:
- 周一、三:看《分布式系统原理与范式》
- 周二、四:跑GitHub上的开源案例
- 周五:复盘当天线上问题
- 周末:带娃之余,用纸笔画事务流程图(老婆说我魔怔了)
重点研究了三种主流方案,结合我们项目的实际情况,做了对比:
1. 2PC(两阶段提交) —— 理论很美,现实很骨感
优点:强一致性,ACID拉满。
缺点:性能差、阻塞严重、对数据库压力大。
我们试了一下Atomikos(Java的JTA实现),结果QPS直接掉了一半。老板问:“为啥系统变卡了?” 我只能苦笑:“我们在追求‘绝对正确’的路上,把用户体验弄丢了。”
结论:适合金融级场景,但我们这种日活几万的小SaaS,扛不住。
2. TCC(Try-Confirm-Cancel) —— 灵活但写起来想哭
TCC要求每个业务操作拆成三步:
- Try:预留资源(比如冻结库存)
- Confirm:真正执行
- Cancel:回滚
听起来很优雅,但代码量爆炸。一个简单的下单,要写三套逻辑,还要处理各种异常状态。更坑的是,Cancel必须幂等且可靠——万一Cancel本身失败了,那才叫“雪上加霜”。
我们在测试环境跑了两周,光是状态机就写了800多行。同事吐槽:“这哪是写业务,这是在造轮子+造火箭。”
结论:可控性强,但开发成本太高,小团队慎入。
3. 基于消息队列的最终一致性 —— 我们最终的选择
这条路的核心思想是:用可靠消息 + 本地事务 + 幂等消费,实现“最终一致”。
具体做法:
- 订单服务开启本地事务,插入订单 + 插入一条“待发送”的消息记录(同库)
- 事务提交后,由定时任务或监听器发送MQ消息
- 库存服务消费消息,执行扣减(需幂等)
- 消费成功后,回标消息为“已处理”
关键点在于:消息和订单在同一个本地事务里,避免“订单成功但消息没发”的问题。
我们选了RocketMQ的事务消息机制(比Kafka更适合这个场景),并参考了GitHub上一个叫 distributed-tx-demo 的开源项目(作者star不多,但代码清晰,注释良心)。
改完之后,压测QPS稳在1200+,数据一致性也达标了。最重要的是——代码改动不大,团队能快速上手。
四、安全意识:别让“一致性”变成“灾难源”
很多人只关注“如何保证一致”,却忽略了安全边界。
举个例子:我们最初的消息体是明文JSON,包含用户ID、商品ID、数量。后来被安全团队审计,说“可能被中间人篡改”。
于是我们加了三道防线:
- 消息签名:用HMAC-SHA256对消息体签名,消费端验证
- 敏感字段脱敏:用户手机号、地址等绝不进消息
- 权限隔离:MQ Topic按业务划分,库存服务只能读自己的Topic
还有一次,测试同学发现:重复消费会导致库存负数。
我一拍脑袋——忘了加“库存充足校验”!
赶紧在扣减逻辑前加了判断:if (stock < 0) throw new BusinessException("库存不足")。
这些细节,文档里不会写,面试也不会问,但线上事故往往就藏在这些角落里。
五、GitHub不是终点,是起点
很多人以为,找到一个GitHub项目clone下来就能解决问题。
但现实是:开源代码只是骨架,血肉得你自己长。
比如我们用的那个demo,它假设网络永远可靠、服务永不宕机。可我们生产环境呢?
- 天通苑宽带偶尔抽风
- 阿里云ECS半夜自动重启
- 运营小姐姐误删数据库(真的发生过)
所以我们做了大量适配:
- 加了死信队列处理N次失败的消息
- 写了补偿Job,每天凌晨扫一遍“悬空订单”
- 用Prometheus + Grafana监控事务成功率
这些代码,我整理后也push到了公司的私有GitLab(不敢放GitHub,怕被同行笑)。但核心思路,其实都来自社区。
所以我想说:别迷信轮子,但要善用轮子。站在巨人肩膀上,才能看得更远。
六、写在最后:一个奶爸的碎碎念
现在,系统已经稳定运行半年了。老板没再在群里@我,运营也没再冲进办公室。上周五发工资,看到22k到账,心里总算踏实了点。
但我知道,这远远不够。分布式事务只是冰山一角,后面还有限流、熔断、链路追踪……每一座都是大山。
可怎么办呢?娃要养,房贷(哦不,是房租)要交,技术不能停。
唯一的办法,就是把碎片时间榨干——
地铁上刷LeetCode,午休看论文,娃睡后写代码。
有时候累到眼皮打架,但想到“如果系统崩了,明天就得去送外卖”,又猛地清醒过来。
我想对和我一样的普通程序员说:
我们不是天才,没有大厂光环,住在天通苑,拿着中等薪资,但只要肯沉下心,一点一点啃,总能搞定那些看似高不可攀的问题。
分布式事务很难吗?难。
但比起哄睡一个不肯闭眼的两岁娃,它简直太讲道理了。
结语:一致性,不仅是技术,更是态度
技术上的“一致性”,本质是对确定性的追求。
而生活中的我们,何尝不在追求一种“确定性”?
——确定孩子健康,确定工作安稳,确定明天还能坐在电脑前,写出一行不报错的代码。
分布式世界没有银弹,人生也没有。
但我们可以选择:认真对待每一个事务,无论是数据库里的,还是生活里的。
共勉。
(全文完。写于2024年5月17日 凌晨2:13,娃刚翻了个身,我赶紧保存退出。)

评论 0