后端面试题 - 分布式系统与微服务篇

3 阅读22分钟

一、分布式理论基础

1.1 CAP理论

Q1: 什么是CAP理论?为什么分布式系统无法同时满足CAP?

答案:

CAP定理 (Eric Brewer, 2000年)

三个特性:

C - Consistency (一致性)

定义: 所有节点在同一时间看到的数据是一致的

示例:
用户在节点A写入数据: name="Alice"
在节点B立即读取,必须也能读到: name="Alice"

强一致性场景:
- 银行转账: A账户扣款,B账户必须立即到账
- 库存扣减: 扣减后立即查询,必须是最新值
- 分布式锁: 获取锁后,其他节点必须立即看到锁已被占用

A - Availability (可用性)

定义: 系统在任何时候都能响应请求,不会出现"服务不可用"

示例:
无论访问哪个节点,都能得到响应(可能不是最新数据)

高可用场景:
- 电商网站: 即使部分服务器宕机,用户仍能浏览商品
- 社交网络: 即使数据中心故障,用户仍能查看朋友圈
- CDN: 即使源站不可用,仍能从缓存返回内容

P - Partition Tolerance (分区容错性)

定义: 网络分区(节点间通信中断)时,系统仍能继续工作

示例:
节点A和节点B之间网络中断,但系统仍能对外提供服务

网络分区场景:
- 跨机房部署: 机房间光纤被挖断
- 跨地域部署: 海底光缆故障
- 交换机故障: 部分节点无法通信

为什么无法同时满足CAP?

场景: 分布式数据库,3个节点

初始状态:
节点A: balance=100
节点B: balance=100
节点C: balance=100

操作: 用户在节点A更新余额为200

CAP不可能三角:

情况1: 选择CA (一致性 + 可用性),放弃P

实现方式: 单机数据库或紧耦合集群

流程:
1. 用户在节点A写入: balance=200
2. 节点A同步更新节点B和节点C
3. 三个节点都更新成功后,返回"写入成功"

问题:
网络分区发生时:
- 节点A和节点B/C之间网络中断
- 节点A无法同步数据到BC
- 有两个选择:
  a) 拒绝写入 → 牺牲可用性(A)
  b) 继续写入 → 牺牲一致性(C)

结论: 网络分区时,CA不可能同时满足

典型系统:
- 传统单机数据库 (MySQL, PostgreSQL)
- 两阶段提交 (2PC)

情况2: 选择CP (一致性 + 分区容错),牺牲A

实现方式: 强一致性分布式系统

流程:
1. 用户在节点A写入: balance=200
2. 节点A尝试同步到节点B和C
3. 网络分区发生,无法同步到节点C
4. 节点A拒绝写入,返回"服务不可用"

特点:
- 保证一致性: 要么所有节点都是200,要么都是100
- 牺牲可用性: 网络分区时,拒绝服务

典型系统:
- ZooKeeper: 集群中少于半数节点存活时,拒绝服务
- etcd: 基于Raft协议,需要多数节点同意才能写入
- HBase: 强一致性,但Region Server不可用时无法访问
- MongoDB (强一致性模式): 主节点不可用时,无法写入

实际案例:
场景: ZooKeeper集群 (3节点)
- 正常情况: 3个节点都可用,写入需要2个节点同意
- 网络分区: 节点A与节点B/C断开
  - 节点A单独一个分区,无法获得多数同意,拒绝服务 (牺牲A)
  - 节点B/C分区,可以获得多数同意,继续服务
  - 保证了一致性 (C)

情况3: 选择AP (可用性 + 分区容错),牺牲C

实现方式: 最终一致性分布式系统

流程:
1. 用户在节点A写入: balance=200
2. 节点A立即返回"写入成功"
3. 后台异步同步到节点B和C
4. 网络分区时,节点A和节点B/C数据不一致

特点:
- 保证可用性: 任何时候都能写入和读取
- 牺牲一致性: 不同节点可能看到不同数据
- 最终一致性: 网络恢复后,数据会同步一致

