分布式事务
一、分布式事务基本概念
1.1 定义与特点
graph TB
subgraph 单体应用事务
A1[Service] -->|本地事务| D1[(MySQL)]
style A1 fill:#c8e6c9
style D1 fill:#c8e6c9
end
subgraph 分布式事务
A2[订单服务] -->|事务1| D2[(订单库)]
A3[库存服务] -->|事务2| D3[(库存库)]
A4[账户服务] -->|事务3| D4[(账户库)]
A2 -.->|网络调用| A3
A3 -.->|网络调用| A4
style A2 fill:#ffcdd2
style A3 fill:#ffcdd2
style A4 fill:#ffcdd2
end
Note["多个数据库 + 网络通信<br/>ACID难以保证"]
Note --> D4
分布式事务的核心挑战:
| 挑战 | 说明 | 示例 |
|---|---|---|
| 网络延迟 | 节点间通信存在延迟 | 订单服务调用库存服务,RT=200ms |
| 网络分区 | 网络故障导致节点间无法通信 | 机房之间网络中断 |
| 节点故障 | 部分节点随时可能宕机 | 库存服务突然重启 |
| 数据一致性 | 多个数据源状态需保持一致 | 订单创建成功但库存未扣减 |
1.2 事务的ACID特性
graph LR
A[原子性<br/>Atomicity] -->|保证| B[一致性<br/>Consistency]
B -->|保证| C[隔离性<br/>Isolation]
C -->|保证| D[持久性<br/>Durability]
style A fill:#e3f2fd
style B fill:#e8f5e9
style C fill:#fff3e0
style D fill:#fce4ec
ACID详解:
| 特性 | 定义 | 分布式场景下的难点 | 解决思路 |
|---|---|---|---|
| 原子性 | 操作要么全做,要么全不做 | 多个节点操作,如何保证同时成功或同时回滚 | 两阶段提交、事务协调器 |
| 一致性 | 事务执行前后数据状态一致 | 网络延迟导致各节点数据不一致 | 最终一致性、强一致性协议 |
| 隔离性 | 事务间互不干扰 | 分布式锁实现复杂,性能开销大 | 乐观锁、悲观锁、MVCC |
| 持久性 | 事务一旦提交,结果永久保存 | 节点宕机可能丢失未持久化数据 | WAL日志、多副本复制 |
代码示例:本地事务 vs 分布式事务
// ========== 本地事务(单体应用)==========
@Service
public class LocalOrderService {
@Transactional
public void createOrder(Order order) {
// 同一个数据库连接,同一个事务
orderDao.insert(order); // 插入订单
orderItemDao.insert(orderItems); // 插入订单项
// 要么都成功,要么都回滚(数据库保证)
}
}
// ========== 分布式事务(微服务)==========
@Service
public class DistributedOrderService {
// 涉及3个服务 + 3个数据库,无全局事务控制
public void createOrder(Order order) {
orderService.createOrder(order); // 订单库
stockService.deductStock(order.getSkuId()); // 库存库(远程调用)
accountService.deductBalance(order.getUserId(), order.getAmount()); // 账户库(远程调用)
// 问题:如果库存扣减失败,订单已创建,如何回滚?
}
}
二、分布式事务管理策略
2.1 两阶段提交协议(2PC)
sequenceDiagram
participant C as Coordinator<br/>协调者
participant P1 as Participant 1<br/>订单库
participant P2 as Participant 2<br/>库存库
participant P3 as Participant 3<br/>账户库
Note over C,P3: 阶段一:准备阶段(Prepare Phase)
C->>P1: 1. CanCommit?
P1->>P1: 执行本地事务(不提交)
P1-->>C: 2. Yes/No(Vote)
C->>P2: 3. CanCommit?
P2->>P2: 执行本地事务(不提交)
P2-->>C: 4. Yes/No(Vote)
C->>P3: 5. CanCommit?
P3->>P3: 执行本地事务(不提交)
P3-->>C: 6. Yes/No(Vote)
Note over C,P3: 阶段二:提交阶段(Commit Phase)
alt 所有参与者返回Yes
C->>P1: 7. DoCommit
P1->>P1: 正式提交本地事务
P1-->>C: 8. ACK
C->>P2: 9. DoCommit
P2->>P2: 正式提交本地事务
P2-->>C: 10. ACK
C->>P3: 11. DoCommit
P3->>P3: 正式提交本地事务
P3-->>C: 12. ACK
Note right of C: 事务完成!
else 任一参与者返回No
C->>P1: 7. DoRollback
P1->>P1: 回滚本地事务
C->>P2: 8. DoRollback
P2->>P2: 回滚本地事务
C->>P3: 9. DoRollback
P3->>P3: 回滚本地事务
Note right of C: 全部回滚!
end
2PC 核心问题分析:
2PC 代码实现(Seata AT模式底层):
// 模拟2PC协调者
public class TwoPhaseCommitCoordinator {
private List<Participant> participants = new ArrayList<>();
/**
* 阶段一:准备阶段
*/
public boolean preparePhase() {
boolean allYes = true;
for (Participant p : participants) {
try {
// 询问参与者是否可以提交
boolean vote = p.prepare();
if (!vote) {
allYes = false;
break; // 有一个No就结束
}
} catch (Exception e) {
allYes = false;
break;
}
}
return allYes;
}
/**
* 阶段二:提交或回滚
*/
public void commitPhase(boolean shouldCommit) {
if (shouldCommit) {
// 所有参与者都同意,发送DoCommit
for (Participant p : participants) {
p.commit(); // 正式提交
}
} else {
// 有参与者不同意,发送DoRollback
for (Participant p : participants) {
p.rollback(); // 回滚
}
}
}
}
// 参与者接口
public interface Participant {
boolean prepare() throws Exception; // 执行本地事务,不提交,返回Vote
void commit(); // 提交本地事务
void rollback(); // 回滚本地事务
}
2.2 三阶段提交协议(3PC)
sequenceDiagram
participant C as Coordinator
participant P1 as Participant 1
participant P2 as Participant 2
Note over C,P2: 阶段一:CanCommit(预检查)
C->>P1: 1. CanCommit?
P1->>P1: 检查资源是否可用(不执行事务)
P1-->>C: 2. Yes/No
C->>P2: 3. CanCommit?
P2->>P2: 检查资源是否可用
P2-->>C: 4. Yes/No
Note over C,P2: 阶段二:PreCommit(预提交)
alt 所有参与者返回Yes
C->>P1: 5. PreCommit
P1->>P1: 执行本地事务(不提交)
P1-->>C: 6. ACK
C->>P2: 7. PreCommit
P2->>P2: 执行本地事务(不提交)
P2-->>C: 8. ACK
else 有参与者返回No
C->>P1: 5. DoAbort
C->>P2: 6. DoAbort
end
Note over C,P2: 阶段三:DoCommit(正式提交)
alt 所有参与者PreCommit成功
C->>P1: 9. DoCommit
P1->>P1: 正式提交
P1-->>C: 10. ACK
C->>P2: 11. DoCommit
P2->>P2: 正式提交
P2-->>C: 12. ACK
else 协调者超时或参与者未收到DoCommit
P1->>P1: 超时自动提交(默认提交策略)
P2->>P2: 超时自动提交
end
2PC vs 3PC 对比:
| 特性 | 2PC | 3PC |
|---|---|---|
| 阶段数 | 2个阶段 | 3个阶段 |
| 阻塞性 | 参与者阻塞等待协调者指令 | 引入超时机制,减少阻塞 |
| 单点故障 | 协调者宕机,参与者一直阻塞 | 引入超时自动提交,降低阻塞风险 |
| 数据一致性 | 协调者宕机可能不一致 | 超时默认提交,仍可能不一致 |
| 性能 | 较少网络交互 | 多一次网络交互 |
| 实现复杂度 | 相对简单 | 更复杂 |
3PC的改进与局限:
graph LR
A[2PC问题] -->|解决思路| B[3PC改进]
A1[协调者宕机<br/>参与者阻塞] -->|引入超时| B1[参与者超时自动提交]
A2[无法判断<br/>参与者状态] -->|增加CanCommit| B2[预检查阶段]
B -->|新问题| C[3PC局限]
B1 -->|网络分区时| C1[部分提交部分回滚<br/>数据不一致]
B2 -->|多一轮交互| C2[性能更差]
style A1 fill:#ffcdd2
style A2 fill:#ffcdd2
style C1 fill:#ffcdd2
style C2 fill:#ffcdd2
2.3 补偿事务(TCC)
TCC 完整代码示例:
// ========== TCC接口定义 ==========
public interface OrderTccService {
/**
* Try阶段:预留资源
* 创建订单,但状态为"冻结",不真正扣减资源
*/
@TwoPhaseBusinessAction(name = "orderTccAction",
commitMethod = "commit",
rollbackMethod = "rollback")
boolean tryCreateOrder(@BusinessActionContextParameter(paramName = "orderId") String orderId,
@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "amount") BigDecimal amount);
/**
* Confirm阶段:确认操作
* 将订单状态改为"已支付",正式生效
*/
boolean commit(BusinessActionContext context);
/**
* Cancel阶段:取消操作
* 删除订单或改为"已取消",释放预留资源
*/
boolean rollback(BusinessActionContext context);
}
// ========== 订单服务TCC实现 ==========
@Service
public class OrderTccServiceImpl implements OrderTccService {
@Autowired
private OrderDao orderDao;
@Override
public boolean tryCreateOrder(String orderId, String userId, BigDecimal amount) {
// Try: 创建冻结订单
Order order = new Order();
order.setOrderId(orderId);
order.setUserId(userId);
order.setAmount(amount);
order.setStatus("FREEZE"); // 冻结状态
order.setCreateTime(new Date());
// 插入订单记录(预留资源)
int result = orderDao.insert(order);
return result > 0;
}
@Override
public boolean commit(BusinessActionContext context) {
String orderId = context.getActionContext("orderId");
// Confirm: 将冻结订单改为已支付
Order order = orderDao.selectById(orderId);
if ("FREEZE".equals(order.getStatus())) {
order.setStatus("PAID");
orderDao.update(order);
log.info("订单{}确认成功", orderId);
return true;
}
// 幂等性处理:如果已经是PAID,直接返回成功
return "PAID".equals(order.getStatus());
}
@Override
public boolean rollback(BusinessActionContext context) {
String orderId = context.getActionContext("orderId");
// Cancel: 取消订单,释放资源
Order order = orderDao.selectById(orderId);
if (order != null && "FREEZE".equals(order.getStatus())) {
order.setStatus("CANCELLED");
orderDao.update(order);
log.info("订单{}已取消", orderId);
return true;
}
return true; // 幂等性:已取消或不存在也算成功
}
}
// ========== 库存服务TCC实现 ==========
@Service
public class StockTccServiceImpl {
@Autowired
private StockDao stockDao;
@Autowired
private StockFreezeDao freezeDao;
public boolean tryDeduct(String skuId, Integer count) {
// 检查库存是否充足
Stock stock = stockDao.selectBySkuId(skuId);
if (stock.getAvailable() < count) {
return false; // 库存不足
}
// 冻结库存(不真正扣减)
StockFreeze freeze = new StockFreeze();
freeze.setSkuId(skuId);
freeze.setCount(count);
freeze.setStatus("FREEZE");
freezeDao.insert(freeze);
// 预扣减可用库存
stockDao.deductAvailable(skuId, count);
return true;
}
public boolean commit(String skuId, Integer count) {
// 正式扣减:将冻结转为实际扣减
freezeDao.updateStatus(skuId, "CONFIRMED");
// 扣减总库存(可用已在Try阶段扣减)
stockDao.deductTotal(skuId, count);
return true;
}
public boolean rollback(String skuId, Integer count) {
// 释放冻结:恢复可用库存
freezeDao.updateStatus(skuId, "CANCELLED");
stockDao.addAvailable(skuId, count);
return true;
}
}
TCC 事务协调流程:
sequenceDiagram
participant TM as Transaction Manager
participant O as Order服务
participant S as Stock服务
participant A as Account服务
Note over TM,A: TCC事务开始
TM->>O: 1. tryCreateOrder()
O-->>TM: 2. 返回true(预留成功)
TM->>S: 3. tryDeduct()
S-->>TM: 4. 返回true(预留成功)
TM->>A: 5. tryDeductBalance()
alt 账户余额不足
A-->>TM: 6a. 返回false
TM->>O: 7a. rollback()
TM->>S: 8a. rollback()
Note right of TM: 全部回滚
else 余额充足
A-->>TM: 6b. 返回true
Note over TM,A: 所有Try成功,执行Confirm
TM->>O: 7b. commit()
O-->>TM: 8b. 确认成功
TM->>S: 9b. commit()
S-->>TM: 10b. 确认成功
TM->>A: 11b. commit()
A-->>TM: 12b. 确认成功
Note right of TM: 事务完成!
end
TCC 核心要点:
| 要点 | 说明 | 实现注意 |
|---|---|---|
| 幂等性 | Confirm/Cancel可能被多次调用 | 记录事务状态,已处理过的直接返回成功 |
| 空回滚 | Try未执行或失败,Cancel被调用 | Cancel需判断资源是否被预留 |
| 悬挂 | Try超时后执行,此时事务已回滚 | Try执行前检查事务是否已结束 |
| 业务侵入 | 每个操作需拆分为3个方法 | 业务逻辑需配合改造 |
三、分布式事务应用场景
3.1 微服务架构中的分布式服务调用
graph TB
subgraph 电商下单场景
GW[API网关]
OS[订单服务<br/>order-service]
SS[库存服务<br/>stock-service]
PS[支付服务<br/>pay-service]
US[用户服务<br/>user-service]
DB1[(订单库)]
DB2[(库存库)]
DB3[(支付库)]
DB4[(用户库)]
GW --> OS
OS -->|1.创建订单| DB1
OS -->|2.扣减库存| SS
SS -->|2.1| DB2
OS -->|3.查询用户信息| US
US -->|3.1| DB4
OS -->|4.发起支付| PS
PS -->|4.1| DB3
end
style OS fill:#ffcdd2
style SS fill:#ffcdd2
style PS fill:#ffcdd2
style DB1 fill:#e8f5e9
style DB2 fill:#e8f5e9
场景分析:
sequenceDiagram
participant C as 用户
participant OS as 订单服务
participant SS as 库存服务
participant PS as 支付服务
C->>OS: 1. 提交订单请求
OS->>OS: 2. 本地事务:创建订单(状态=待支付)
OS->>SS: 3. 远程调用:扣减库存
alt 库存充足
SS->>SS: 3.1 扣减库存成功
SS-->>OS: 3.2 返回成功
else 库存不足
SS-->>OS: 3.3 返回库存不足
OS->>OS: 3.4 回滚本地订单
OS-->>C: 3.5 下单失败
end
OS->>PS: 4. 远程调用:创建支付单
PS->>PS: 4.1 创建支付记录
PS-->>OS: 4.2 返回支付链接
OS-->>C: 5. 返回订单信息+支付链接
Note over C,PS: 问题:如果步骤3成功,步骤4失败,库存已扣但订单未支付<br/>需要分布式事务保证一致性
3.2 分布式缓存与数据库的一致性同步
graph TB
subgraph 缓存一致性策略
direction TB
subgraph 先更新数据库再更新缓存
A1[更新数据库] -->|成功| A2[更新缓存]
A2 -->|失败| A3[缓存不一致]
style A3 fill:#ffcdd2
end
subgraph "先更新数据库再删缓存(Cache Aside)"
B1[更新数据库] -->|成功| B2[删除缓存]
B2 -->|下次读取时| B3[从DB加载到缓存]
style B2 fill:#c8e6c9
style B3 fill:#c8e6c9
end
subgraph 延时双删
C1[删除缓存] -->|更新数据库| C2[延时500ms]
C2 -->|再次删除缓存| C3[保证最终一致]
style C3 fill:#fff3e0
end
end
缓存一致性方案对比:
| 方案 | 流程 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 先更新DB再更新缓存 | 更新DB → 更新Cache | 简单直观 | 并发时数据不一致 | 低并发读场景 |
| Cache Aside(推荐) | 读:先Cache,miss则读DB写Cache 写:更新DB → 删Cache | 简单可靠 | 短暂不一致 | 绝大多数场景 |
| 延时双删 | 删Cache → 更新DB → 延时删Cache | 减少不一致窗口 | 延时时间难确定 | 高一致性要求 |
| 基于消息队列 | 更新DB → 发送消息 → 异步更新Cache | 解耦 | 最终一致 | 高并发场景 |
基于消息队列的缓存同步代码:
@Service
public class ProductService {
@Autowired
private ProductDao productDao;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 更新商品信息:先更新DB,再发送消息异步更新缓存
*/
@Transactional
public void updateProduct(Product product) {
// 1. 更新数据库(本地事务)
productDao.update(product);
// 2. 发送消息通知缓存更新(事务消息保证一致性)
Message<ProductChangeEvent> message = MessageBuilder
.withPayload(new ProductChangeEvent(product.getId(), "UPDATE"))
.build();
// 发送事务消息:保证消息发送和本地事务同时成功或失败
TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(
"cache-update-topic",
message,
product.getId()
);
if (!result.getLocalTransactionState().equals(LocalTransactionState.COMMIT_MESSAGE)) {
throw new BusinessException("缓存同步消息发送失败");
}
}
}
// 缓存更新消费者
@Service
@RocketMQMessageListener(topic = "cache-update-topic", consumerGroup = "cache-consumer")
public class CacheUpdateConsumer implements RocketMQListener<ProductChangeEvent> {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private ProductDao productDao;
@Override
public void onMessage(ProductChangeEvent event) {
Long productId = event.getProductId();
switch (event.getAction()) {
case "UPDATE":
// 删除缓存,下次读取时从DB加载
redisTemplate.delete("product:" + productId);
break;
case "DELETE":
redisTemplate.delete("product:" + productId);
break;
}
}
}
3.3 分布式消息队列的事务处理
RocketMQ事务消息完整代码:
@Service
public class OrderTransactionService {
@Autowired
private OrderDao orderDao;
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 创建订单并发送消息(事务消息保证一致性)
*/
public void createOrderWithMessage(Order order) {
// 构建消息
Message<OrderCreatedEvent> message = MessageBuilder
.withPayload(new OrderCreatedEvent(order.getOrderId(), order.getUserId()))
.setHeader("KEYS", order.getOrderId())
.build();
// 发送事务消息
TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(
"order-topic",
message,
order // 本地事务参数
);
if (sendResult.getSendStatus() != SendStatus.SEND_OK) {
throw new RuntimeException("事务消息发送失败");
}
}
/**
* 事务监听器
*/
@RocketMQTransactionListener
class OrderTransactionListener implements RocketMQLocalTransactionListener {
@Autowired
private OrderDao orderDao;
/**
* 执行本地事务
*/
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
Order order = (Order) arg;
try {
// 执行本地事务:创建订单
orderDao.insert(order);
// 本地事务成功,提交Half消息
return RocketMQLocalTransactionState.COMMIT;
} catch (Exception e) {
// 本地事务失败,回滚Half消息
return RocketMQLocalTransactionState.ROLLBACK;
}
}
/**
* 回查本地事务状态
*/
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
String orderId = msg.getHeaders().get("KEYS", String.class);
// 查询订单是否存在
Order order = orderDao.selectById(orderId);
if (order != null) {
// 订单存在,本地事务已提交
return RocketMQLocalTransactionState.COMMIT;
} else {
// 订单不存在,本地事务未提交或已回滚
return RocketMQLocalTransactionState.ROLLBACK;
}
}
}
}
四、分布式事务挑战与解决方案
4.1 数据一致性问题
一致性保障方案对比:
| 方案 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 分布式锁 | 通过锁保证操作串行化 | 强一致 | 性能差,死锁风险 | 库存扣减等强一致场景 |
| 分布式事务协调器 | 2PC/3PC/TCC/Saga | 标准方案 | 复杂度高 | 金融交易 |
| 异步补偿 | 失败时发送补偿消息 | 最终一致,性能高 | 补偿逻辑复杂 | 长事务场景 |
| 本地消息表 | 本地事务+消息表+定时扫描 | 可靠,不依赖外部组件 | 需要额外开发 | 大多数业务场景 |
本地消息表实现:
graph TB
subgraph 本地消息表架构
A[业务服务] -->|1. 本地事务| B[(业务表)]
A -->|1. 同一事务| C[(消息表)]
D[定时任务] -->|2. 扫描未发送消息| C
D -->|3. 发送消息| E[消息队列]
E -->|4. 消费| F[下游服务]
F -->|5. 业务处理| G[(下游库)]
F -->|6. 发送ACK| E
E -->|7. 更新消息状态| C
end
style C fill:#fff3e0
style D fill:#e3f2fd
@Service
public class LocalMessageTableService {
@Autowired
private OrderDao orderDao;
@Autowired
private MessageRecordDao messageDao;
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 本地消息表模式:保证业务操作和消息发送的原子性
*/
@Transactional
public void createOrder(Order order) {
// 1. 保存订单
orderDao.insert(order);
// 2. 同一事务中,插入消息记录
MessageRecord record = new MessageRecord();
record.setId(UUID.randomUUID().toString());
record.setTopic("order-created");
record.setBody(JsonUtils.toJson(order));
record.setStatus("PENDING"); // 待发送
record.setCreateTime(new Date());
record.setRetryCount(0);
messageDao.insert(record);
// 事务提交后,订单和消息记录同时成功或同时失败
}
/**
* 定时任务:扫描并发送未处理消息
*/
@Scheduled(fixedRate = 5000) // 每5秒执行
public void scanPendingMessages() {
// 查询待发送消息(限制数量,防止积压)
List<MessageRecord> pendingList = messageDao
.selectByStatus("PENDING", 100);
for (MessageRecord record : pendingList) {
try {
// 发送消息到MQ
rabbitTemplate.convertAndSend(
record.getTopic(),
record.getBody()
);
// 更新状态为已发送
record.setStatus("SENT");
record.setSendTime(new Date());
messageDao.update(record);
} catch (Exception e) {
// 发送失败,增加重试次数
record.setRetryCount(record.getRetryCount() + 1);
if (record.getRetryCount() > 5) {
record.setStatus("FAILED"); // 进入死信队列人工处理
}
messageDao.update(record);
}
}
}
}
4.2 事务超时与故障恢复
graph TB
subgraph 超时处理策略
A[设置合理超时时间] --> B[订单创建超时: 30秒]
A --> C[支付超时: 15分钟]
A --> D[库存冻结超时: 30分钟]
B -->|超时后| E[自动回滚]
C -->|超时后| F[订单关闭]
D -->|超时后| G[自动释放库存]
end
subgraph 故障恢复机制
H[日志记录] --> I[操作日志<br/>undo_log / redo_log]
J[持久化记录] --> K[事务状态表]
L[定时扫描] --> M[恢复未完成事务]
end
style E fill:#ffcdd2
style F fill:#ffcdd2
style G fill:#ffcdd2
style M fill:#c8e6c9
超时与恢复代码示例:
@Service
public class TransactionRecoveryService {
@Autowired
private TransactionRecordDao transactionDao;
@Autowired
private OrderDao orderDao;
@Autowired
private StockService stockService;
/**
* 定时任务:恢复超时未完成的事务
*/
@Scheduled(fixedRate = 60000) // 每分钟执行
public void recoverTimeoutTransactions() {
// 查询超时事务(创建时间超过30分钟且状态未完成)
Date timeout = new Date(System.currentTimeMillis() - 30 * 60 * 1000);
List<TransactionRecord> timeoutList = transactionDao
.selectTimeoutTransactions("INIT", timeout);
for (TransactionRecord tx : timeoutList) {
try {
// 根据事务类型执行恢复
switch (tx.getType()) {
case "ORDER_CREATE":
recoverOrderTransaction(tx);
break;
case "STOCK_DEDUCT":
recoverStockTransaction(tx);
break;
}
} catch (Exception e) {
log.error("事务恢复失败: {}", tx.getId(), e);
// 记录失败,人工介入
tx.setStatus("RECOVER_FAILED");
transactionDao.update(tx);
}
}
}
private void recoverOrderTransaction(TransactionRecord tx) {
// 查询订单状态
Order order = orderDao.selectByTxId(tx.getId());
if (order == null) {
// 订单未创建,直接标记事务为已回滚
tx.setStatus("ROLLBACK");
} else if ("PENDING".equals(order.getStatus())) {
// 订单待支付但已超时,关闭订单并回滚库存
order.setStatus("CLOSED");
orderDao.update(order);
// 发送库存回滚消息
stockService.rollbackDeduct(order.getOrderId());
tx.setStatus("ROLLBACK");
}
transactionDao.update(tx);
}
}
4.3 性能瓶颈与优化
graph TB
subgraph 性能优化策略
A[减少网络通信开销] --> B[合并多个小事务]
A --> C[批量操作<br/>Batch Insert/Update]
A --> D[本地缓存热点数据]
E[优化事务协调器性能] --> F[异步提交]
E --> G[并行Prepare]
E --> H[减少锁粒度]
I[其他优化] --> J[读写分离]
I --> K[分库分表]
I --> L[异步化非核心操作]
end
style B fill:#c8e6c9
style F fill:#c8e6c9
style L fill:#c8e6c9
批量操作优化示例:
@Service
public class BatchOperationService {
@Autowired
private OrderDao orderDao;
/**
* 批量创建订单(减少事务数量)
*/
@Transactional
public void batchCreateOrders(List<Order> orders) {
// 批量插入,减少数据库交互次数
orderDao.batchInsert(orders);
// 批量发送消息(而非逐条发送)
List<Message> messages = orders.stream()
.map(order -> MessageBuilder
.withPayload(new OrderCreatedEvent(order.getOrderId()))
.build())
.collect(Collectors.toList());
// 批量发送,减少网络开销
rocketMQTemplate.syncSend("order-topic", messages, 3000);
}
/**
* 异步化非核心操作
*/
public void createOrderAsync(Order order) {
// 核心操作:同步执行(创建订单)
orderDao.insert(order);
// 非核心操作:异步执行(发送通知、更新统计等)
CompletableFuture.runAsync(() -> {
sendNotification(order);
updateStatistics(order);
});
}
}
五、分布式事务实践案例
5.1 电商平台的订单处理
graph TB
subgraph 电商订单分布式事务
U[用户] -->|1. 提交订单| GW[网关]
GW -->|2. 创建订单| OS[订单服务]
OS -->|3. 扣减库存| SS[库存服务]
OS -->|4. 查询用户信息| US[用户服务]
OS -->|5. 创建支付单| PS[支付服务]
OS -->|6. 发送延迟消息<br/>15分钟后检查| MQ[消息队列]
SS -->|3.1| SDB[(库存库)]
US -->|4.1| UDB[(用户库)]
PS -->|5.1| PDB[(支付库)]
OS -->|2.1| ODB[(订单库)]
MQ -->|7. 超时未支付| OS
OS -->|8. 关闭订单| ODB
OS -->|9. 释放库存| SS
end
style OS fill:#ffcdd2
style SS fill:#ffcdd2
style PS fill:#ffcdd2
style MQ fill:#fff3e0
电商订单TCC完整实现:
@Service
public class EcommerceOrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private StockTccService stockTccService;
@Autowired
private AccountTccService accountTccService;
@Autowired
private TransactionTemplate transactionTemplate;
/**
* 创建订单(TCC分布式事务)
*/
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public OrderResult createOrder(CreateOrderRequest request) {
String orderId = generateOrderId();
String userId = request.getUserId();
BigDecimal amount = request.getAmount();
String skuId = request.getSkuId();
Integer count = request.getCount();
try {
// ========== Try阶段:预留所有资源 ==========
// 1. 订单服务Try:创建冻结订单
boolean orderTry = orderTccService.tryCreateOrder(orderId, userId, amount);
if (!orderTry) throw new BusinessException("订单创建失败");
// 2. 库存服务Try:冻结库存
boolean stockTry = stockTccService.tryDeduct(skuId, count);
if (!stockTry) throw new BusinessException("库存不足");
// 3. 账户服务Try:冻结金额
boolean accountTry = accountTccService.tryDeduct(userId, amount);
if (!accountTry) throw new BusinessException("余额不足");
// ========== 所有Try成功,进入Confirm阶段 ==========
// 由@GlobalTransactional自动触发各服务的commit
// 发送延时消息:15分钟后检查支付状态
sendDelayCheckMessage(orderId, 15 * 60 * 1000);
return new OrderResult(orderId, "CREATED");
} catch (Exception e) {
// 任一Try失败,自动触发Cancel回滚所有预留资源
throw e;
}
}
/**
* 支付回调(Confirm阶段)
*/
public void onPaymentSuccess(String orderId) {
// 将冻结订单改为已支付
orderDao.updateStatus(orderId, "PAID");
// 触发各服务Confirm
// 库存:冻结转实际扣减
// 账户:冻结转实际扣减
}
/**
* 超时未支付处理(Cancel阶段)
*/
@RocketMQMessageListener(topic = "order-timeout-check")
public class OrderTimeoutListener implements RocketMQListener<String> {
@Override
public void onMessage(String orderId) {
Order order = orderDao.selectById(orderId);
if ("FREEZE".equals(order.getStatus())) {
// 超时未支付,触发Cancel
// 1. 取消订单
orderDao.updateStatus(orderId, "CANCELLED");
// 2. 释放库存
stockTccService.cancelDeduct(order.getSkuId(), order.getCount());
// 3. 释放金额
accountTccService.cancelDeduct(order.getUserId(), order.getAmount());
}
}
}
}
5.2 金融系统的转账操作
sequenceDiagram
participant F as 转账服务
participant A as 账户A服务
participant B as 账户B服务
participant L as 事务日志服务
Note over F,L: 跨行转账分布式事务
F->>F: 1. 生成全局事务ID
F->>A: 2. Try:冻结账户A金额
A->>A: 2.1 检查余额
A->>A: 2.2 冻结金额(状态=PENDING)
A->>L: 2.3 记录操作日志
A-->>F: 2.4 返回成功
F->>B: 3. Try:预增加账户B金额
B->>B: 3.1 预增加金额(状态=PENDING)
B->>L: 3.2 记录操作日志
B-->>F: 3.3 返回成功
alt 所有Try成功
F->>A: 4a. Confirm:正式扣减
A->>A: 4a.1 冻结转扣减(状态=CONFIRMED)
A->>L: 4a.2 更新日志
F->>B: 5a. Confirm:正式增加
B->>B: 5a.1 预增加转正(状态=CONFIRMED)
B->>L: 5a.2 更新日志
Note over F,L: 转账成功!
else 任一Try失败
F->>A: 4b. Cancel:释放冻结
A->>A: 4b.1 解冻金额
A->>L: 4b.2 更新日志
F->>B: 5b. Cancel:取消预增加
B->>B: 5b.1 删除预增加记录
B->>L: 5b.2 更新日志
Note over F,L: 转账失败,全部回滚!
end
金融转账TCC实现:
@Service
public class TransferService {
@Autowired
private AccountTccService accountTccService;
@Autowired
private TransferLogDao transferLogDao;
/**
* 跨行转账(强一致性要求)
*/
@GlobalTransactional(name = "bank-transfer", timeoutMills = 30000)
public TransferResult transfer(TransferRequest request) {
String txId = request.getTransactionId();
String fromAccount = request.getFromAccount();
String toAccount = request.getToAccount();
BigDecimal amount = request.getAmount();
// 记录转账日志
TransferLog log = new TransferLog();
log.setTxId(txId);
log.setFromAccount(fromAccount);
log.setToAccount(toAccount);
log.setAmount(amount);
log.setStatus("INIT");
transferLogDao.insert(log);
try {
// Try阶段
boolean fromTry = accountTccService.tryDeduct(fromAccount, amount, txId);
if (!fromTry) throw new InsufficientBalanceException("转出账户余额不足");
boolean toTry = accountTccService.tryAdd(toAccount, amount, txId);
if (!toTry) throw new AccountException("转入账户异常");
// 所有Try成功,自动进入Confirm
log.setStatus("SUCCESS");
transferLogDao.update(log);
return new TransferResult(txId, "SUCCESS");
} catch (Exception e) {
log.setStatus("FAILED");
transferLogDao.update(log);
throw e;
}
}
}
@Service
public class AccountTccServiceImpl {
@Autowired
private AccountDao accountDao;
@Autowired
private AccountFreezeDao freezeDao;
public boolean tryDeduct(String accountNo, BigDecimal amount, String txId) {
Account account = accountDao.selectByAccountNo(accountNo);
// 检查余额
if (account.getBalance().compareTo(amount) < 0) {
return false;
}
// 创建冻结记录
AccountFreeze freeze = new AccountFreeze();
freeze.setTxId(txId);
freeze.setAccountNo(accountNo);
freeze.setAmount(amount);
freeze.setType("DEDUCT");
freeze.setStatus("FREEZE");
freezeDao.insert(freeze);
// 扣减可用余额
accountDao.deductAvailable(accountNo, amount);
return true;
}
public boolean confirmDeduct(String txId) {
AccountFreeze freeze = freezeDao.selectByTxId(txId);
if (freeze == null || !"FREEZE".equals(freeze.getStatus())) {
return true; // 幂等性处理
}
// 正式扣减:减少总余额
accountDao.deductTotal(freeze.getAccountNo(), freeze.getAmount());
// 更新冻结状态
freeze.setStatus("CONFIRMED");
freezeDao.update(freeze);
return true;
}
public boolean cancelDeduct(String txId) {
AccountFreeze freeze = freezeDao.selectByTxId(txId);
if (freeze == null) return true;
// 恢复可用余额
accountDao.addAvailable(freeze.getAccountNo(), freeze.getAmount());
// 更新冻结状态
freeze.setStatus("CANCELLED");
freezeDao.update(freeze);
return true;
}
}
5.3 分布式数据库的数据迁移与同步
graph TB
subgraph 数据库迁移架构
A[源数据库<br/>MySQL主库] -->|1. 全量迁移| B[目标数据库<br/>TiDB/分库分表]
A -->|2. 增量同步| C[Canal/Debezium<br/>Binlog解析]
C -->|3. 消息队列| D[Kafka]
D -->|4. 消费同步| B
E[迁移校验服务] -->|5. 数据一致性校验| A
E -->|5. 数据一致性校验| B
F[双写阶段] -->|6. 同时写入新旧库| A
F -->|6. 同时写入新旧库| B
G[灰度切换] -->|7. 逐步切流量| H[新库]
end
style C fill:#e3f2fd
style D fill:#fff3e0
style E fill:#c8e6c9
style F fill:#ffcdd2
数据迁移事务一致性保障:
@Service
public class DataMigrationService {
@Autowired
private JdbcTemplate oldDbTemplate;
@Autowired
private JdbcTemplate newDbTemplate;
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
/**
* 双写阶段:保证新旧库数据一致
*/
@Transactional(transactionManager = "oldDbTxManager")
public void dualWrite(Order order) {
try {
// 1. 写入旧库(主库,事务控制)
oldDbTemplate.update(
"INSERT INTO orders (id, user_id, amount, status) VALUES (?, ?, ?, ?)",
order.getId(), order.getUserId(), order.getAmount(), order.getStatus()
);
// 2. 发送消息到Kafka,异步写入新库
// 使用事务消息保证:旧库写入成功,消息一定发送
kafkaTemplate.send("order-sync-topic", JsonUtils.toJson(order));
} catch (Exception e) {
// 旧库写入失败,事务回滚,消息不会发送
throw e;
}
}
/**
* 增量同步消费者
*/
@KafkaListener(topics = "order-sync-topic", groupId = "sync-consumer")
public void syncToNewDb(String message) {
Order order = JsonUtils.fromJson(message, Order.class);
try {
// 写入新库
newDbTemplate.update(
"INSERT INTO orders (id, user_id, amount, status) VALUES (?, ?, ?, ?) " +
"ON DUPLICATE KEY UPDATE status = ?",
order.getId(), order.getUserId(), order.getAmount(),
order.getStatus(), order.getStatus()
);
} catch (Exception e) {
// 写入失败,进入死信队列人工处理
kafkaTemplate.send("order-sync-dlq", message);
}
}
/**
* 数据一致性校验
*/
public void verifyConsistency() {
// 抽样对比新旧库数据
List<Order> oldOrders = oldDbTemplate.query(
"SELECT * FROM orders WHERE id > ? LIMIT 1000",
new BeanPropertyRowMapper<>(Order.class),
lastCheckedId
);
for (Order oldOrder : oldOrders) {
Order newOrder = newDbTemplate.queryForObject(
"SELECT * FROM orders WHERE id = ?",
new BeanPropertyRowMapper<>(Order.class),
oldOrder.getId()
);
if (!oldOrder.equals(newOrder)) {
// 记录不一致,触发修复
log.error("数据不一致: old={}, new={}", oldOrder, newOrder);
repairInconsistency(oldOrder);
}
}
}
}
六、主流分布式事务框架对比
graph TB
subgraph 分布式事务框架
A[Seata<br/>阿里开源] -->|AT模式| A1[自动补偿<br/>零侵入]
A -->|TCC模式| A2[业务侵入<br/>高性能]
A -->|Saga模式| A3[长事务<br/>状态机]
A -->|XA模式| A4[强一致<br/>性能差]
B[ByteTCC<br/>开源] -->|TCC| B1[纯TCC实现]
C[ShardingSphere<br/>Apache] -->|XA| C1[集成Atomikos]
C -->|BASE| C2[柔性事务]
D[自研方案] -->|本地消息表| D1[可靠消息]
D -->|Saga| D2[事件溯源]
end
style A fill:#e3f2fd
style A1 fill:#c8e6c9
style A2 fill:#fff3e0
style D1 fill:#ffcdd2
| 框架 | 模式 | 侵入性 | 性能 | 适用场景 | 学习成本 |
|---|---|---|---|---|---|
| Seata AT | 自动补偿 | 低(代理数据源) | 中 | 大多数业务场景 | 低 |
| Seata TCC | 手动补偿 | 高(需实现3个方法) | 高 | 高性能要求场景 | 中 |
| Seata Saga | 状态机 | 中(需定义状态图) | 中 | 长事务、业务流程复杂 | 高 |
| Seata XA | 2PC | 低 | 低 | 强一致性要求 | 低 |
| 本地消息表 | 异步补偿 | 高(需自建) | 高 | 不依赖外部组件 | 中 |
总结图谱
mindmap
root((分布式事务))
基本概念
定义与特点
多数据源
网络复杂性
ACID特性
原子性
一致性
隔离性
持久性
管理策略
2PC
准备阶段
提交阶段
同步阻塞问题
3PC
CanCommit
PreCommit
DoCommit
超时机制
TCC
Try预留
Confirm确认
Cancel回滚
幂等性要求
应用场景
微服务调用
缓存一致性
消息队列事务
挑战与方案
数据一致性
强一致
最终一致
本地消息表
超时故障
超时设置
故障恢复
性能优化
批量操作
异步化
实践案例
电商订单
金融转账
数据迁移
分布式事务的核心在于根据业务场景选择合适的一致性模型和实现方案。强一致性方案(2PC/XA)性能较差但数据可靠,最终一致性方案(TCC/Saga/消息队列)性能高但需处理补偿逻辑。实际生产中,BASE理论(基本可用、软状态、最终一致)指导下的柔性事务方案更为常用。