从单机到分布式:一致性实战(五)服务拆分:跨服务事务一致性

从这里开始 分库分表解决了数据量问题后,小明的二手书平台继续快速发展。团队也从最初的 3 人扩展到了 20 人。 单体应用的问题开始显现: 代码库越来越大,改一个小功能都要担心影响其他模块 每次发布都是全量部署,风险高 不同模块的技术栈被绑定在一起,无法独立演进 技术负责人建议:“是时候拆分服务了。” 团队经过讨论,决定把系统拆分成多个服务: 用户服务:管理用户账户和余额 库存服务:管理书籍库存 订单服务:处理订单逻辑 拆分后,架构清晰了很多。但第一个需求就让小明犯了难: 用户下单时,需要:1)从用户账户扣款,2)扣减库存,3)创建订单 在单体应用时代,这三个操作可以放在同一个数据库事务里: // 单体时代:一个事务搞定 async fn place_order(pool: &PgPool, order: OrderRequest) -> Result<Order, Error> { let mut tx = pool.begin().await?; // 1. 扣款 sqlx::query!("UPDATE users SET balance = balance - $1 WHERE id = $2", order.amount, order.user_id) .execute(&mut *tx).await?; // 2. 扣库存 sqlx::query!("UPDATE books SET stock = stock - 1 WHERE id = $1", order.book_id) .execute(&mut *tx).await?; // 3. 创建订单 let order = sqlx::query_as!(Order, "INSERT INTO orders (user_id, book_id, amount) VALUES ($1, $2, $3) RETURNING *", order.user_id, order.book_id, order.amount ).fetch_one(&mut *tx).await?; tx.commit().await?; Ok(order) } 但现在,用户余额在用户服务的数据库,库存在库存服务的数据库,订单在订单服务的数据库。三个操作跨越三个独立的数据库,无法在同一个事务中完成。 如果扣款成功了,但扣库存失败了,怎么办?钱扣了,货没发,用户肯定要投诉。 这就是跨服务事务一致性问题。 问题的本质 分布式事务难在哪里?让我们看一个简化的场景: 订单服务 库存服务 │ │ │──── 1. 扣库存请求 ──────>│ │ │ (扣库存成功) │<─── 2. 扣库存响应 ───────│ │ │ │ (创建订单...) │ │ (本地事务失败!) │ │ │ │ 现在怎么办? │ │ 库存已经扣了,但订单没创建│ 问题的根源是: 没有全局事务协调者:每个服务只能控制自己的本地事务 网络不可靠:调用可能超时、失败、或成功但响应丢失 部分失败:一个操作成功,另一个失败 传统解决方案是两阶段提交(2PC),但它有致命缺陷: ...

December 11, 2025 · 12 min · 2405 words · Nanlong