TCC—最终一致性分布式事务方案及【案例】

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 的【幂等性】++,保证事务最终完成确认或者取消。

在上面的这篇文章中提到一种 幂等性实现方案 ------ 【请求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 运行流程

  1. 启动协调者服务(端口8080)

  2. 启动订单服务(端口8081)

  3. 启动库存服务(端口8082)

  4. 启动积分服务(端口8083)

  5. 调用下单接口

    复制代码
    POST http://localhost:8081/order/create?userId=123&productId=P001&quantity=2
  6. 执行流程

    • 订单服务调用协调者开始全局事务,获取xid
    • 订单服务调用协调者注册库存分支,获取stockBranchId
    • 订单服务调用库存服务的try接口。
    • 库存服务执行防悬挂检查,预留库存。
    • 订单服务创建本地订单。
    • 订单服务调用协调者注册积分分支,获取pointsBranchId
    • 订单服务调用积分服务的try接口。
    • 积分服务执行防悬挂检查,预增加积分。
    • 所有try成功,订单服务调用协调者提交事务。
    • 协调者异步调用各服务的confirm接口。

3.8 关键点总结

  1. 协调者是独立服务:负责全局事务和分支事务的状态管理。
  2. branchId由协调者生成:在调用Try之前就生成,确保Cancel时也有branchId可用。
  3. ++空回滚机制++:Cancel时检查是否有Try记录,没有则执行【空回滚】
  4. ++防悬挂机制++:Try时检查是否有Cancel记录,有则拒绝执行Try
  5. ++幂等性设计++:所有操作都要支持【重试】
  6. 本地事务记录:每个参与者都需要保存本地分支事务状态。

这个完整实现包含了TCC分布式事务的所有核心组件,当然,大家还可以在此基础上进行扩展和优化。实际生产环境中还需要考虑更多因素,如超时重试、异步消息、补偿任务、监控告警等。

相关推荐
alonewolf_992 小时前
RabbitMQ高级功能全面解析:队列选型、死信队列与消息分片实战指南
分布式·消息队列·rabbitmq·ruby
indexsunny2 小时前
互联网大厂Java面试实战:Spring Boot与微服务在电商场景中的应用解析
java·数据库·spring boot·微服务·maven·flyway·电商
hellojackjiang20113 小时前
如何保障分布式IM聊天系统的消息有序性(即消息不乱)
分布式·架构·即时通讯·im开发
burning_maple4 小时前
设计数据密集型应用阅读笔记
分布式·后端·中间件
振华OPPO4 小时前
开源高性能RPC框架:Apache Dubbo全览与实践指南
微服务·rpc·开源·apache·dubbo·总线
alonewolf_994 小时前
RabbitMQ快速上手与核心概念详解
分布式·消息队列·rabbitmq
ChineHe4 小时前
Docker基础篇001_Docker入门指南(基于官方教程,5W字详细版)
运维·docker·微服务·容器·云计算·devops
陌路204 小时前
RPC分布式通信(4)--Zookeeper
分布式·zookeeper·rpc
陌路205 小时前
RPC分布式通信(6)---调用方自动封装请求数据、从 ZK 获取服务地址、建立 TCP 连接发送请求、接收并解析响应
分布式·tcp/ip·rpc