从本地事务到分布式事务

从本地事务到分布式事务

  • 从本地事务到分布式事务
    • [1. 从最简单的转账开始](#1. 从最简单的转账开始)
    • [2. 本地事务为什么能做到?](#2. 本地事务为什么能做到?)
    • [3. 微服务出现以后,事情变了](#3. 微服务出现以后,事情变了)
    • [4. 为什么不能直接 rollback?](#4. 为什么不能直接 rollback?)
    • [5. 更麻烦的是:网络不可靠](#5. 更麻烦的是:网络不可靠)
    • [6. 那我重试不就好了?](#6. 那我重试不就好了?)
    • [7. 分布式事务其实有两条路线](#7. 分布式事务其实有两条路线)
      • [路线 A:大家一起提交](#路线 A:大家一起提交)
      • [路线 B:允许暂时不一致,最后修正](#路线 B:允许暂时不一致,最后修正)
    • [8. 用一个项目贯穿后面的课程](#8. 用一个项目贯穿后面的课程)

下一篇:《ACID、CAP、BASE,以及强一致性和最终一致性

从本地事务到分布式事务

先记住一句话:

分布式事务,本质上是在多个独立事务资源之间,解决一个业务操作的一致性问题。

1. 从最简单的转账开始

假设银行转账:

text 复制代码
张三账户 -100
李四账户 +100

如果两个账户都在同一个 MySQL 数据库,可以这样做:

sql 复制代码
BEGIN;

UPDATE account
SET balance = balance - 100
WHERE user_id = 1;

UPDATE account
SET balance = balance + 100
WHERE user_id = 2;

COMMIT;

如果第二条 SQL 失败:

sql 复制代码
ROLLBACK;

于是两条 SQL:

text 复制代码
要么全部成功
要么全部失败

这就是本地事务


2. 本地事务为什么能做到?

因为数据库给我们提供了 ACID。

text 复制代码
A:Atomicity     原子性
C:Consistency   一致性
I:Isolation     隔离性
D:Durability    持久性

现阶段最需要理解的是 Atomicity(原子性)

例如:

text 复制代码
事务开始

① 创建订单
② 扣库存
③ 扣余额

事务提交

如果执行到第 ③ 步失败:

text 复制代码
① 创建订单   → 回滚
② 扣库存     → 回滚
③ 扣余额     → 失败

最终数据库恢复到事务开始之前。


3. 微服务出现以后,事情变了

现在做一个电商系统:

text 复制代码
                 用户下单
                    │
          ┌─────────┼─────────┐
          ↓         ↓         ↓
       订单服务   库存服务   支付服务
          │         │         │
        DB1       DB2       DB3

用户购买一台 5000 元的电脑。

业务流程:

text 复制代码
① 创建订单
        ↓
② 扣减库存
        ↓
③ 扣款 5000

假设运行结果:

text 复制代码
订单服务
创建订单成功 ✅

      ↓

库存服务
扣库存成功 ✅

      ↓

支付服务
扣款失败 ❌

现在发生了非常麻烦的事情:

text 复制代码
订单:已创建
库存:已经 -1
支付:没有成功

数据已经不一致了。


4. 为什么不能直接 rollback?

这是理解分布式事务最关键的一步。

订单服务的代码可能是:

java 复制代码
@Transactional
public void createOrder() {

    orderMapper.insert(order);

    stockClient.deduct();

    paymentClient.pay();
}

很多初学者容易产生一个误解:

我加了 @Transactional,paymentClient.pay() 抛异常,整个方法回滚不就行了吗?

不行。

因为:

text 复制代码
@Transactional
       │
       ↓
只能控制订单服务自己的数据库事务
       │
       ↓
      DB1

它控制不了:

text 复制代码
库存服务 → DB2
支付服务 → DB3

因此可能发生:

text 复制代码
orderMapper.insert()
        ↓
DB1 写入成功

stockClient.deduct()
        ↓
DB2 扣库存成功

paymentClient.pay()
        ↓
失败

        ↓

@Transactional
回滚 DB1

最后:

text 复制代码
DB1:订单回滚了       ✅

DB2:库存已经扣了     ❌

DB3:没有扣钱         ❌

本地事务只能保证自己的数据库,无法天然保证整个调用链。

这就是我们需要分布式事务的重要原因。


5. 更麻烦的是:网络不可靠

分布式事务真正困难的地方不只是"多个数据库"。

而是:

网络会让你不知道对方到底成功了还是失败了。

例如订单服务调用支付服务:

text 复制代码
订单服务
   │
   │ pay(5000)
   ↓
支付服务

订单服务等待了一段时间:

text 复制代码
TimeoutException

问题来了:

支付到底成功没有?

存在两种可能。

情况 A:

text 复制代码
请求根本没到支付服务

订单 ───X──→ 支付

支付失败。

但是还可能是情况 B:

text 复制代码
订单 ─────→ 支付

           扣款成功 ✅
               │
               │ response
               X
             网络断了

实际上支付服务已经:

text 复制代码
balance = balance - 5000

只是响应没有回来。

于是订单服务看到:

text 复制代码
TimeoutException

但真实情况是:

text 复制代码
支付已经成功

这叫:

结果不确定性。

这是理解分布式系统非常重要的一个思想:

超时 ≠ 失败。


6. 那我重试不就好了?

很好,这马上引出了第二个核心问题。

订单服务:

java 复制代码
try {
    paymentClient.pay(5000);
} catch (TimeoutException e) {
    paymentClient.pay(5000);
}

第一次其实已经成功:

text 复制代码
余额 -5000

只是响应丢失。

于是你重试:

text 复制代码
余额再次 -5000

用户买了一台电脑:

text 复制代码
扣款:10000 元

事故出现。

因此我们马上需要另外一个知识:

幂等

例如支付请求必须携带:

text 复制代码
requestId = PAY_20260914_00001

第一次:

text 复制代码
PAY_20260914_00001
        ↓
扣款 5000
        ↓
记录:已经处理

第二次又收到:

text 复制代码
PAY_20260914_00001
        ↓
发现已经处理
        ↓
不再扣款
        ↓
返回之前结果

于是:

text 复制代码
请求 1 次
请求 10 次
请求 100 次

业务结果仍然一样

这就是幂等性

所以你现在应该开始发现:

text 复制代码
分布式事务
并不仅仅是
"怎么 rollback"

它实际上涉及一整套问题:

text 复制代码
网络超时
   ↓
结果不确定
   ↓
需要重试
   ↓
重试产生重复执行
   ↓
需要幂等
   ↓
部分服务已经成功
   ↓
可能需要补偿
   ↓
补偿也可能失败
   ↓
继续重试 / 对账 / 人工兜底

这条链非常重要。


7. 分布式事务其实有两条路线

接下来整个分布式章节专栏都围绕这两种思想展开。

路线 A:大家一起提交

核心思想:

要成功大家一起成功,要失败大家一起失败。

典型方案:

text 复制代码
XA
2PC
Seata XA
Seata AT

可以粗略理解为追求更强的一致性。


路线 B:允许暂时不一致,最后修正

例如:

text 复制代码
订单创建成功
库存扣减成功
积分增加失败

没有必要把订单取消。

可以:

text 复制代码
积分失败
   ↓
重试
   ↓
还是失败
   ↓
继续重试
   ↓
最终成功

订单创建后 500ms:

text 复制代码
积分:0

过了 2 秒:

text 复制代码
积分:+500

中间短暂不一致,但最终一致。

这就是:

最终一致性。

典型技术:

text 复制代码
MQ
事务消息
本地消息表
Transactional Outbox
Saga
补偿机制

8. 用一个项目贯穿后面的课程

后面我们可以一直使用一个电商项目:

text 复制代码
mall
│
├── order-service
│      └── MySQL
│
├── stock-service
│      └── MySQL
│
├── payment-service
│      └── MySQL
│
├── coupon-service
│      └── MySQL
│
└── points-service
       └── MySQL

用户下单:

text 复制代码
                 createOrder()
                       │
                       ↓
                  创建订单
                       │
                       ↓
                  扣减库存
                       │
                       ↓
                  使用优惠券
                       │
                       ↓
                     支付
                       │
                       ↓
                  增加积分

后面我们分别思考:

text 复制代码
支付失败怎么办?

库存扣了,订单失败怎么办?

MQ 消息丢了怎么办?

消息重复消费怎么办?

服务突然宕机怎么办?

Cancel 比 Try 先到怎么办?

数据库成功、MQ 失败怎么办?

然后你就会发现:

2PC、TCC、Saga、Outbox、MQ、幂等这些技术,都是为了解决这些具体问题,而不是为了背概念。


你需要真正记住 5 件事

  1. 本地事务只能天然管理自己的事务资源。
  2. 跨服务、跨数据库以后,会产生部分成功、部分失败
  3. 分布式环境存在结果不确定性 ,尤其要记住:超时不等于失败
  4. 网络异常必然带来重试 问题,重试又要求我们考虑幂等
  5. 分布式事务不只有"全体回滚"这一条路,还可以通过重试和补偿实现最终一致性

下一篇:《ACID、CAP、BASE,以及强一致性和最终一致性


若有转载,请标明出处:https://blog.csdn.net/CharlesYuangc/article/details/165324033

相关推荐
黑马程序员毕设3 小时前
基于微信小程序的节目活动报名与管理系统设计与实现
java·开发语言·spring boot·后端·电脑
小坏讲微服务3 小时前
Spring AI 高频面试题20道
java·spring·ai·agent·springai
Bs_MoneyMagnet3 小时前
基于springboot+vue的会议室预约管理系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·spring·毕业设计·计算机毕业设计
sunshine22 girl3 小时前
Java学习 java原理
java·开发语言·学习
Bs_MoneyMagnet4 小时前
基于springboot+vue的茶铺管理系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·spring·毕业设计·计算机毕业设计
她说..4 小时前
RabbitMQ 使用场景详解
java·spring boot·后端·spring·rabbitmq·java-rabbitmq
宁渡AI大模型4 小时前
河南宁渡科技有限公司|宁渡课堂 AI 全栈面试分享,AI 应用开发高频考点汇总
java·c++·人工智能·python·深度学习·神经网络·rag
代码什么用4 小时前
JSON一文速通
java·json
Lyyaoo.4 小时前
【二分查找】【中等】搜索二维矩阵/排序数组的第一位和最后位置/搜索旋转排序数组/旋转排序数组中的最小值
java·数据结构·算法