典型系统:
- Cassandra: 可用性优先,多数据中心部署
- DynamoDB: 亚马逊的NoSQL数据库,高可用
- Eureka: Netflix的服务注册中心,AP模型
- Redis Cluster: 异步复制,优先可用性

实际案例:
场景: 电商购物车 (Cassandra)
- 用户在北京节点添加商品到购物车
- 北京节点立即返回成功 (保证可用性A)
- 异步同步到上海节点
- 网络分区时,上海节点暂时看不到最新商品 (牺牲一致性C)
- 网络恢复后,数据同步完成 (最终一致性)

用户体验:
- 用户添加商品后立即看到成功提示 (好体验)
- 切换到上海机房,可能暂时看不到刚添加的商品 (可接受)
- 几秒后刷新页面,商品出现 (最终一致)

CAP的实际应用:

1. 金融系统 → CP
   - 转账必须强一致
   - 宁可拒绝服务,也不能数据错误
   - 案例: 银行核心系统

2. 电商系统 → AP
   - 用户体验优先
   - 可以接受短暂的数据不一致
   - 案例: 购物车、浏览历史

3. 注册中心 → AP (Eureka) 或 CP (ZooKeeper)
   - Eureka: 保证可用性,服务注册信息可能延迟
   - ZooKeeper: 保证一致性,网络分区时拒绝服务

4. 配置中心 → CP
   - 配置必须一致
   - 宁可服务不可用,也不能配置错误
   - 案例: Apollo, Nacos(CP模式)

常见误区:

误区1: P是可选的

错误理解: 可以不考虑网络分区

实际情况:
- 分布式系统必然存在网络分区
- 机房断电、光纤被挖断、路由器故障、网卡故障...
- P不是可选项,是必须考虑的!

正确理解:
分布式系统只能在CP和AP之间选择,没有CA选项

误区2: CAP是非黑即白

错误理解: 必须100%选择C或100%选择A

实际情况:
- 可以在不同场景选择不同策略
- 可以提供多种一致性级别供用户选择

示例: Cassandra的可调一致性
- 写入时可选择:
  - ONE: 写入1个节点即返回 (高可用,弱一致)
  - QUORUM: 写入多数节点后返回 (平衡)
  - ALL: 写入所有节点后返回 (强一致,低可用)

- 读取时可选择:
  - ONE: 从1个节点读取 (快,可能读到旧数据)
  - QUORUM: 从多数节点读取并比较 (平衡)
  - ALL: 从所有节点读取 (慢,强一致)

策略:
- 核心数据 (余额): 写QUORUM,读QUORUM → 强一致
- 非核心数据 (浏览历史): 写ONE,读ONE → 高性能

误区3: 一致性只有强一致和最终一致

实际上有多种一致性级别:

1. 强一致性 (Strong Consistency)
   - 写入后立即可见
   - 所有节点看到相同数据

2. 弱一致性 (Weak Consistency)
   - 写入后不保证立即可见
   - 可能需要等待一段时间

3. 最终一致性 (Eventual Consistency)
   - 不保证立即一致
   - 保证最终会一致
   - 常见时间: 秒级到分钟级

4. 因果一致性 (Causal Consistency)
   - 保证因果关系的操作顺序
   - A操作影响B操作,则B一定能看到A的结果

5. 会话一致性 (Session Consistency)
   - 同一会话内保证一致
   - 用户自己的写入能立即读到
   - 其他用户的写入可能延迟

示例: MongoDB
- 默认: 最终一致性
- 可选: 读己之写 (Read Your Writes) - 会话一致性
- 可选: 强一致性 (读写主节点)

CAP定理的现代理解 - PACELC:

PACELC扩展:
- if Partition (P): choose between A and C
- Else (E): choose between Latency (L) and Consistency (C)

含义:
- 网络分区时: 选择可用性(A)还是一致性(C)
- 正常情况下: 选择低延迟(L)还是强一致性(C)

