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

分布式事务解决方案(二)

在Java分布式系统中,强一致性事务方案(如2PC、3PC)虽能保障数据的绝对一致,但因阻塞、性能等问题难以适配高并发场景。相比之下,最终一致性方案通过牺牲强一致性换取可用性和性能,成为互联网架构的主流选择。本文将深入解析TCC(Try-Confirm-Cancel)本地消息表两种经典最终一致性方案,结合Java代码示例与应用场景,探讨其原理、实现与实践要点。

一、TCC(Try-Confirm-Cancel)模式

1.1 TCC核心思想

TCC将分布式事务拆解为三个阶段:

  • Try阶段:预留资源(如冻结金额、锁定库存),确保后续操作具备可行性。
  • Confirm阶段:在Try成功后执行真正的业务逻辑(如扣款、扣减库存),此阶段需保证幂等性。
  • Cancel阶段:若Try或Confirm失败,执行补偿操作(如解冻金额、释放库存),撤销已执行的Try操作。

1.2 示例场景:电商订单与支付事务

以电商场景为例,订单服务需协调库存服务(扣减库存)与支付服务(扣款),实现“下单-支付”原子性。

1.2.1 接口定义

// 库存服务接口
public interface StockService {boolean tryDeductStock(String orderId, int quantity);  // Tryboolean confirmDeductStock(String orderId, int quantity);  // Confirmboolean cancelDeductStock(String orderId, int quantity);  // Cancel
}// 支付服务接口
public interface PaymentService {boolean tryFreezeAmount(String orderId, BigDecimal amount);  // Tryboolean confirmPay(String orderId, BigDecimal amount);  // Confirmboolean cancelFreeze(String orderId, BigDecimal amount);  // Cancel
}

