【Easylive】手动实现分布式事务解决方案流程解析

【Easylive】项目常见问题解答(自用&持续更新中…) 汇总版

分布式事务解决方案深度解析

一、两阶段提交(2PC)

核心流程

  1. 准备阶段

    • 协调者发送prepare请求,参与者执行事务但不提交
    • 参与者锁定资源并记录undo/redo日志
    • 返回Yes/No响应
  2. 提交阶段

    • 全票通过则发送commit命令
    • 任一失败则发送rollback命令

实现方案

-- 事务状态表
CREATE TABLE transaction_log (
  tx_id VARCHAR(64) PRIMARY KEY,
  status ENUM('PREPARED', 'COMMITTED', 'ROLLBACKED'),
  create_time DATETIME
);
// 协调者伪代码
public class Coordinator {
    public boolean execute(TransactionContext ctx) {
        // 阶段1:准备
        for (Participant p : participants) {
            if (!p.prepare(ctx)) return false;
        }
        // 阶段2:提交
        try {
            for (Participant p : participants) {
                p.commit(ctx);
            }
            return true;
        } catch (Exception e) {
            for (Participant p : participants) {
                p.rollback(ctx);
            }
            return false;
        }
    }
}

优缺点

✅ 强一致性保证
❌ 同步阻塞、协调者单点故障
❌ 网络分区可能导致资源锁定


二、补偿事务(TCC)

三阶段操作

阶段 操作说明
Try 资源预留(如冻结库存)
Confirm 确认执行(实际扣减)
Cancel 补偿回滚(释放冻结资源)

关键实现

public interface PaymentServiceTCC {
    @Transactional
    boolean tryPayment(Long orderId, BigDecimal amount);
    
    @Transactional
    boolean confirmPayment(Long orderId);
    
    @Transactional
    boolean cancelPayment(Long orderId);
}

注意事项

  1. 幂等控制:需处理重复调用
  2. 空补偿:处理未执行Try直接Cancel的情况
  3. 悬挂问题:防止Try超时后Cancel先执行

三、本地消息表

实现架构

[业务事务] → [消息表记录] → [定时任务] → [MQ] → [消费者]

核心代码