示例:
- DynamoDB: PA/EL - 分区时选A,正常时选L (低延迟)
- HBase: PC/EC - 分区时选C,正常时选C (强一致)
- Cassandra: PA/EL - 可用性和低延迟优先
- ZooKeeper: PC/EC - 一致性优先

总结:

特性CP系统AP系统
一致性强一致最终一致
可用性网络分区时可能不可用任何时候都可用
应用场景金融、配置中心电商、社交网络
典型产品ZooKeeper, etcd, HBaseCassandra, DynamoDB, Eureka
用户体验可能拒绝服务可能读到旧数据

选择建议:

  1. 金融、支付、库存 → CP (强一致性)
  2. 购物车、浏览历史、推荐 → AP (高可用)
  3. 服务注册发现 → AP (Eureka) 或 CP (ZooKeeper/Consul)
  4. 配置中心 → CP (配置错误影响巨大)
  5. 缓存 → AP (允许数据延迟)

1.2 BASE理论

Q2: 什么是BASE理论?它与ACID有什么关系?

答案:

BASE理论 (Basically Available, Soft state, Eventually consistent)

BASE是对CAP中AP方案的延伸,是对ACID的放宽。

三个特性:

BA - Basically Available (基本可用)

定义: 系统在出现故障时,允许损失部分可用性,但核心功能仍可用

允许的损失:
1. 响应时间损失
   - 正常: 0.5秒响应
   - 故障: 2秒响应
   - 用户体验下降,但仍可用

2. 功能损失
   - 正常: 显示推荐商品
   - 故障: 不显示推荐,只显示基本信息
   - 核心功能(浏览、下单)仍可用

实际案例:
电商大促:
- 正常: 显示商品详情 + 评论 + 推荐 + Q&A
- 高峰: 只显示商品详情,其他功能降级
- 保证核心购买流程可用

S - Soft State (软状态)

定义: 系统中的数据存在中间状态,允许不同节点的数据存在延迟

对比:
ACID (硬状态):
- 事务提交后,数据立即一致
- 要么成功,要么失败,没有中间状态

BASE (软状态):
- 允许存在中间状态
- 数据在不同节点可能不一致
- 最终会达到一致状态

实际案例:
订单状态:
时刻T1:
- 订单服务: status=PAID
- 库存服务: stock=10 (未扣减)
- 物流服务: 未知该订单

时刻T2 (5秒后):
- 订单服务: status=PAID
- 库存服务: stock=9 (已扣减)
- 物流服务: 未知该订单

时刻T3 (10秒后):
- 订单服务: status=PAID
- 库存服务: stock=9
- 物流服务: status=PROCESSING

中间状态允许存在,最终会一致

E - Eventually Consistent (最终一致性)

定义: 系统不保证实时一致性,但保证在一定时间后达到一致

时间窗口:
- 毫秒级: 缓存更新 (1-100ms)
- 秒级: 数据同步 (1-10s)
- 分钟级: 离线数据处理 (1-5min)

实际案例:
微博关注:
1. 用户A关注用户B
2. 用户A立即看到"已关注"
3. 用户B的粉丝数 +1 (异步,延迟1秒)
4. 推荐系统更新 (异步,延迟10秒)
5. 数据仓库同步 (异步,延迟1小时)

最终所有系统都会看到"A关注了B"这个状态

BASE与ACID的对比:

特性ACIDBASE
一致性强一致最终一致
隔离性完全隔离允许中间状态
可用性可能阻塞高可用
性能较低较高
适用场景传统数据库分布式系统

ACID的局限性:

场景: 跨机房的分布式事务

ACID方案 (两阶段提交):
1. 协调者发送"准备提交"到所有参与者
2. 等待所有参与者响应
3. 如果都同意,发送"提交"
4. 如果有拒绝,发送"回滚"

问题:
- 阻塞: 参与者在等待期间,锁定资源,阻塞其他事务
- 单点: 协调者故障,所有参与者阻塞
- 网络延迟: 跨机房网络延迟50ms,往返100ms,性能差
- 可用性: 任何一个参与者故障,整个事务失败

