分布式事务这玩意儿,真的能搞明白吗?(附 Python 实战)
上周五晚上 10 点半,我戴着 AirPods 听着 Lo-fi Hip Hop,手里的 Vim 光标还在疯狂跳动。深圳的夏天闷得要死,空调外机轰隆作响,而我正被一个线上问题搞得快原地爆炸——用户下单成功了,但钱没扣;或者钱扣了,订单却没创建。
产品经理在钉钉群里@我:“这个双11前必须搞定啊,不然我们会被老板祭天的。”
运维小哥补刀:“兄弟,你这数据库跨了三个微服务,出事不奇怪。”
测试妹子默默甩来一个 Jira:“分布式事务一致性验证失败”。
行吧,看来是时候认真搞一搞分布式事务了。作为一个在腾讯系公司混迹多年、天天和 K8s 打交道的老油条,我本以为自己对“最终一致性”这种词已经免疫了。结果现实狠狠扇了我一耳光:理论很丰满,线上很骨感。
为啥这事这么难?
简单说,传统单体应用里,一个 BEGIN TRANSACTION + COMMIT/ROLLBACK 就能搞定的事,在微服务架构下直接裂成碎片。比如我们现在的订单系统:
- Order Service(Python + FastAPI)负责创建订单
- Account Service(Go 写的,别问,历史包袱)扣减余额
- Inventory Service(又是 Python,团队统一栈)扣库存
这三个服务各自连自己的 DB,网络可能抖、服务可能挂、DB 可能主从延迟……你让它们“要么全成功,要么全失败”,简直比让三个程序员同时同意用哪种代码格式还难。
更搞笑的是,有次 PM 居然问我:“能不能用区块链解决这个问题?”
我差点笑出声——区块链确实强调不可篡改和一致性,但它那套共识机制(PoW/PoS)延迟高、吞吐低,拿来处理每秒几千单的电商交易?怕不是想让服务器冒烟。
划重点:区块链 ≠ 分布式事务解决方案。它更适合需要审计、防篡改、多方协作但对性能要求不高的场景(比如供应链溯源)。咱们的高并发订单系统,还是得靠成熟的工程方案。
我试过的几种方案(踩坑实录)
1. 两阶段提交(2PC)——理论上完美,实践中劝退
2PC 是教科书级方案:协调者先问所有参与者“你们准备好没”(Prepare),大家都 OK 了再发“执行吧”(Commit)。听起来很稳?
但实际用起来,阻塞严重、性能差、单点故障风险高。我们试过用 Atomikos 集成,结果在压测时发现 TPS 直接腰斩。而且一旦协调者挂了,整个事务就卡在“悬而未决”状态,运维半夜打电话骂人。
结论:只适合内部低频、强一致场景,别碰高并发业务。
2. 消息队列 + 本地消息表 —— 老派但稳如老狗
这是我在上一家公司用过的方案,思路很朴素:
- 订单服务在本地 DB 插一条“待发送消息”记录(和订单写在一个事务里)
- 后台任务轮询这张表,把消息发到 Kafka/RabbitMQ
- Account/Inventory 服务消费消息,执行本地操作
- 如果失败,消息重试(配合指数退避 + 死信队列)
优点?简单、可靠、不依赖外部协调者。缺点?代码侵入性强,每个服务都要维护“消息表”+“补偿逻辑”。
不过对我们这种 deadline 逼近的项目来说,能跑就行。于是我撸起袖子,用 Python 干了起来。
# order_service/models.py
class Order(db.Model):
id = Column(Integer, primary_key=True)
user_id = Column(Integer)
status = Column(String)
class LocalMessage(db.Model):
id = Column(Integer, primary_key=True)
topic = Column(String) # e.g., "account.debit"
payload = Column(JSON) # {"user_id": 123, "amount": 99.9}
status = Column(String, default="pending") # pending / sent / failed
# 创建订单 + 发消息(同一个事务)
def create_order(user_id, amount):
with db.transaction():
order = Order.create(user_id=user_id, status="created")
LocalMessage.create(
topic="account.debit",
payload={"user_id": user_id, "amount": amount}
)
LocalMessage.create(
topic="inventory.deduct",
payload={"sku_id": "SKU123", "qty": 1}
)
return order.id
后台任务用 Celery 定时扫表发消息,消费端做好幂等(比如用 message_id 去重)。上线后跑了两周,零数据不一致!虽然架构看起来有点“土”,但真香。
3. Saga 模式 —— 补偿事务的艺术
后来团队技术升级,我们决定试试更优雅的 Saga 模式。核心思想:把大事务拆成一串本地事务,每个步骤配一个“撤销”操作(补偿)。
比如:
- Step 1: 创建订单 → 补偿:删除订单
- Step 2: 扣余额 → 补偿:加回余额
- Step 3: 扣库存 → 补偿:加回库存
如果第 3 步失败,就依次执行 Step 2 和 Step 1 的补偿。
我们用 Eventuate Tram(Java 生态)试过,但团队主力是 Python,于是转向开源方案 SAGA-PY(注:此为示意,真实项目可用 celery-saga 或自研)。
关键在于:补偿操作必须幂等且可重试!曾有一次因为补偿接口没做幂等,导致用户余额被反复加了 5 次……那次事故后,我给所有补偿函数都加上了 @idempotent 装饰器。
# saga/coordinator.py
class OrderSaga:
def __init__(self, order_id, user_id, amount):
self.steps = [
(self.create_order, self.rollback_create_order),
(self.debit_account, self.compensate_debit),
(self.deduct_inventory, self.compensate_inventory),
]
def execute(self):
for action, compensator in self.steps:
try:
action()
except Exception as e:
self._run_compensations(up_to=compensator)
raise e
@idempotent # 关键!防止重复补偿
def compensate_debit(self):
account_service.credit(user_id=self.user_id, amount=self.amount)
Saga 的好处是无锁、高吞吐,但缺点也很明显:业务逻辑复杂度飙升,而且“补偿”不一定能完全回滚(比如已发邮件通知用户订单成功)。
方案对比:别再盲目选型了!
| 方案 | 一致性级别 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 2PC | 强一致 | 低 | 中 | 内部低频系统(如财务对账) |
| 本地消息表 + MQ | 最终一致 | 高 | 低 | 高并发业务(电商、支付) |
| Saga | 最终一致 | 高 | 高 | 业务流程长、需灵活补偿的场景 |
| 区块链 | 强一致(但慢) | 极低 | 极高 | 审计、存证、多方信任场景 |
看到没?区块链根本不在我们的选项里。别被 buzzword 带偏了节奏。
给 Python 开发者的几点忠告
- 别迷信“银弹”:没有哪种方案通吃所有场景。我们最终混合使用了“本地消息表”(核心交易) + “Saga”(复杂履约流程)。
- 幂等性是生命线:所有接收消息/补偿的接口,必须支持重复调用。建议用 DB 唯一索引或 Redis token 防重。
- 监控要到位:在 Prometheus 里埋点,监控“悬挂事务”、“补偿失败次数”。我们曾靠 Grafana 面板提前发现了一个死循环 Bug。
- K8s 不是万能的:虽然我们部署在腾讯云 TKE 上,Pod 自动扩缩容很爽,但事务逻辑还得自己兜底。别指望 Sidecar 自动帮你搞定一致性。
最后:搞定了,开心!
现在系统稳定跑了三个月,双11 大促期间零资损。虽然架构图看起来不如“基于区块链的去中心化事务引擎”那么酷炫,但老板只关心结果,不关心你用了多 fancy 的技术。
写这篇文章的时候,我正听着《Blinding Lights》敲键盘。窗外深圳湾的夜景依旧璀璨,而我终于可以准时下班了(大概)。
如果你也在被分布式事务折磨,别慌。记住:能跑通、能监控、能兜底,就是好方案。至于区块链?留着吹牛用吧。
P.S. 代码已脱敏上传 GitHub,欢迎 Star(求不喷):
github.com/claudexu/distributed-tx-python-demo
P.P.S. 下次团建我请奶茶,感谢运维兄弟没在我改配置时重启 Pod。

评论 0