1.2.2 订单服务调用逻辑

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class OrderService {@Autowiredprivate StockService stockService;@Autowiredprivate PaymentService paymentService;@Transactionalpublic boolean createOrder(String orderId, int quantity, BigDecimal amount) {// Try阶段if (!stockService.tryDeductStock(orderId, quantity) ||!paymentService.tryFreezeAmount(orderId, amount)) {rollbackAll(orderId);return false;}try {// Confirm阶段if (!stockService.confirmDeductStock(orderId, quantity) ||!paymentService.confirmPay(orderId, amount)) {rollbackAll(orderId);return false;}return true;} catch (Exception e) {rollbackAll(orderId);return false;}}private void rollbackAll(String orderId) {stockService.cancelDeductStock(orderId, 0);paymentService.cancelFreeze(orderId, null);}
}

1.3 TCC实现难点与解决方案

  1. 幂等性处理

    • 方案:为每个事务添加唯一ID,在Confirm/Cancel阶段通过Redis或数据库记录操作状态,重复调用时直接返回成功。
  2. 空回滚与悬挂处理

    • 空回滚:Cancel阶段在Try未执行时被调用。
      解决方案:在Cancel前检查Try是否执行(如数据库记录Try状态)。
    • 悬挂:网络延迟导致Try请求后发先至。
      解决方案:在Try前检查Cancel是否已执行,若已执行则直接返回失败。
  3. 异常补偿

    • 引入补偿任务调度(如XXL-Job、Elastic-Job),定时扫描未完成的事务并触发补偿。

二、本地消息表方案

2.1 核心原理

本地消息表通过**“事务消息化”**将分布式事务拆解为两个本地事务,借助消息队列实现异步最终一致性:

  1. 生产者:将业务操作与消息写入数据库(本地事务),消息状态标记为“待发送”。
  2. 消息发送:异步将消息发送至消息队列,并更新消息状态为“发送中”。
  3. 消费者:消费消息并执行对应业务(本地事务),成功后通知生产者更新消息状态为“已完成”。
  4. 补偿机制:通过定时任务扫描“发送中”或超时的消息,重试发送或人工介入。

2.2 示例场景:订单创建与物流通知

订单服务创建订单后,需通知物流服务生成运单。

2.2.1 数据库表设计

-- 订单表
CREATE TABLE orders (order_id VARCHAR(64) PRIMARY KEY,user_id VARCHAR(64),product_id VARCHAR(64),status TINYINT  -- 0:待支付, 1:已支付...
);-- 消息表
CREATE TABLE message_table (message_id VARCHAR(64) PRIMARY KEY,order_id VARCHAR(64),topic VARCHAR(64),  -- 消息主题,如"order_created"content TEXT,  -- 消息内容status TINYINT,  -- 0:待发送, 1:发送中, 2:已完成retry_count INT DEFAULT 0,create_time TIMESTAMP
);

2.2.2 Java代码实现

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.kafka.core.KafkaTemplate;@Service
public class OrderService {@Autowiredprivate JdbcTemplate jdbcTemplate;@Autowiredprivate KafkaTemplate<String, String> kafkaTemplate;@Transactionalpublic void createOrder(String orderId, String userId, String productId) {// 1. 插入订单数据jdbcTemplate.update("INSERT INTO orders (order_id, user_id, product_id, status) VALUES (?,?,?, 0)",orderId, userId, productId);// 2. 插入消息数据String messageId = UUID.randomUUID().toString();String messageContent = "{\"orderId\":\"" + orderId + "\"}";jdbcTemplate.update("INSERT INTO message_table (message_id, order_id, topic, content, status) VALUES (?,?,?,?, 0)",messageId, orderId, "order_created", messageContent);// 3. 异步发送消息sendMessage(messageId);}private void sendMessage(String messageId) {jdbcTemplate.update("UPDATE message_table SET status = 1 WHERE message_id = ?", messageId);try {String messageContent = jdbcTemplate.queryForObject("SELECT content FROM message_table WHERE message_id = ?",String.class, messageId);kafkaTemplate.send("order_topic", messageContent);jdbcTemplate.update("UPDATE message_table SET status = 2 WHERE message_id = ?", messageId);} catch (Exception e) {// 发送失败,后续由定时任务重试}}
}// 消息消费逻辑
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;@Component
public class MessageConsumer {@Autowiredprivate LogisticsService logisticsService;@Autowiredprivate JdbcTemplate jdbcTemplate;@KafkaListener(topics = "order_topic", groupId = "logistics_group")public void handleMessage(String message) {try {// 解析消息获取orderIdString orderId = JSON.parseObject(message).getString("orderId");// 执行物流服务logisticsService.generateWaybill(orderId);// 更新消息状态为已完成jdbcTemplate.update("UPDATE message_table SET status = 2 WHERE order_id = ?", orderId);} catch (Exception e) {// 消费失败,由生产者重试}}
}

2.3 方案优化点

  • 幂等消费:消费者通过数据库唯一键或Redis记录已处理消息ID。
  • 重试机制:设置消息重试次数阈值,超过后转为人工处理。
  • 对账任务:每日扫描消息表与业务表,修复不一致数据。

三、TCC vs 本地消息表:方案对比

维度TCC模式本地消息表
一致性强最终一致性(出现异常需补偿)最终一致性(依赖重试机制)
性能同步阻塞(Try/Confirm阶段)异步高并发(消息队列解耦)
业务侵入性高(需改造业务接口为TCC模式)中(需增加消息表与重试逻辑)
适用场景资金、库存等强一致性场景订单通知、日志同步等弱一致性场景
典型案例银行转账、电商交易订单系统与物流系统异步协作

四、总结与实践建议

TCC和本地消息表作为Java分布式系统中最终一致性的核心方案,各有优劣。实际应用中,可根据业务场景特性组合使用:

  • 核心链路场景(如支付、库存):优先选择TCC,确保资金与资源安全。
  • 异步解耦场景(如订单通知、数据同步):采用本地消息表,提升系统吞吐量。
  • 混合方案:结合TCC处理关键业务,本地消息表处理辅助业务,实现性能与一致性的平衡。

http://www.lryc.cn/news/581486.html

相关文章:

  • 探索实现C++ STL容器适配器:优先队列priority_queue
  • react当中的this指向
  • (C++)学生管理系统(正式版)(map数组的应用)(string应用)(引用)(文件储存的应用)(C++教学)(C++项目)
  • .NET9 实现字符串拼接(StringConcatenation)性能测试
  • 深入探索 pnpm:高效磁盘利用与灵活的包管理解决方案
  • jmm,`as - if - serial` 与 `happens - before` 原则
  • 【一起来学AI大模型】算法核心:数组/哈希表/树/排序/动态规划(LeetCode精练)
  • OpenSearch 向量搜索与Qwen3-Embedding 集成示例
  • @Data、@AllArgsConstructor、@NoArgsConstructor不生效。lombok不起作用怎么解决?
  • Web前端开发-Vue
  • 多人协同开发时Git使用命令
  • 锁和事务的关系
  • 深入探索开源爬虫MediaCrawler,从入门到掌握多平台数据收集
  • HarmonyOS学习6 --- 数据存储
  • 9. 【Vue实战--孢子记账--Web 版开发】-- 账户账本管理(二)
  • MySQL CDC与Kafka整合指南:构建实时数据管道的完整方案
  • 1.线性神经网络--线性回归
  • 华为云 银河麒麟 vscode远程连接
  • 前端开发问题:SyntaxError: “undefined“ is not valid JSON
  • Flutter 每日翻译之 Widget
  • Vue+Openlayers加载OSM、加载天地图
  • java学习——guava并发编程练习
  • 【Guava】1.0.设计虚拟机的方向
  • 第一个Flink 程序:词频统计 WordCount(流处理)
  • LeetCode--41.缺失的第一个正数
  • 《Redis》缓存与分布式锁
  • AGV选型指南:AGV智能搬运车智能问答系统助力从技术参数到供应商选择的完整方案
  • Flutter 项目开启 UI 层级虚线(UI Guides)
  • 深度学习篇---简单果实分类网络
  • JAVA 项目找不到符号