实际案例:
订单系统(北京) + 库存系统(上海) + 支付系统(广州)
- 网络往返: 100ms
- 锁定时间: 500ms (事务处理)
- 总耗时: 600ms+
- QPS: 1000 / 600 < 2

无法支撑高并发!

BASE的实现:

方案1: 消息队列保证最终一致性

流程:
1. 订单服务: 创建订单,发送消息到MQ
2. 库存服务: 消费消息,扣减库存
3. 物流服务: 消费消息,创建配送单

特点:
- 异步: 订单服务不等待库存和物流
- 解耦: 订单服务不关心下游实现
- 最终一致: 消息保证最终会被消费

代码示例:
# 订单服务
def create_order(order_data):
    # 1. 创建订单
    order_id = db.execute("INSERT INTO orders ...", order_data)

    # 2. 发送消息
    mq.send("order.created", {
        "order_id": order_id,
        "product_id": order_data["product_id"],
        "quantity": order_data["quantity"]
    })

    # 3. 立即返回 (不等待库存扣减)
    return {"order_id": order_id, "status": "PENDING"}

# 库存服务
@mq.subscribe("order.created")
def reduce_stock(message):
    product_id = message["product_id"]
    quantity = message["quantity"]

    # 扣减库存
    db.execute("UPDATE products SET stock = stock - ? WHERE id = ?",
               quantity, product_id)

# 物流服务
@mq.subscribe("order.created")
def create_delivery(message):
    order_id = message["order_id"]

    # 创建配送单
    db.execute("INSERT INTO deliveries ...", order_id)

时间线:
T1: 创建订单 (10ms)
T2: 返回用户 (10ms)
T3: 扣减库存 (50ms后)
T4: 创建配送单 (100ms后)

用户体验: 10ms就看到"下单成功"
后台处理: 异步完成,用户无感知

方案2: Saga模式处理分布式事务

定义: 将长事务拆分为多个本地事务,每个本地事务都有对应的补偿操作

示例: 订单流程
正常流程:
1. 创建订单 (订单服务)
2. 扣减库存 (库存服务)
3. 扣减余额 (账户服务)
4. 创建配送单 (物流服务)

补偿流程 (如果某步失败):
1. 创建订单成功
2. 扣减库存成功
3. 扣减余额失败 → 触发补偿
4. 补偿: 恢复库存
5. 补偿: 取消订单

代码示例:
class OrderSaga:
    def __init__(self, order_data):
        self.order_data = order_data
        self.order_id = None
        self.stock_reduced = False
        self.balance_reduced = False

    def execute(self):
        try:
            # 步骤1: 创建订单
            self.order_id = self.create_order()

            # 步骤2: 扣减库存
            self.reduce_stock()
            self.stock_reduced = True

            # 步骤3: 扣减余额
            self.reduce_balance()
            self.balance_reduced = True

            # 步骤4: 创建配送单
            self.create_delivery()

            return {"success": True, "order_id": self.order_id}

        except Exception as e:
            # 发生异常,执行补偿
            self.compensate()
            return {"success": False, "error": str(e)}

    def compensate(self):
        # 补偿: 按相反顺序撤销操作
        if self.balance_reduced:
            self.restore_balance()

        if self.stock_reduced:
            self.restore_stock()

        if self.order_id:
            self.cancel_order()

    def create_order(self):
        return order_service.create(self.order_data)

    def reduce_stock(self):
        stock_service.reduce(self.order_data["product_id"],
                            self.order_data["quantity"])

    def reduce_balance(self):
        account_service.reduce(self.order_data["user_id"],
                              self.order_data["amount"])

    def create_delivery(self):
        logistics_service.create(self.order_id)

    def restore_stock(self):
        stock_service.restore(self.order_data["product_id"],
                             self.order_data["quantity"])

    def restore_balance(self):
        account_service.restore(self.order_data["user_id"],
                               self.order_data["amount"])

    def cancel_order(self):
        order_service.cancel(self.order_id)

