当前位置: 首页 > news >正文

【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 {@Transactionalboolean tryPayment(Long orderId, BigDecimal amount);@Transactionalboolean confirmPayment(Long orderId);@Transactionalboolean 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. 定期归档历史小票(消息表分库策略)

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

相关文章:

  • 【Flask】Explore-Flask:早期 Flask 生态的实用指南
  • 多模态大语言模型arxiv论文略读(三十三)
  • 【产品经理思维】
  • 多级缓存架构,让系统更快的跑起来!
  • 特伦斯智慧钢琴评测:如何用科技重塑钢琴学习新体验
  • Cribl 利用表向event 中插入相应的字段-example-01
  • C++入门语法
  • FreeRTOS中的优先级翻转问题及其解决方案:互斥信号量详解
  • 第十四届蓝桥杯 2023 C/C++组 平方差
  • 设计模式 建造者模式
  • Pycharm(九)函数的闭包、装饰器
  • compat-openssl10和libnsl下载安装
  • Java高效合并Excel报表实战:GcExcel让数据处理更简单
  • 靠华为脱胎换骨,但赛力斯仍需要Plan B
  • MySQL访问权限授权问题
  • 二分查找、分块查找、冒泡排序、选择排序、插入排序、快速排序
  • SPL 量化 序言
  • 【FFmpeg从入门到精通】第四章-FFmpeg转码
  • 性能比拼: Nginx vs Caddy
  • 乾元通渠道商中标舟山市自然灾害应急能力提升工程基层防灾项目
  • 最高法:抢票软件为用户提供不正当优势,构成不正当竞争
  • 从沙漠到都市:贝亲世界地球日特别行动,以桃叶冰爽力开启地球降温之旅
  • 今年一季度,上海对东盟进出口总值同比增长7.1%
  • 新科世界冠军!雨果4比1战胜林诗栋,首夺世界杯男单冠军
  • 重点并不在于设计更聪明的机器,而在于开发宇宙技术的多样性
  • “这是本届政府的态度”,英国明确拒绝与中国脱钩