分布式事务这玩意儿,真的能搞明白吗?(附 Python 实战)

朱思宇
2025-12-16 23:46
阅读 328

上周五晚上 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. 消息队列 + 本地消息表 —— 老派但稳如老狗

这是我在上一家公司用过的方案,思路很朴素:

  1. 订单服务在本地 DB 插一条“待发送消息”记录(和订单写在一个事务里)
  2. 后台任务轮询这张表,把消息发到 Kafka/RabbitMQ
  3. Account/Inventory 服务消费消息,执行本地操作
  4. 如果失败,消息重试(配合指数退避 + 死信队列)

优点?简单、可靠、不依赖外部协调者。缺点?代码侵入性强,每个服务都要维护“消息表”+“补偿逻辑”。

不过对我们这种 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 开发者的几点忠告

  1. 别迷信“银弹”:没有哪种方案通吃所有场景。我们最终混合使用了“本地消息表”(核心交易) + “Saga”(复杂履约流程)。
  2. 幂等性是生命线:所有接收消息/补偿的接口,必须支持重复调用。建议用 DB 唯一索引或 Redis token 防重。
  3. 监控要到位:在 Prometheus 里埋点,监控“悬挂事务”、“补偿失败次数”。我们曾靠 Grafana 面板提前发现了一个死循环 Bug。
  4. K8s 不是万能的:虽然我们部署在腾讯云 TKE 上,Pod 自动扩缩容很爽,但事务逻辑还得自己兜底。别指望 Sidecar 自动帮你搞定一致性。

最后:搞定了,开心!

现在系统稳定跑了三个月,双11 大促期间零资损。虽然架构图看起来不如“基于区块链的去中心化事务引擎”那么酷炫,但老板只关心结果,不关心你用了多 fancy 的技术。

写这篇文章的时候,我正听着《Blinding Lights》敲键盘。窗外深圳湾的夜景依旧璀璨,而我终于可以准时下班了(大概)。

如果你也在被分布式事务折磨,别慌。记住:能跑通、能监控、能兜底,就是好方案。至于区块链?留着吹牛用吧。

P.S. 代码已脱敏上传 GitHub,欢迎 Star(求不喷):github.com/claudexu/distributed-tx-python-demo
P.P.S. 下次团建我请奶茶,感谢运维兄弟没在我改配置时重启 Pod。

评论 0

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