特点:
- 没有全局锁,每个服务独立提交本地事务
- 发生错误时,通过补偿恢复一致性
- 最终一致性: 可能出现中间状态,但最终会一致或回滚

方案3: TCC (Try-Confirm-Cancel)

定义: 两阶段提交的变种,但不锁定资源

三个阶段:
1. Try: 尝试执行,预留资源
2. Confirm: 确认执行,使用预留的资源
3. Cancel: 取消执行,释放预留的资源

示例: 扣减库存
Try阶段:
- 不直接扣减库存
- 预留库存: available_stock -= 1, frozen_stock += 1

Confirm阶段:
- 确认扣减: frozen_stock -= 1

Cancel阶段:
- 释放预留: available_stock += 1, frozen_stock -= 1

代码示例:
class StockService:
    def try_reduce(self, product_id, quantity):
        """Try阶段: 预留库存"""
        result = db.execute("""
            UPDATE products
            SET available_stock = available_stock - ?,
                frozen_stock = frozen_stock + ?
            WHERE id = ? AND available_stock >= ?
        """, quantity, quantity, product_id, quantity)

        if result.affected_rows == 0:
            raise InsufficientStockException()

        return {"transaction_id": generate_id()}

    def confirm_reduce(self, product_id, quantity, transaction_id):
        """Confirm阶段: 确认扣减"""
        db.execute("""
            UPDATE products
            SET frozen_stock = frozen_stock - ?
            WHERE id = ?
        """, quantity, product_id)

    def cancel_reduce(self, product_id, quantity, transaction_id):
        """Cancel阶段: 释放预留"""
        db.execute("""
            UPDATE products
            SET available_stock = available_stock + ?,
                frozen_stock = frozen_stock - ?
            WHERE id = ?
        """, quantity, quantity, product_id)

# 订单流程
def create_order_with_tcc(order_data):
    transaction_id = generate_id()
    transactions = []

    try:
        # Try阶段: 预留所有资源
        stock_tx = stock_service.try_reduce(
            order_data["product_id"],
            order_data["quantity"]
        )
        transactions.append(("stock", stock_tx))

        balance_tx = account_service.try_reduce(
            order_data["user_id"],
            order_data["amount"]
        )
        transactions.append(("account", balance_tx))

        # Confirm阶段: 确认所有操作
        for service, tx in transactions:
            if service == "stock":
                stock_service.confirm_reduce(**tx)
            elif service == "account":
                account_service.confirm_reduce(**tx)

        return {"success": True}

    except Exception as e:
        # Cancel阶段: 取消所有操作
        for service, tx in transactions:
            if service == "stock":
                stock_service.cancel_reduce(**tx)
            elif service == "account":
                account_service.cancel_reduce(**tx)

        return {"success": False, "error": str(e)}

优点:
- 不锁定资源,性能较好
- 每个服务独立提交,不阻塞其他事务

缺点:
- 实现复杂,每个服务需要实现Try/Confirm/Cancel三个接口
- 需要考虑幂等性、悬挂、空回滚等问题

BASE在实际系统中的应用:

1. 电商订单系统
   - 创建订单: 立即返回成功 (Basically Available)
   - 扣库存、发货: 异步处理 (Soft State)
   - 最终: 所有系统状态一致 (Eventually Consistent)

2. 社交网络
   - 发布动态: 立即成功
   - 同步到粉丝: 异步推送 (几秒延迟)
   - 最终: 所有粉丝都能看到

3. 分布式缓存
   - 更新数据库: 立即成功
   - 删除缓存: 异步删除
   - 最终: 缓存数据一致

4. 数据同步
   - 主库写入: 立即成功
   - 从库同步: 异步复制 (毫秒级延迟)
   - 最终: 主从数据一致

总结:

场景理论特点应用
单机数据库ACID强一致性,低可用金融核心系统
分布式系统BASE最终一致性,高可用互联网应用
配置中心ACID强一致性Apollo, Nacos
服务注册BASE高可用Eureka

