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

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

在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.xdnf.cn/news/14923.html

相关文章:

  • 电信、移动、联通、广电跨运营商网速慢原因
  • Linux国产与国外进度对垒
  • 408第三季part2 - 计算机网络 - 传输层
  • C++高频知识点(二)
  • std::vector<bool>有什么特殊的吗
  • 植物大战僵尸杂交重制版1.0,经典焕新,重燃策略塔防之火
  • 极简的神经网络反向传播例子
  • Vue计算属性(computed)全面解析:原理、用法与最佳实践
  • Web攻防-XMLXXE上传解析文件预览接口服务白盒审计应用功能SRC报告
  • 【Python】文件应用: 查找读取的文件内容
  • list和list中的注意事项
  • 使用ADSL接入Internet,用户端需安装什么协议?
  • Kubernetes 集群相关规范
  • 手机电工仿真软件更新了
  • 让你的asp.net网站在调试模式下也能在局域网通过ip访问
  • NLP之文本纠错开源大模型:兼看语音大模型总结
  • bottles安装网易云出现的问题01中文出现乱码问题
  • JavaScript基础语法之运算符和控制流
  • 程序员在线接单
  • 记录一点开发技巧
  • 七牛云前端面试题及参考答案 (上)
  • 隐马尔可夫模型(HMM):观测背后的状态解码艺术
  • 动手学深度学习-学习笔记【二】(基础知识)
  • 编程中的英语
  • 【C语言刷题】第十一天:加量加餐继续,代码题训练,融会贯通IO模式
  • cloudflare配合github搭建免费开源影视LibreTV一个独享视频网站 详细教程
  • HarmonyOS学习2---工程目录UIAbility
  • rk3128 emmc显示剩余容量为0
  • C++ 模板参数匹配、特化
  • openai和chatgpt什么关系