TCC---最终一致性分布式事务方案及【案例】
- [1. 2PC的实现层次](#1. 2PC的实现层次)
-
- [1.1 数据库层(基础设施层)2PC](#1.1 数据库层(基础设施层)2PC)
- [1.2 中间件层2PC](#1.2 中间件层2PC)
- [1.3 应用层2PC](#1.3 应用层2PC)
- [2. TCC](#2. TCC)
-
- [2.1 TCC基本概念](#2.1 TCC基本概念)
- [2.2 TCC要注意的问题](#2.2 TCC要注意的问题)
-
- [2.2.1 幂等问题](#2.2.1 幂等问题)
- [2.2.2 空回滚问题](#2.2.2 空回滚问题)
- [2.2.3 悬挂问题](#2.2.3 悬挂问题)
- [3. 完整的TCC分布式事务示例](#3. 完整的TCC分布式事务示例)
-
- [3.1 整体架构](#3.1 整体架构)
- [3.2 协调者服务(独立服务)](#3.2 协调者服务(独立服务))
-
- [3.2.1 数据库表结构](#3.2.1 数据库表结构)
- [3.2.2 CoordinatorService.java](#3.2.2 CoordinatorService.java)
- [3.2.3 CoordinatorController.java](#3.2.3 CoordinatorController.java)
- [3.3 订单服务(主业务)](#3.3 订单服务(主业务))
-
- [3.3.1 OrderService.java](#3.3.1 OrderService.java)
- [3.3.2 OrderController.java](#3.3.2 OrderController.java)
- [3.4 库存服务(参与者1)](#3.4 库存服务(参与者1))
-
- [3.4.1 StockService.java](#3.4.1 StockService.java)
- [3.4.2 StockController.java](#3.4.2 StockController.java)
- [3.5 积分服务(参与者2)](#3.5 积分服务(参与者2))
-
- [3.5.1 PointsService.java](#3.5.1 PointsService.java)
- [3.5.2 PointsController.java](#3.5.2 PointsController.java)
- [3.6 配置文件和依赖](#3.6 配置文件和依赖)
-
- [3.6.1 公共依赖(pom.xml)](#3.6.1 公共依赖(pom.xml))
- [3.6.2 协调者服务配置(application.yml)](#3.6.2 协调者服务配置(application.yml))
- [3.6.3 订单服务配置(application.yml)](#3.6.3 订单服务配置(application.yml))
- [3.6.4 库存服务配置(application.yml)](#3.6.4 库存服务配置(application.yml))
- [3.6.5 积分服务配置(application.yml)](#3.6.5 积分服务配置(application.yml))
- [3.7 运行流程](#3.7 运行流程)
- [3.8 关键点总结](#3.8 关键点总结)
1. 2PC的实现层次
2PC/3PC可以在不同层次实现,这是理解分布式事务的关键。
1.1 数据库层(基础设施层)2PC
java
// 典型的XA协议(数据库原生支持)
Connection conn1 = db1.getConnection();
Connection conn2 = db2.getConnection();
// XA事务管理器协调多个数据库资源
XAConnection xaConn1 = new XAConnection(conn1);
XAConnection xaConn2 = new XAConnection(conn2);
// 第一阶段:prepare
xaConn1.prepare("INSERT INTO orders ...");
xaConn2.prepare("UPDATE stock ...");
// 第二阶段:commit/rollback
if (allPreparedSuccess) {
xaConn1.commit();
xaConn2.commit();
} else {
xaConn1.rollback();
xaConn2.rollback();
}
特点:
- 数据库厂商实现(Oracle、MySQL、PostgreSQL等)。
- 使用XA接口标准。
- 协调者是数据库驱动程序或中间件。
1.2 中间件层2PC
java
// 消息队列的【事务消息】(如RocketMQ)
Message msg = new Message("order_topic", orderData);
// 第一阶段:发送half消息(半消息)
SendResult sendResult = producer.sendMessageInTransaction(msg, null);
// 事务执行...
// 第二阶段:commit或rollback(由事务监听器决定)
TransactionListener listener = new OrderTransactionListener();
特点:
- 消息中间件实现(RocketMQ、Kafka)。
- 协调者是消息队列服务端。
1.3 应用层2PC
java
// 1. TCC模式(应用层2PC)
public void createOrderTCC() {
// 第一阶段:try(资源预留)
orderService.tryCreate();
stockService.tryReduce();
// 第二阶段:confirm/cancel
if (allTrySuccess) {
orderService.confirm();
stockService.confirm();
} else {
orderService.cancel();
stockService.cancel();
}
}
// 2. Seata AT模式(应用层2PC)
@GlobalTransactional // 应用层注解
public void createOrder() {
// 第一阶段:执行业务SQL,生成undo log
orderMapper.insert(order);
// SQL被Seata DataSourceProxy拦截,自动生成回滚日志
// 第二阶段:根据协调者指令提交或回滚
// Seata框架自动完成
}
特点:
- 应用代码或框架实现(Seata、ShardingSphere等)。
- 协调者通常是独立服务(Seata-Server)。
2. TCC
TCC是一种典型的解决分布式事务问题的方案,主要用来解决【跨服务调用场景】下的分布式事务问题。
2.1 TCC基本概念
1. ++TCC本质上是一种【应用层】实现的 二阶段提交协议++ ,它的 执行流程 如下:
- 阶段一
- Try(尝试操作):负责资源的检查和预留。
- 阶段二
- Confirm(确认操作):负责提交操作,执行真正的业务。
- Cancel(取消操作):执行预留资源的取消,让资源回到初始状态。
- TCC 其 Try、Confirm、Cancel 3 个方法均由业务编码实现,故 TCC 可以被称为是服务化的资源管理器。
2. TCC的优缺点:
- 优点:TCC完全不依赖底层数据库,能够实现跨数据库、跨应用资源管理,可以提供给业务方更细粒度的控制。
- 缺点 :TCC是一种【侵入式】的分布式事务解决方案,需要业务系统自己实现 Try、Confirm、Cancel 三个操作,对业务系统有非常大的侵入性,设计相对复杂。
3. TCC适用场景:TCC模式是高性能分布式事务解决方案,适用于核心系统等对性能有很高要求的场景。
2.2 TCC要注意的问题
2.2.1 幂等问题
在分布式架构/微服务架构中,由于存在 服务器宕机、应用崩溃、网络异常(网络抖动、网络分区等)等问题,可能会出现 API接口调用超时 的情况。所以,为了保证接口能够正常执行,通常都会在 TCC方案中加入 超时重试机制。但是,重试机制有可能会导致数据不一致,所以,需要 TCC方案中的 Confirm 和 Cancel 实现【++幂等性++】。
也就是说,++在 TCC方案中,【数据最终一致性】基于 Confirm 和 Cancel 的【幂等性】++,保证事务最终完成确认或者取消。
- 幂等性相关文章 :浅谈接口幂等性、MQ消费幂等性,这篇文章会详尽的说明:① 什么是幂等性?② 幂等性解决了什么问题?③ 幂等性的几种实现方案。
在上面的这篇文章中提到一种 幂等性实现方案 ------ 【请求ID + 数据库】。我们在 TCC这种分布式事务解决方案下实现幂等性的方案与此类似,具体方案如下:
- 在【分支事务记录表】中增加事务的执行状态,每次执行分支事务的 Confirm 和 Cancel 阶段对应的方法时,都查询此事务的执行状态,以此判断事务的幂等性。
2.2.2 空回滚问题
1. 场景 :网络超时导致 Try未执行,但Cancel先执行了。
2. 时序:
- 主业务发起全局事务,生成 xid(全局事务ID)。
- 主业务调用参与者A的Try方法,但网络超时,导致调用失败。
- 主业务 认为Try失败,然后调用参与者A的Cancel方法。
- 但实际:参与者A的Try可能已经执行了,只是响应超时 或者 参与者A的Try根本没执行。
3. 问题:
- ① 如果Try执行了,那么Cancel正常执行业务逻辑就没有什么问题。
- ② 如果Try没有执行,那么Cancel就不可以执行回滚业务逻辑,因为执行回滚操作的话就会 ++【导致数据不一致】++,正确做法是什么都不做,我们把这种情况就叫做 【空回滚】。
4. 解决方案:
- ① 在主业务发起全局事务时,生成全局事务记录,并给全局事务记录生成一个全局唯一的ID(全局事务ID)。【这个全局事务ID会贯穿整个分布式事务的执行流程】。
- ② 再创建一张分支事务记录表,将全局事务ID和分支事务ID保存到分支事务表中。
- ③ 在执行 Try阶段 的方法(或接口)时,会给分支事务记录表中插入一条记录,其中包含全局事务ID和分支事务ID,表示 Try操作已经执行过了。
- ④ 当事务回滚执行 Cancel阶段 的方法(或接口)时,首先根据全局事务ID和分支事务ID查询分支事务记录表,如果存在Try阶段插入的数据,则执行正常的Cancel操作回滚事务,否则为++【空回滚】,即【不做任何操作(不执行实际业务回滚)】++** 。也就是说,【++Cancel方法 需要判断 Try是否执行过++】。
由此,我们可以知道 ------ ++TCC服务允许空回滚++。
2.2.3 悬挂问题
1. 场景 :Cancel先执行,然后Try才到达执行。
2. 时序:
- 主业务调用 Try(网络延迟,很久才到)。
- 主业务认为 Try超时(Try阶段超时可以认为失败,当然也可以重试),然后调用Cancel进行事务回滚。
- Cancel先到达并执行(可能是空回滚)。
- 然后Try请求才到达并执行。
3. 问题:
一个全局事务是包含多个分支事务的。假设现在有一个全局事务包含两个分支事务(A和B),执行流程如下:
- 事务协调者发送 Try请求给 分支事务A和B。
- 分支事务A收到Try请求后,成功执行了,并正确响应了 事务协调者。
- 分支事务B由于网络异常没有收到Try请求。
- 事务协调者 在超时时间内 只收到了 分支事务A的响应,没有收到分支事务B的响应。这个时候事务协调者认为全局事务执行失败,所以,给分支事务A和B 发出 cancel请求。
- 分支事务A收到cancel请求后,成功回滚了事务,并正确响应了 事务协调者。
- 分支事务B收到cancel请求后,发现并没有执行Try请求,所以执行了【空回滚】。
- 网络恢复正常,分支事务B收到了Try请求,【并正常执行了】 。问题就出现在这里了,别人(其他分支事务)都回滚了,你先执行了回滚(空回滚),然后又执行了Try,关键是你在执行Try后就再也收不到Cancel请求了,回不去了。换句话说就是,++别人都没动,就你动了!这就导致数据不一致了++。
"Cancel先执行,然后Try才到达执行",这就会 ++导致 Try阶段 预留的资源无法释放了,这种情况就叫【悬挂】++。
4. 解决方案 :
解决方案的思路就是:++如果 Cancel阶段的方法 已执行,那么就拒绝执行 Try阶段的方法++。
在【空回滚】的解决方案中:我们的做法是【什么都不做】。但是,为了防止悬挂问题的出现(防悬挂),就要干点啥了。所以,当发生空回滚的时候,即Cancel在执行的时候,根据全局事务ID和分支事务ID查询分支事务记录表,如果没查到数据(说明Try没有执行),这个时候的正确做法是 ------ 往分支事务记录表中插入一条状态为空回滚的记录(表中添加一个状态字段)。这样,当一阶段的Try后执行时,发现分支事务记录表中存在一条空回滚的记录(即,已有Cancel记录,拒绝执行Try(防悬挂))。
3. 完整的TCC分布式事务示例
下面是一个完整的TCC分布式事务示例,包含协调者服务、订单服务、库存服务和积分服务。
3.1 整体架构
项目结构:
tcc-distributed-transaction/
├── tcc-coordinator/ # 协调者服务(独立部署)
├── order-service/ # 订单服务(主业务)
├── stock-service/ # 库存服务(参与者1)
└── points-service/ # 积分服务(参与者2)
时序图大致如下:
主业务(TM) 协调者(TC) 参与者(RM)-库存服务 参与者(RM)-积分服务
| | | |
|---begin()-------->| | |
|<-----xid----------| | |
| | | |
|---prepareBranch(xid,"stock")--------->| |
|<-----stockBranchId--------------------| |
| | | |
|---try(xid,stockBranchId)------------->| |
| | |--1. 防悬挂检查---->|
| | |--2. 执行业务------>|
| | |--3. 记录状态------>|
|<-----成功/失败-------------------------| |
| | | |
|---prepareBranch(xid,"points")-------->| |
|<-----pointsBranchId-------------------| |
| | | |
|---try(xid,pointsBranchId)--------------------------------->|
| | | |
|<-----成功/失败---------------------------------------------|
| | | |
|---commit/rollback-------------------->| |
| |---cancel(xid,stockBranchId)----------->|
| | |--1. 空回滚判断---->|
| | |--2. 执行回滚------>|
| | | |
| |---cancel(xid,pointsBranchId)---------->|
3.2 协调者服务(独立服务)
3.2.1 数据库表结构
sql
-- 全局事务表
CREATE TABLE global_transaction (
xid VARCHAR(128) PRIMARY KEY,
status TINYINT NOT NULL COMMENT '0-开始,1-进行中,2-已提交,3-已回滚,4-失败',
application_id VARCHAR(64) COMMENT '应用ID',
transaction_name VARCHAR(128) COMMENT '事务名称',
timeout INT DEFAULT 60000 COMMENT '超时时间(ms)',
begin_time BIGINT COMMENT '开始时间',
end_time BIGINT COMMENT '结束时间',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_status_time (status, begin_time)
);
-- 分支事务表
CREATE TABLE branch_transaction (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
xid VARCHAR(128) NOT NULL,
branch_id VARCHAR(128) NOT NULL UNIQUE,
service_name VARCHAR(64) NOT NULL,
action_name VARCHAR(64) NOT NULL COMMENT '操作名称',
status TINYINT NOT NULL COMMENT '0-初始化,1-Try执行中,2-Try完成,3-Confirm完成,4-Cancel完成,5-空回滚',
params TEXT COMMENT '业务参数(JSON)',
error_message TEXT COMMENT '错误信息',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_xid_branch (xid, branch_id),
INDEX idx_xid_status (xid, status)
);
3.2.2 CoordinatorService.java
java
package com.example.tcccoordinator.service;
import com.example.tcccoordinator.entity.*;
import com.example.tcccoordinator.repository.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
// 协调者 service层
@Service
public class CoordinatorService {
@Autowired
private GlobalTransactionRepository globalTransactionRepo;
@Autowired
private BranchTransactionRepository branchTransactionRepo;
// 内存中的事务状态,用于快速查询
private final Map<String, GlobalTransaction> globalTxCache = new ConcurrentHashMap<>();
private final Map<String, BranchTransaction> branchTxCache = new ConcurrentHashMap<>();
/**
* 开始全局事务
*/
@Transactional
public String beginGlobalTransaction(String applicationId, String transactionName) {
String xid = "TX" + UUID.randomUUID().toString().replace("-", "");
GlobalTransaction globalTx = new GlobalTransaction();
globalTx.setXid(xid);
globalTx.setStatus(GlobalStatus.BEGIN);
globalTx.setApplicationId(applicationId);
globalTx.setTransactionName(transactionName);
globalTx.setTimeout(60000); // 60秒超时
globalTx.setBeginTime(System.currentTimeMillis());
globalTransactionRepo.save(globalTx);
globalTxCache.put(xid, globalTx);
return xid;
}
/**
* 注册分支事务
*/
@Transactional
public String registerBranch(String xid, String serviceName, String actionName) {
// 检查全局事务是否存在
GlobalTransaction globalTx = globalTransactionRepo.findByXid(xid);
if (globalTx == null) {
throw new RuntimeException("全局事务不存在: " + xid);
}
if (globalTx.getStatus() == GlobalStatus.ROLLBACKED ||
globalTx.getStatus() == GlobalStatus.COMMITTED) {
throw new RuntimeException("全局事务已结束: " + xid);
}
// 生成分支事务ID
String branchId = xid + ":" + serviceName + ":" + actionName + ":" + UUID.randomUUID().toString().substring(0, 8);
// 创建分支事务记录
BranchTransaction branchTx = new BranchTransaction();
branchTx.setXid(xid);
branchTx.setBranchId(branchId);
branchTx.setServiceName(serviceName);
branchTx.setActionName(actionName);
branchTx.setStatus(BranchStatus.INIT);
branchTx.setCreatedAt(new Date());
branchTransactionRepo.save(branchTx);
branchTxCache.put(branchId, branchTx);
// 更新全局事务状态
globalTx.setStatus(GlobalStatus.ACTIVE);
globalTx.setUpdatedAt(new Date());
globalTransactionRepo.save(globalTx);
return branchId;
}
/**
* 报告分支事务Try执行结果
*/
@Transactional
public void reportBranchTryResult(String xid, String branchId, boolean success, String errorMsg) {
BranchTransaction branchTx = branchTransactionRepo.findByBranchId(branchId);
if (branchTx == null) {
throw new RuntimeException("分支事务不存在: " + branchId);
}
if (success) {
branchTx.setStatus(BranchStatus.TRIED);
} else {
branchTx.setStatus(BranchStatus.TRY_FAILED);
branchTx.setErrorMessage(errorMsg);
}
branchTx.setUpdatedAt(new Date());
branchTransactionRepo.save(branchTx);
branchTxCache.put(branchId, branchTx);
}
/**
* 提交全局事务
*/
@Transactional
public boolean commitGlobalTransaction(String xid) {
GlobalTransaction globalTx = globalTransactionRepo.findByXid(xid);
if (globalTx == null) {
throw new RuntimeException("全局事务不存在: " + xid);
}
// 检查所有分支事务是否都Try成功
List<BranchTransaction> branches = branchTransactionRepo.findByXid(xid);
for (BranchTransaction branch : branches) {
if (branch.getStatus() != BranchStatus.TRIED) {
// 有分支没有成功,不能提交
globalTx.setStatus(GlobalStatus.COMMIT_FAILED);
globalTx.setEndTime(System.currentTimeMillis());
globalTransactionRepo.save(globalTx);
return false;
}
}
// 所有分支Try成功,可以提交
globalTx.setStatus(GlobalStatus.COMMITTING);
globalTransactionRepo.save(globalTx);
// TODO: 这里应该异步调用所有分支的Confirm方法
// 实际中会有专门的线程池来处理
globalTx.setStatus(GlobalStatus.COMMITTED);
globalTx.setEndTime(System.currentTimeMillis());
globalTransactionRepo.save(globalTx);
return true;
}
/**
* 回滚全局事务
*/
@Transactional
public boolean rollbackGlobalTransaction(String xid) {
GlobalTransaction globalTx = globalTransactionRepo.findByXid(xid);
if (globalTx == null) {
throw new RuntimeException("全局事务不存在: " + xid);
}
globalTx.setStatus(GlobalStatus.ROLLBACKING);
globalTransactionRepo.save(globalTx);
// TODO: 这里应该异步调用所有分支的Cancel方法
globalTx.setStatus(GlobalStatus.ROLLBACKED);
globalTx.setEndTime(System.currentTimeMillis());
globalTransactionRepo.save(globalTx);
return true;
}
/**
* 检查是否允许空回滚
*/
public boolean canEmptyRollback(String xid, String branchId) {
BranchTransaction branchTx = branchTransactionRepo.findByBranchId(branchId);
if (branchTx == null) {
// 分支记录不存在,可能是协调者重启数据丢失
// 这种情况让参与者根据本地记录判断
return true;
}
// 如果分支状态是INIT(Try没执行过),允许空回滚
return branchTx.getStatus() == BranchStatus.INIT;
}
/**
* 检查是否允许执行Try(防悬挂)
*/
public boolean canTry(String xid, String branchId) {
BranchTransaction branchTx = branchTransactionRepo.findByBranchId(branchId);
if (branchTx == null) {
// 分支记录不存在,可能是协调者重启
return true;
}
// 如果分支已经被取消或空回滚,不允许再执行Try
return !(branchTx.getStatus() == BranchStatus.CANCELLED ||
branchTx.getStatus() == BranchStatus.EMPTY_ROLLBACKED);
}
}
3.2.3 CoordinatorController.java
java
package com.example.tcccoordinator.controller;
import com.example.tcccoordinator.service.CoordinatorService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
// 协调者 API接口层
@RestController
@RequestMapping("/coordinator")
public class CoordinatorController {
@Autowired
private CoordinatorService coordinatorService;
/**
* 开始全局事务
*/
@PostMapping("/begin")
public Map<String, Object> beginGlobalTransaction(
@RequestParam String applicationId,
@RequestParam String transactionName) {
String xid = coordinatorService.beginGlobalTransaction(applicationId, transactionName);
return Map.of("success", true, "xid", xid);
}
/**
* 注册分支事务
*/
@PostMapping("/register-branch")
public Map<String, Object> registerBranch(
@RequestParam String xid,
@RequestParam String serviceName,
@RequestParam String actionName) {
String branchId = coordinatorService.registerBranch(xid, serviceName, actionName);
return Map.of("success", true, "branchId", branchId);
}
/**
* 报告分支事务Try执行结果
*/
@PostMapping("/report-try")
public Map<String, Object> reportBranchTryResult(
@RequestParam String xid,
@RequestParam String branchId,
@RequestParam boolean success,
@RequestParam(required = false) String errorMsg) {
coordinatorService.reportBranchTryResult(xid, branchId, success, errorMsg);
return Map.of("success", true);
}
/**
* 提交全局事务
*/
@PostMapping("/commit")
public Map<String, Object> commitGlobalTransaction(@RequestParam String xid) {
boolean result = coordinatorService.commitGlobalTransaction(xid);
return Map.of("success", result);
}
/**
* 回滚全局事务
*/
@PostMapping("/rollback")
public Map<String, Object> rollbackGlobalTransaction(@RequestParam String xid) {
boolean result = coordinatorService.rollbackGlobalTransaction(xid);
return Map.of("success", result);
}
/**
* 检查是否允许空回滚
*/
@GetMapping("/can-empty-rollback")
public Map<String, Object> canEmptyRollback(
@RequestParam String xid,
@RequestParam String branchId) {
boolean result = coordinatorService.canEmptyRollback(xid, branchId);
return Map.of("canEmptyRollback", result);
}
/**
* 检查是否允许执行Try(防悬挂)
*/
@GetMapping("/can-try")
public Map<String, Object> canTry(
@RequestParam String xid,
@RequestParam String branchId) {
boolean result = coordinatorService.canTry(xid, branchId);
return Map.of("canTry", result);
}
}
3.3 订单服务(主业务)
3.3.1 OrderService.java
java
package com.example.orderservice.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
import java.util.HashMap;
@Service
public class OrderService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private OrderRepository orderRepository;
// 协调者服务地址
private static final String COORDINATOR_URL = "http://localhost:8080/coordinator";
/**
* 创建订单(TCC分布式事务)
*/
public Map<String, Object> createOrder(String userId, String productId, int quantity) {
String xid = null;
String stockBranchId = null;
String pointsBranchId = null;
try {
// 1. 开始全局事务
xid = beginGlobalTransaction("order-service", "create-order");
// 2. 注册库存分支事务
stockBranchId = registerBranch(xid, "stock-service", "reduce-stock");
// 3. 调用库存服务Try
boolean stockTryResult = callStockTry(xid, stockBranchId, productId, quantity);
if (!stockTryResult) {
rollbackGlobalTransaction(xid);
return Map.of("success", false, "message", "库存预留失败");
}
// 4. 创建订单(本地事务)
Order order = createLocalOrder(userId, productId, quantity);
// 5. 注册积分分支事务
pointsBranchId = registerBranch(xid, "points-service", "add-points");
// 6. 调用积分服务Try
boolean pointsTryResult = callPointsTry(xid, pointsBranchId, userId, quantity * 10);
if (!pointsTryResult) {
rollbackGlobalTransaction(xid);
return Map.of("success", false, "message", "积分预留失败");
}
// 7. 提交全局事务
boolean commitResult = commitGlobalTransaction(xid);
if (commitResult) {
return Map.of("success", true, "orderId", order.getId(), "message", "订单创建成功");
} else {
// 提交失败,需要手动补偿
rollbackGlobalTransaction(xid);
return Map.of("success", false, "message", "订单提交失败");
}
} catch (Exception e) {
// 发生异常,回滚事务
if (xid != null) {
rollbackGlobalTransaction(xid);
}
return Map.of("success", false, "message", "系统异常: " + e.getMessage());
}
}
private String beginGlobalTransaction(String applicationId, String transactionName) {
String url = COORDINATOR_URL + "/begin?applicationId=" + applicationId +
"&transactionName=" + transactionName;
Map response = restTemplate.postForObject(url, null, Map.class);
return (String) response.get("xid");
}
private String registerBranch(String xid, String serviceName, String actionName) {
String url = COORDINATOR_URL + "/register-branch?xid=" + xid +
"&serviceName=" + serviceName + "&actionName=" + actionName;
Map response = restTemplate.postForObject(url, null, Map.class);
return (String) response.get("branchId");
}
private boolean callStockTry(String xid, String branchId, String productId, int quantity) {
try {
String url = "http://localhost:8081/stock/try";
Map<String, Object> params = new HashMap<>();
params.put("xid", xid);
params.put("branchId", branchId);
params.put("productId", productId);
params.put("quantity", quantity);
Map response = restTemplate.postForObject(url, params, Map.class);
return (Boolean) response.get("success");
} catch (Exception e) {
// 调用失败,报告给协调者
reportTryResult(xid, branchId, false, e.getMessage());
return false;
}
}
private boolean callPointsTry(String xid, String branchId, String userId, int points) {
try {
String url = "http://localhost:8082/points/try";
Map<String, Object> params = new HashMap<>();
params.put("xid", xid);
params.put("branchId", branchId);
params.put("userId", userId);
params.put("points", points);
Map response = restTemplate.postForObject(url, params, Map.class);
return (Boolean) response.get("success");
} catch (Exception e) {
// 调用失败,报告给协调者
reportTryResult(xid, branchId, false, e.getMessage());
return false;
}
}
private Order createLocalOrder(String userId, String productId, int quantity) {
Order order = new Order();
order.setId("ORD" + System.currentTimeMillis());
order.setUserId(userId);
order.setProductId(productId);
order.setQuantity(quantity);
order.setStatus("CREATED");
order.setCreateTime(new Date());
orderRepository.save(order);
return order;
}
private void reportTryResult(String xid, String branchId, boolean success, String errorMsg) {
String url = COORDINATOR_URL + "/report-try?xid=" + xid +
"&branchId=" + branchId + "&success=" + success;
if (errorMsg != null) {
url += "&errorMsg=" + errorMsg;
}
restTemplate.postForObject(url, null, Map.class);
}
private boolean commitGlobalTransaction(String xid) {
String url = COORDINATOR_URL + "/commit?xid=" + xid;
Map response = restTemplate.postForObject(url, null, Map.class);
return (Boolean) response.get("success");
}
private void rollbackGlobalTransaction(String xid) {
String url = COORDINATOR_URL + "/rollback?xid=" + xid;
restTemplate.postForObject(url, null, Map.class);
}
}
3.3.2 OrderController.java
java
package com.example.orderservice.controller;
import com.example.orderservice.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/create")
public Map<String, Object> createOrder(
@RequestParam String userId,
@RequestParam String productId,
@RequestParam(defaultValue = "1") int quantity) {
return orderService.createOrder(userId, productId, quantity);
}
}
3.4 库存服务(参与者1)
3.4.1 StockService.java
java
package com.example.stockservice.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import java.util.Date;
import java.util.Map;
@Service
public class StockService {
@Autowired
private StockRepository stockRepository;
@Autowired
private BranchTransactionRepository branchTxRepository;
@Autowired
private RestTemplate restTemplate;
private static final String COORDINATOR_URL = "http://localhost:8080/coordinator";
/**
* Try阶段:预留库存
*/
@Transactional
public boolean tryReduceStock(String xid, String branchId, String productId, int quantity) {
try {
// 1. 防悬挂检查:查询本地是否有Cancel记录
BranchTransaction localBranch = branchTxRepository.findByXidAndBranchId(xid, branchId);
if (localBranch != null &&
(localBranch.getStatus() == BranchStatus.CANCELLED ||
localBranch.getStatus() == BranchStatus.EMPTY_ROLLBACKED)) {
// 已有Cancel记录,拒绝执行Try(防悬挂)
log.warn("Try rejected due to existing cancel record, xid: {}, branchId: {}", xid, branchId);
return false;
}
// 2. 检查协调者是否允许执行Try
boolean canTry = checkCanTryFromCoordinator(xid, branchId);
if (!canTry) {
return false;
}
// 3. 检查库存是否足够
Stock stock = stockRepository.findByProductId(productId);
if (stock == null) {
throw new RuntimeException("商品不存在: " + productId);
}
if (stock.getAvailable() < quantity) {
throw new RuntimeException("库存不足,可用: " + stock.getAvailable() + ", 需要: " + quantity);
}
// 4. 冻结库存(预留)
int newAvailable = stock.getAvailable() - quantity;
int newFrozen = stock.getFrozen() + quantity;
stock.setAvailable(newAvailable);
stock.setFrozen(newFrozen);
stock.setUpdateTime(new Date());
stockRepository.save(stock);
// 5. 保存本地分支事务记录
if (localBranch == null) {
localBranch = new BranchTransaction();
localBranch.setXid(xid);
localBranch.setBranchId(branchId);
localBranch.setServiceName("stock-service");
localBranch.setActionName("reduce-stock");
localBranch.setStatus(BranchStatus.TRYING);
localBranch.setParams("{\"productId\":\"" + productId + "\",\"quantity\":" + quantity + "}");
localBranch.setCreateTime(new Date());
} else {
localBranch.setStatus(BranchStatus.TRYING);
localBranch.setParams("{\"productId\":\"" + productId + "\",\"quantity\":" + quantity + "}");
}
localBranch.setUpdateTime(new Date());
branchTxRepository.save(localBranch);
// 6. 报告Try结果给协调者
reportTryResultToCoordinator(xid, branchId, true, null);
// 7. 更新本地状态为TRIED
localBranch.setStatus(BranchStatus.TRIED);
localBranch.setUpdateTime(new Date());
branchTxRepository.save(localBranch);
return true;
} catch (Exception e) {
// 执行失败,报告给协调者
reportTryResultToCoordinator(xid, branchId, false, e.getMessage());
// 删除或更新本地记录
BranchTransaction localBranch = branchTxRepository.findByXidAndBranchId(xid, branchId);
if (localBranch != null) {
localBranch.setStatus(BranchStatus.TRY_FAILED);
localBranch.setErrorMessage(e.getMessage());
localBranch.setUpdateTime(new Date());
branchTxRepository.save(localBranch);
}
throw e;
}
}
/**
* Confirm阶段:实际扣减库存
*/
@Transactional
public boolean confirmReduceStock(String xid, String branchId) {
BranchTransaction localBranch = branchTxRepository.findByXidAndBranchId(xid, branchId);
if (localBranch == null) {
log.error("分支事务记录不存在, xid: {}, branchId: {}", xid, branchId);
return false;
}
// 幂等性检查:如果已经Confirm过了,直接返回成功
if (localBranch.getStatus() == BranchStatus.CONFIRMED) {
return true;
}
try {
// 解析参数
String params = localBranch.getParams();
// 解析JSON获取productId和quantity...
// 实际扣减冻结的库存
Stock stock = stockRepository.findByProductId(productId);
if (stock != null) {
stock.setFrozen(stock.getFrozen() - quantity);
stock.setUpdateTime(new Date());
stockRepository.save(stock);
}
// 更新本地状态
localBranch.setStatus(BranchStatus.CONFIRMED);
localBranch.setUpdateTime(new Date());
branchTxRepository.save(localBranch);
return true;
} catch (Exception e) {
log.error("Confirm失败, xid: {}, branchId: {}, error: {}", xid, branchId, e.getMessage());
return false;
}
}
/**
* Cancel阶段:回滚预留的库存
*/
@Transactional
public boolean cancelReduceStock(String xid, String branchId) {
// 1. 查询本地记录
BranchTransaction localBranch = branchTxRepository.findByXidAndBranchId(xid, branchId);
// 2. 空回滚判断
if (localBranch == null || localBranch.getStatus() == BranchStatus.INIT) {
// 没有执行过Try,执行空回滚
// 检查协调者是否允许空回滚
boolean canEmptyRollback = checkCanEmptyRollbackFromCoordinator(xid, branchId);
if (!canEmptyRollback) {
// 协调者不允许空回滚,可能是Try正在执行
// 等待一段时间重试
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
localBranch = branchTxRepository.findByXidAndBranchId(xid, branchId);
if (localBranch != null && localBranch.getStatus() != BranchStatus.INIT) {
// 现在有记录了,不是空回滚了
return doRealCancel(xid, branchId, localBranch);
}
}
// 执行空回滚:创建一条空回滚记录
if (localBranch == null) {
localBranch = new BranchTransaction();
localBranch.setXid(xid);
localBranch.setBranchId(branchId);
localBranch.setServiceName("stock-service");
localBranch.setActionName("reduce-stock");
localBranch.setStatus(BranchStatus.EMPTY_ROLLBACKED);
localBranch.setCreateTime(new Date());
} else {
localBranch.setStatus(BranchStatus.EMPTY_ROLLBACKED);
}
localBranch.setUpdateTime(new Date());
branchTxRepository.save(localBranch);
log.info("执行空回滚, xid: {}, branchId: {}", xid, branchId);
return true;
}
// 3. 正常回滚
return doRealCancel(xid, branchId, localBranch);
}
private boolean doRealCancel(String xid, String branchId, BranchTransaction localBranch) {
// 幂等性检查:如果已经Cancel过了,直接返回成功
if (localBranch.getStatus() == BranchStatus.CANCELLED) {
return true;
}
try {
// 解析参数
String params = localBranch.getParams();
// 解析JSON获取productId和quantity...
// 释放冻结的库存
Stock stock = stockRepository.findByProductId(productId);
if (stock != null) {
stock.setAvailable(stock.getAvailable() + quantity);
stock.setFrozen(stock.getFrozen() - quantity);
stock.setUpdateTime(new Date());
stockRepository.save(stock);
}
// 更新本地状态
localBranch.setStatus(BranchStatus.CANCELLED);
localBranch.setUpdateTime(new Date());
branchTxRepository.save(localBranch);
return true;
} catch (Exception e) {
log.error("Cancel失败, xid: {}, branchId: {}, error: {}", xid, branchId, e.getMessage());
return false;
}
}
private boolean checkCanTryFromCoordinator(String xid, String branchId) {
try {
String url = COORDINATOR_URL + "/can-try?xid=" + xid + "&branchId=" + branchId;
Map response = restTemplate.getForObject(url, Map.class);
return (Boolean) response.get("canTry");
} catch (Exception e) {
// 调用协调者失败,保守策略:允许执行Try
log.warn("调用协调者失败,允许执行Try, xid: {}, branchId: {}", xid, branchId);
return true;
}
}
private boolean checkCanEmptyRollbackFromCoordinator(String xid, String branchId) {
try {
String url = COORDINATOR_URL + "/can-empty-rollback?xid=" + xid + "&branchId=" + branchId;
Map response = restTemplate.getForObject(url, Map.class);
return (Boolean) response.get("canEmptyRollback");
} catch (Exception e) {
// 调用协调者失败,保守策略:允许空回滚
log.warn("调用协调者失败,允许空回滚, xid: {}, branchId: {}", xid, branchId);
return true;
}
}
private void reportTryResultToCoordinator(String xid, String branchId, boolean success, String errorMsg) {
try {
String url = COORDINATOR_URL + "/report-try?xid=" + xid +
"&branchId=" + branchId + "&success=" + success;
if (errorMsg != null) {
url += "&errorMsg=" + errorMsg;
}
restTemplate.postForObject(url, null, Map.class);
} catch (Exception e) {
log.error("报告Try结果失败, xid: {}, branchId: {}", xid, branchId, e);
}
}
}
3.4.2 StockController.java
java
package com.example.stockservice.controller;
import com.example.stockservice.service.StockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/stock")
public class StockController {
@Autowired
private StockService stockService;
@PostMapping("/try")
public Map<String, Object> tryReduceStock(
@RequestParam String xid,
@RequestParam String branchId,
@RequestParam String productId,
@RequestParam int quantity) {
try {
boolean result = stockService.tryReduceStock(xid, branchId, productId, quantity);
return Map.of("success", result);
} catch (Exception e) {
return Map.of("success", false, "message", e.getMessage());
}
}
@PostMapping("/confirm")
public Map<String, Object> confirmReduceStock(
@RequestParam String xid,
@RequestParam String branchId) {
boolean result = stockService.confirmReduceStock(xid, branchId);
return Map.of("success", result);
}
@PostMapping("/cancel")
public Map<String, Object> cancelReduceStock(
@RequestParam String xid,
@RequestParam String branchId) {
boolean result = stockService.cancelReduceStock(xid, branchId);
return Map.of("success", result);
}
}
3.5 积分服务(参与者2)
3.5.1 PointsService.java
java
package com.example.pointsservice.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import java.util.Date;
import java.util.Map;
@Service
public class PointsService {
@Autowired
private PointsRepository pointsRepository;
@Autowired
private BranchTransactionRepository branchTxRepository;
@Autowired
private RestTemplate restTemplate;
private static final String COORDINATOR_URL = "http://localhost:8080/coordinator";
/**
* Try阶段:预增加积分
*/
@Transactional
public boolean tryAddPoints(String xid, String branchId, String userId, int points) {
try {
// 1. 防悬挂检查
BranchTransaction localBranch = branchTxRepository.findByXidAndBranchId(xid, branchId);
if (localBranch != null &&
(localBranch.getStatus() == BranchStatus.CANCELLED ||
localBranch.getStatus() == BranchStatus.EMPTY_ROLLBACKED)) {
log.warn("Try rejected due to existing cancel record, xid: {}, branchId: {}", xid, branchId);
return false;
}
// 2. 检查协调者是否允许执行Try
boolean canTry = checkCanTryFromCoordinator(xid, branchId);
if (!canTry) {
return false;
}
// 3. 查找用户积分账户
UserPoints userPoints = pointsRepository.findByUserId(userId);
if (userPoints == null) {
// 用户不存在,创建新账户
userPoints = new UserPoints();
userPoints.setUserId(userId);
userPoints.setTotalPoints(0);
userPoints.setAvailablePoints(0);
userPoints.setFrozenPoints(0);
userPoints.setCreateTime(new Date());
}
// 4. 预增加积分(冻结)
userPoints.setFrozenPoints(userPoints.getFrozenPoints() + points);
userPoints.setUpdateTime(new Date());
pointsRepository.save(userPoints);
// 5. 保存本地分支事务记录
if (localBranch == null) {
localBranch = new BranchTransaction();
localBranch.setXid(xid);
localBranch.setBranchId(branchId);
localBranch.setServiceName("points-service");
localBranch.setActionName("add-points");
localBranch.setStatus(BranchStatus.TRYING);
localBranch.setParams("{\"userId\":\"" + userId + "\",\"points\":" + points + "}");
localBranch.setCreateTime(new Date());
} else {
localBranch.setStatus(BranchStatus.TRYING);
localBranch.setParams("{\"userId\":\"" + userId + "\",\"points\":" + points + "}");
}
localBranch.setUpdateTime(new Date());
branchTxRepository.save(localBranch);
// 6. 报告Try结果给协调者
reportTryResultToCoordinator(xid, branchId, true, null);
// 7. 更新本地状态为TRIED
localBranch.setStatus(BranchStatus.TRIED);
localBranch.setUpdateTime(new Date());
branchTxRepository.save(localBranch);
return true;
} catch (Exception e) {
reportTryResultToCoordinator(xid, branchId, false, e.getMessage());
BranchTransaction localBranch = branchTxRepository.findByXidAndBranchId(xid, branchId);
if (localBranch != null) {
localBranch.setStatus(BranchStatus.TRY_FAILED);
localBranch.setErrorMessage(e.getMessage());
localBranch.setUpdateTime(new Date());
branchTxRepository.save(localBranch);
}
throw e;
}
}
/**
* Confirm阶段:实际增加积分
*/
@Transactional
public boolean confirmAddPoints(String xid, String branchId) {
BranchTransaction localBranch = branchTxRepository.findByXidAndBranchId(xid, branchId);
if (localBranch == null) {
log.error("分支事务记录不存在, xid: {}, branchId: {}", xid, branchId);
return false;
}
if (localBranch.getStatus() == BranchStatus.CONFIRMED) {
return true; // 幂等
}
try {
// 解析参数
String params = localBranch.getParams();
// 解析JSON获取userId和points...
// 实际增加积分
UserPoints userPoints = pointsRepository.findByUserId(userId);
if (userPoints != null) {
userPoints.setTotalPoints(userPoints.getTotalPoints() + points);
userPoints.setAvailablePoints(userPoints.getAvailablePoints() + points);
userPoints.setFrozenPoints(userPoints.getFrozenPoints() - points);
userPoints.setUpdateTime(new Date());
pointsRepository.save(userPoints);
}
// 更新本地状态
localBranch.setStatus(BranchStatus.CONFIRMED);
localBranch.setUpdateTime(new Date());
branchTxRepository.save(localBranch);
return true;
} catch (Exception e) {
log.error("Confirm失败, xid: {}, branchId: {}, error: {}", xid, branchId, e.getMessage());
return false;
}
}
/**
* Cancel阶段:回滚预增加的积分
*/
@Transactional
public boolean cancelAddPoints(String xid, String branchId) {
BranchTransaction localBranch = branchTxRepository.findByXidAndBranchId(xid, branchId);
// 空回滚判断
if (localBranch == null || localBranch.getStatus() == BranchStatus.INIT) {
// 检查协调者是否允许空回滚
boolean canEmptyRollback = checkCanEmptyRollbackFromCoordinator(xid, branchId);
if (!canEmptyRollback) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
localBranch = branchTxRepository.findByXidAndBranchId(xid, branchId);
if (localBranch != null && localBranch.getStatus() != BranchStatus.INIT) {
return doRealCancel(xid, branchId, localBranch);
}
}
// 执行空回滚
if (localBranch == null) {
localBranch = new BranchTransaction();
localBranch.setXid(xid);
localBranch.setBranchId(branchId);
localBranch.setServiceName("points-service");
localBranch.setActionName("add-points");
localBranch.setStatus(BranchStatus.EMPTY_ROLLBACKED);
localBranch.setCreateTime(new Date());
} else {
localBranch.setStatus(BranchStatus.EMPTY_ROLLBACKED);
}
localBranch.setUpdateTime(new Date());
branchTxRepository.save(localBranch);
log.info("执行空回滚, xid: {}, branchId: {}", xid, branchId);
return true;
}
// 正常回滚
return doRealCancel(xid, branchId, localBranch);
}
private boolean doRealCancel(String xid, String branchId, BranchTransaction localBranch) {
if (localBranch.getStatus() == BranchStatus.CANCELLED) {
return true; // 幂等
}
try {
// 解析参数
String params = localBranch.getParams();
// 解析JSON获取userId和points...
// 释放冻结的积分
UserPoints userPoints = pointsRepository.findByUserId(userId);
if (userPoints != null) {
userPoints.setFrozenPoints(userPoints.getFrozenPoints() - points);
userPoints.setUpdateTime(new Date());
pointsRepository.save(userPoints);
}
// 更新本地状态
localBranch.setStatus(BranchStatus.CANCELLED);
localBranch.setUpdateTime(new Date());
branchTxRepository.save(localBranch);
return true;
} catch (Exception e) {
log.error("Cancel失败, xid: {}, branchId: {}, error: {}", xid, branchId, e.getMessage());
return false;
}
}
private boolean checkCanTryFromCoordinator(String xid, String branchId) {
try {
String url = COORDINATOR_URL + "/can-try?xid=" + xid + "&branchId=" + branchId;
Map response = restTemplate.getForObject(url, Map.class);
return (Boolean) response.get("canTry");
} catch (Exception e) {
log.warn("调用协调者失败,允许执行Try, xid: {}, branchId: {}", xid, branchId);
return true;
}
}
private boolean checkCanEmptyRollbackFromCoordinator(String xid, String branchId) {
try {
String url = COORDINATOR_URL + "/can-empty-rollback?xid=" + xid + "&branchId=" + branchId;
Map response = restTemplate.getForObject(url, Map.class);
return (Boolean) response.get("canEmptyRollback");
} catch (Exception e) {
log.warn("调用协调者失败,允许空回滚, xid: {}, branchId: {}", xid, branchId);
return true;
}
}
private void reportTryResultToCoordinator(String xid, String branchId, boolean success, String errorMsg) {
try {
String url = COORDINATOR_URL + "/report-try?xid=" + xid +
"&branchId=" + branchId + "&success=" + success;
if (errorMsg != null) {
url += "&errorMsg=" + errorMsg;
}
restTemplate.postForObject(url, null, Map.class);
} catch (Exception e) {
log.error("报告Try结果失败, xid: {}, branchId: {}", xid, branchId, e);
}
}
}
3.5.2 PointsController.java
java
package com.example.pointsservice.controller;
import com.example.pointsservice.service.PointsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/points")
public class PointsController {
@Autowired
private PointsService pointsService;
@PostMapping("/try")
public Map<String, Object> tryAddPoints(
@RequestParam String xid,
@RequestParam String branchId,
@RequestParam String userId,
@RequestParam int points) {
try {
boolean result = pointsService.tryAddPoints(xid, branchId, userId, points);
return Map.of("success", result);
} catch (Exception e) {
return Map.of("success", false, "message", e.getMessage());
}
}
@PostMapping("/confirm")
public Map<String, Object> confirmAddPoints(
@RequestParam String xid,
@RequestParam String branchId) {
boolean result = pointsService.confirmAddPoints(xid, branchId);
return Map.of("success", result);
}
@PostMapping("/cancel")
public Map<String, Object> cancelAddPoints(
@RequestParam String xid,
@RequestParam String branchId) {
boolean result = pointsService.cancelAddPoints(xid, branchId);
return Map.of("success", result);
}
}
3.6 配置文件和依赖
3.6.1 公共依赖(pom.xml)
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
3.6.2 协调者服务配置(application.yml)
yaml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/tcc_coordinator
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
3.6.3 订单服务配置(application.yml)
yaml
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/order_db
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
coordinator:
url: http://localhost:8080/coordinator
3.6.4 库存服务配置(application.yml)
yaml
server:
port: 8082
spring:
datasource:
url: jdbc:mysql://localhost:3306/stock_db
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
coordinator:
url: http://localhost:8080/coordinator
3.6.5 积分服务配置(application.yml)
yaml
server:
port: 8083
spring:
datasource:
url: jdbc:mysql://localhost:3306/points_db
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
coordinator:
url: http://localhost:8080/coordinator
3.7 运行流程
-
启动协调者服务(端口8080)
-
启动订单服务(端口8081)
-
启动库存服务(端口8082)
-
启动积分服务(端口8083)
-
调用下单接口:
POST http://localhost:8081/order/create?userId=123&productId=P001&quantity=2 -
执行流程:
- 订单服务调用协调者开始全局事务,获取xid
- 订单服务调用协调者注册库存分支,获取stockBranchId
- 订单服务调用库存服务的try接口。
- 库存服务执行防悬挂检查,预留库存。
- 订单服务创建本地订单。
- 订单服务调用协调者注册积分分支,获取pointsBranchId
- 订单服务调用积分服务的try接口。
- 积分服务执行防悬挂检查,预增加积分。
- 所有try成功,订单服务调用协调者提交事务。
- 协调者异步调用各服务的confirm接口。
3.8 关键点总结
- 协调者是独立服务:负责全局事务和分支事务的状态管理。
- branchId由协调者生成:在调用Try之前就生成,确保Cancel时也有branchId可用。
- ++空回滚机制++:Cancel时检查是否有Try记录,没有则执行【空回滚】。
- ++防悬挂机制++:Try时检查是否有Cancel记录,有则拒绝执行Try。
- ++幂等性设计++:所有操作都要支持【重试】。
- 本地事务记录:每个参与者都需要保存本地分支事务状态。
这个完整实现包含了TCC分布式事务的所有核心组件,当然,大家还可以在此基础上进行扩展和优化。实际生产环境中还需要考虑更多因素,如超时重试、异步消息、补偿任务、监控告警等。