选择建议:

  • 强一致性要求 (金融、支付) → ACID / CP
  • 高可用要求 (电商、社交) → BASE / AP
  • 混合场景 → 核心数据ACID,非核心数据BASE

二、分布式事务

2.1 分布式事务解决方案

Q3: 分布式事务有哪些解决方案?各有什么优缺点?

答案:

场景: 电商下单,涉及3个服务

订单服务: 创建订单
库存服务: 扣减库存
账户服务: 扣减余额

要求: 要么全部成功,要么全部失败 (原子性)

方案1: 两阶段提交 (2PC - Two-Phase Commit)

原理:

角色:
- 协调者 (Coordinator): 事务管理器
- 参与者 (Participant): 订单服务、库存服务、账户服务

阶段1: 准备阶段 (Prepare Phase)
1. 协调者向所有参与者发送"准备提交"请求
2. 参与者执行事务操作,但不提交
3. 参与者锁定资源,记录Undo/Redo日志
4. 参与者返回"同意""拒绝"

阶段2: 提交阶段 (Commit Phase)
情况A: 所有参与者都同意
1. 协调者向所有参与者发送"提交"请求
2. 参与者提交事务,释放锁
3. 参与者返回"完成"
4. 协调者收到所有"完成",事务成功

情况B: 任何参与者拒绝
1. 协调者向所有参与者发送"回滚"请求
2. 参与者回滚事务,释放锁
3. 参与者返回"完成"
4. 协调者收到所有"完成",事务失败

实现示例:

class TwoPhaseCommitCoordinator:
    def __init__(self, participants):
        self.participants = participants  # [订单服务, 库存服务, 账户服务]

    def execute_transaction(self, transaction_data):
        transaction_id = generate_id()

        # 阶段1: 准备
        prepare_results = []
        for participant in self.participants:
            result = participant.prepare(transaction_id, transaction_data)
            prepare_results.append(result)

        # 判断是否所有参与者都同意
        if all(result == "YES" for result in prepare_results):
            # 阶段2: 提交
            for participant in self.participants:
                participant.commit(transaction_id)
            return {"success": True}
        else:
            # 阶段2: 回滚
            for participant in self.participants:
                participant.rollback(transaction_id)
            return {"success": False}

# 参与者实现: 订单服务
class OrderService:
    def prepare(self, transaction_id, data):
        """准备阶段: 执行操作但不提交"""
        try:
            # 开启事务
            conn = db.begin_transaction()

            # 执行业务逻辑
            conn.execute("INSERT INTO orders ...", data)

            # 锁定资源,等待协调者指令
            self.pending_transactions[transaction_id] = conn

            return "YES"
        except Exception as e:
            return "NO"

    def commit(self, transaction_id):
        """提交阶段: 提交事务"""
        conn = self.pending_transactions.get(transaction_id)
        if conn:
            conn.commit()
            del self.pending_transactions[transaction_id]

    def rollback(self, transaction_id):
        """回滚阶段: 回滚事务"""
        conn = self.pending_transactions.get(transaction_id)
        if conn:
            conn.rollback()
            del self.pending_transactions[transaction_id]

优点:

1. 强一致性: 保证所有参与者要么全部成功,要么全部失败
2. 实现简单: 逻辑清晰,易于理解
3. 数据库支持: MySQL, PostgreSQL等都支持XA协议

缺点:

1. 同步阻塞: 参与者在准备阶段锁定资源,等待协调者指令
   - 锁定期间,其他事务无法访问
   - 并发性能极差

2. 单点故障: 协调者故障,所有参与者阻塞
   - 参与者不知道提交还是回滚
   - 资源一直锁定,导致系统不可用

3. 数据不一致: 网络分区时,部分参与者提交,部分回滚
   - 协调者发送"提交"指令
   - 部分参与者收到,提交事务
   - 部分参与者未收到,超时回滚
   - 数据不一致!

4. 性能差: 两次网络往返,延迟高
   - 阶段1: 协调者 → 参与者 → 协调者
   - 阶段2: 协调者 → 参与者
   - 跨机房: 100ms × 2 = 200ms