CREATE TABLE local_message (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  biz_id VARCHAR(64) NOT NULL,
  content TEXT NOT NULL,
  status TINYINT DEFAULT 0 COMMENT '0-待发送,1-已发送',
  retry_count INT DEFAULT 0,
  create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
@Transactional
public void createOrder(Order order) {
    // 1. 业务操作
    orderMapper.insert(order);
    
    // 2. 记录消息(同事务)
    LocalMessage msg = new LocalMessage();
    msg.setBizId(order.getOrderNo());
    msg.setContent(JSON.toJSONString(order));
    messageMapper.insert(msg);
}

优势

  • 实现简单,与业务解耦
  • 天然支持重试机制

四、Saga模式

执行模式对比

类型 特点
协同式 通过事件驱动协调
编排式 中央协调器控制流程

状态机示例

public void placeOrder() {
    try {
        // 正向操作
        inventoryService.reserveStock();
        paymentService.charge();
        shippingService.createShipment();
    } catch (Exception e) {
        // 逆向补偿
        shippingService.cancelShipment();
        paymentService.refund();
        inventoryService.releaseStock();
    }
}

日志追踪设计

CREATE TABLE saga_log (
  saga_id VARCHAR(64) NOT NULL,
  step_name VARCHAR(32) NOT NULL,
  status VARCHAR(16) NOT NULL,
  params TEXT,
  create_time TIMESTAMP,
  PRIMARY KEY (saga_id, step_name)
);

方案选型指南

对比矩阵

方案 一致性 复杂度 性能 适用场景
2PC 强一致 银行转账等强一致场景
TCC 最终一致 中高 电商交易、支付系统
本地消息表 最终一致 物流通知、积分系统
Saga 最终一致 中高 长流程业务(保险理赔)

组合方案推荐

  1. 支付+库存:TCC + 本地消息表
  2. 订单履约:Saga + 异步补偿
  3. 数据一致性:2PC + 超时补偿机制

最佳实践原则

  1. 业务分析:根据CAP理论权衡一致性需求
  2. 降级方案:设计合理的补偿机制
  3. 监控体系:建立事务状态追踪看板
  4. 压力测试:验证方案在高并发下的表现

注:所有方案都需要配合幂等控制、重试机制和日志追踪才能保证可靠性

本地消息表(Local Message Table)方案专业解析

1. 核心设计思想

本地消息表是一种基于最终一致性的分布式事务解决方案,通过异步消息传递+事务日志实现跨服务数据同步:

  • 事务拆分:将分布式事务拆分为多个本地事务
  • 消息驱动:通过本地消息表记录待处理操作,异步触发下游服务
  • 重试补偿:定时任务保证消息可靠投递,失败时自动重试

2. 技术实现组件

组件 作用 技术实现示例
业务事务表 记录主业务数据(如订单表) MySQL order
本地消息表 存储待分发的消息(状态机模式) MySQL local_message
定时任务调度器 扫描未处理消息,触发下游服务调用 Spring @Scheduled + 线程池
幂等处理器 防止下游服务重复消费 Redis唯一ID/数据库唯一约束
监控告警模块 捕获长期失败消息,触发人工干预 Prometheus + Grafana + 企业微信报警

3. 关键流程(ACID特性保障)

Client ServiceA LocalDB Scheduler ServiceB 创建订单 开启事务 INSERT INTO order (...) VALUES (...) INSERT INTO local_message (...) VALUES (...) 提交事务 返回成功 SELECT * FROM local_message WHERE status=0 返回待处理消息 HTTP POST /api/process 200 OK UPDATE local_message SET status=1 UPDATE retry_count=retry_count+1 alt [调用成功] [调用失败] loop [定时任务] Client ServiceA LocalDB Scheduler ServiceB

4. 技术关键点

原子性(Atomicity)保障

@Transactional
public void createOrder(Order order) {
    orderDao.insert(order);          // 业务数据
    messageDao.insert(toMessage(order)); // 事务消息
}

可靠性(Durability)设计

  • 消息表与业务表同库同实例,利用RDBMS的WAL日志保证持久化
  • 定时任务采用至少一次(at-least-once)投递语义

幂等性(Idempotency)控制

@PostMapping("/api/process")
public Response process(@RequestBody Message msg) {
    if (redis.setnx(msg.getId(), "1", 24h)) { // 分布式锁
        realProcess(msg); // 真实业务逻辑
    }
    return Response.success();
}

一致性(Consistency)恢复

long delay = Math.min(1000 * Math.pow(2, retryCount), 3600000);

5. 生产级优化建议

消息表分库分表

CREATE TABLE local_message_${hash(id)%16} (...);

批量消息处理

@Scheduled(fixedDelay = 5000)
public void batchProcess() {
    List<Message> batch = messageDao.scan(100);
    CompletableFuture[] futures = batch.stream()
        .map(msg -> asyncProcess(msg))
        .toArray(CompletableFuture[]::new);
    CompletableFuture.allOf(futures).join();
}

死信队列处理

if (msg.getRetryCount() > MAX_RETRY) {
    deadLetterQueue.add(msg); // 转入死信队列
    alarmService.notifyAdmin(msg);
}

6. 方案局限性

  1. 时效性缺陷
    依赖定时任务扫描,消息处理延迟通常在秒级

  2. 架构约束
    要求业务消息必须可序列化存储

  3. 维护成本
    需额外维护消息表、定时任务等组件

7. 适用场景评估

场景 适用性 理由
订单创建→库存扣减 ★★★★★ 允许短暂延迟,业务容忍最终一致
支付成功→短信通知 ★★★★☆ 通知类操作对实时性要求较低
金融账户转账 ★★☆☆☆ 需要强一致性,建议使用TCC或Saga
日志数据同步 ★★★★★ 天然适合异步处理

该方案在电商、物流等互联网业务中广泛应用,是平衡实现复杂度与可靠性的典型折中方案

用"外卖订餐"理解本地消息表

现实场景 vs 技术实现

餐馆运营问题 分布式系统问题 解决方案
前台接单记录 业务数据存储 MySQL订单表
厨房小票 事务消息 local_message表
服务员送小票 消息投递 定时任务扫描
厨房小黑板 幂等控制 Redis唯一标识
店长监督 监控告警 Prometheus+钉钉

核心四步流程

1️⃣ 接单存双录(事务原子性)

@Transactional // 原子操作保证
public void 接单(订单 order) {
    // 记录主订单(账本)
    订单库.save(order); 
    
    // 生成厨房小票(消息)
    小票机.save(new 小票(
        order.id, 
        "新订单", 
        LocalDateTime.now()
    ));
}

就像收银机:按一次按钮同时打印顾客账单和厨房小票

2️⃣ 异步送小票(最终一致性)

每5分钟
成功
失败
定时任务
扫描未送小票
是否超过3次?
尝试送厨房
放入死信队列
标记已送达
增加重试次数

3️⃣ 厨房防重做(幂等性)

def 做菜(订单号):
    if redis.get(订单号) == "处理中":
        return "已在制作"
    
    redis.set(订单号, "处理中", ex=3600)
    实际做菜操作()
    return "开始制作"

‍ 相当于厨师长看到相同订单号会说:“这份已经在炒了!”

4️⃣ 异常处理三板斧

// 1. 指数退避重试
Thread.sleep(1000 * Math.pow(2, 重试次数));

// 2. 死信队列监控
if(小票.重试次数 > 3){
    钉钉报警("请店长处理订单:"+小票.订单号);
}

// 3. 人工补偿入口
@PostMapping("/手动重试")
public String 人工重试(String 订单号){
    消息 msg = 小票机.find(订单号);
    厨房服务.做菜(msg.getContent());
}

方案优势

  1. 高可靠:小票机相当于WAL日志,断电也不丢单
  2. 可扩展:多个服务员(消费者)并行处理小票
  3. 解耦合:厨房装修(服务升级)不影响前台接单
  4. 可追溯:所有小票永久存档,随时审计

注意事项

  1. 小票内容要包含全部必要信息(如顾客忌口)
  2. 厨房处理能力要匹配送单频率(背压问题)
  3. 定期归档历史小票(消息表分库策略)

就像优秀的外卖系统:订单可能稍有延迟,但绝不会丢失或重复!
用这种模式可以处理:订单→库存、支付→通知等大多数最终一致性场景。

你可能感兴趣的:(Easylive,分布式)