一、分布式理论基础
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无法同步数据到B和C
- 有两个选择:
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, HBase | Cassandra, DynamoDB, Eureka |
| 用户体验 | 可能拒绝服务 | 可能读到旧数据 |
选择建议:
- 金融、支付、库存 → CP (强一致性)
- 购物车、浏览历史、推荐 → AP (高可用)
- 服务注册发现 → AP (Eureka) 或 CP (ZooKeeper/Consul)
- 配置中心 → CP (配置错误影响巨大)
- 缓存 → 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的对比:
| 特性 | ACID | BASE |
|---|---|---|
| 一致性 | 强一致 | 最终一致 |
| 隔离性 | 完全隔离 | 允许中间状态 |
| 可用性 | 可能阻塞 | 高可用 |
| 性能 | 较低 | 较高 |
| 适用场景 | 传统数据库 | 分布式系统 |
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 (性能差,互联网公司已淘汰)
(未完待续...本文档包含分布式理论和事务部分,后续还有服务注册与发现、负载均衡、服务网格等内容)