适用场景:

- 单体应用拆分为微服务,短期过渡方案
- 对一致性要求极高,性能要求不高的场景
- 不推荐用于高并发系统!

方案2: 三阶段提交 (3PC - Three-Phase Commit)

改进点:

在2PC基础上增加了超时机制和CanCommit阶段

阶段1: CanCommit (询问)
- 协调者询问参与者: "能否执行事务?"
- 参与者不执行事务,只检查资源是否可用
- 返回"YES"或"NO"

阶段2: PreCommit (准备)
- 如果阶段1所有参与者都返回YES
- 协调者发送PreCommit,参与者执行事务但不提交
- 如果阶段1有参与者返回NO,直接中止

阶段3: DoCommit (提交)
- 协调者发送DoCommit,参与者提交事务
- 如果超时,参与者自动提交 (假设多数参与者已准备好)

优点:

1. 降低阻塞范围: CanCommit阶段不锁定资源
2. 超时机制: 参与者等待超时后自动提交,避免永久阻塞

缺点:

1. 仍然有阻塞: PreCommit阶段仍需锁定资源
2. 数据不一致: 网络分区时,超时自动提交可能导致不一致
3. 实现复杂: 需要实现超时机制,处理各种异常
4. 使用较少: 2PC和3PC在互联网公司已很少使用

方案3: Saga模式 (推荐)

原理:

将分布式事务拆分为多个本地事务,每个本地事务都有对应的补偿操作

正向流程: T1 → T2 → T3 → T4
补偿流程: C4 → C3 → C2 → C1

如果T3失败,执行C2和C1补偿

两种实现方式:

方式1: 事件驱动 (Choreography)

# 订单服务: 创建订单
def create_order(order_data):
    # 1. 创建订单
    order_id = db.execute("INSERT INTO orders ...", order_data)

    # 2. 发布"订单已创建"事件
    event_bus.publish("OrderCreated", {
        "order_id": order_id,
        "product_id": order_data["product_id"],
        "quantity": order_data["quantity"],
        "amount": order_data["amount"]
    })

    return order_id

# 库存服务: 监听"订单已创建"事件
@event_bus.subscribe("OrderCreated")
def on_order_created(event):
    try:
        # 扣减库存
        db.execute("UPDATE products SET stock = stock - ? WHERE id = ?",
                   event["quantity"], event["product_id"])

        # 发布"库存已扣减"事件
        event_bus.publish("StockReduced", event)

    except InsufficientStockException:
        # 库存不足,发布失败事件
        event_bus.publish("StockReduceFailed", event)

# 订单服务: 监听"库存扣减失败"事件
@event_bus.subscribe("StockReduceFailed")
def on_stock_reduce_failed(event):
    # 补偿: 取消订单
    db.execute("UPDATE orders SET status = 'CANCELLED' WHERE id = ?",
               event["order_id"])

# 账户服务: 监听"库存已扣减"事件
@event_bus.subscribe("StockReduced")
def on_stock_reduced(event):
    try:
        # 扣减余额
        db.execute("UPDATE accounts SET balance = balance - ? WHERE user_id = ?",
                   event["amount"], event["user_id"])

        # 发布"余额已扣减"事件
        event_bus.publish("BalanceReduced", event)

    except InsufficientBalanceException:
        # 余额不足,发布失败事件
        event_bus.publish("BalanceReduceFailed", event)

# 库存服务: 监听"余额扣减失败"事件
@event_bus.subscribe("BalanceReduceFailed")
def on_balance_reduce_failed(event):
    # 补偿: 恢复库存
    db.execute("UPDATE products SET stock = stock + ? WHERE id = ?",
               event["quantity"], event["product_id"])

# 订单服务: 监听"余额扣减失败"事件
@event_bus.subscribe("BalanceReduceFailed")
def on_balance_reduce_failed(event):
    # 补偿: 取消订单
    db.execute("UPDATE orders SET status = 'CANCELLED' WHERE id = ?",
               event["order_id"])

优点:
- 解耦: 服务之间不直接调用,通过事件通信
- 异步: 性能高,不阻塞

缺点:
- 复杂: 需要处理各种事件,逻辑分散
- 难以理解: 流程不直观,调试困难

方式2: 编排模式 (Orchestration,推荐)

class OrderSagaOrchestrator:
    """Saga协调器"""

    def execute(self, order_data):
        saga_id = generate_id()
        state = SagaState()

        try:
            # 步骤1: 创建订单
            order_id = self.create_order(order_data)
            state.record("create_order", order_id)

            # 步骤2: 扣减库存
            self.reduce_stock(order_data["product_id"], order_data["quantity"])
            state.record("reduce_stock", True)

            # 步骤3: 扣减余额
            self.reduce_balance(order_data["user_id"], order_data["amount"])
            state.record("reduce_balance", True)

            # 步骤4: 创建配送单
            delivery_id = self.create_delivery(order_id)
            state.record("create_delivery", delivery_id)

            # 所有步骤成功
            return {"success": True, "order_id": order_id}

        except Exception as e:
            # 发生异常,执行补偿
            self.compensate(state)
            return {"success": False, "error": str(e)}

    def compensate(self, state):
        """按相反顺序补偿"""
        if state.has("create_delivery"):
            self.cancel_delivery(state.get("create_delivery"))

        if state.has("reduce_balance"):
            self.restore_balance(...)

        if state.has("reduce_stock"):
            self.restore_stock(...)

        if state.has("create_order"):
            self.cancel_order(state.get("create_order"))

    def create_order(self, order_data):
        return order_service.create(order_data)

    def reduce_stock(self, product_id, quantity):
        stock_service.reduce(product_id, quantity)

    def reduce_balance(self, user_id, amount):
        account_service.reduce(user_id, amount)

    def create_delivery(self, order_id):
        return logistics_service.create(order_id)

    def cancel_order(self, order_id):
        order_service.cancel(order_id)

    def restore_stock(self, product_id, quantity):
        stock_service.restore(product_id, quantity)

    def restore_balance(self, user_id, amount):
        account_service.restore(user_id, amount)

    def cancel_delivery(self, delivery_id):
        logistics_service.cancel(delivery_id)

优点:
- 集中式: 流程清晰,易于理解和调试
- 可控: 协调器掌握全局状态,易于监控

缺点:
- 协调器可能成为瓶颈
- 协调器故障,需要有容错机制

Saga的注意事项:

1. 幂等性
   - 补偿操作可能重复执行
   - 需要保证幂等性

   示例:
   def restore_stock(product_id, quantity, saga_id):
       # 使用saga_id保证幂等
       if redis.exists(f"saga:compensated:{saga_id}"):
           return  # 已补偿,直接返回

       db.execute("UPDATE products SET stock = stock + ? WHERE id = ?",
                  quantity, product_id)

       redis.set(f"saga:compensated:{saga_id}", "1", ex=86400)

2. 空回滚
   - Try阶段还未执行,就收到Cancel请求
   - 需要记录Try是否执行,避免空回滚

3. 悬挂
   - Cancel比Try先执行
   - Try执行时需要检查是否已被取消

4. 隔离性
   - Saga没有隔离性,中间状态对外可见
   - 需要业务层容忍 (如订单状态显示"处理中")

方案对比总结:

方案一致性可用性性能复杂度适用场景
2PC强一致短期过渡
3PC强一致很少使用
Saga最终一致互联网应用 (推荐)
TCC最终一致较好很高金融系统
本地消息表最终一致通用方案

推荐方案:

  • 高并发场景 → Saga (编排模式)
  • 金融场景 → TCC (严格的一致性)
  • 简单场景 → 本地消息表 + 消息队列
  • 不推荐 → 2PC/3PC (性能差,互联网公司已淘汰)

(未完待续...本文档包含分布式理论和事务部分,后续还有服务注册与发现、负载均衡、服务网格等内容)