news 2026/7/17 15:36:16

后端微服务间的最终一致性:本地消息表与事务消息的实现对比

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
后端微服务间的最终一致性:本地消息表与事务消息的实现对比

后端微服务间的最终一致性:本地消息表与事务消息的实现对比

一、一个下单请求,同时要调库存服务和积分服务,其中一个失败了怎么办

这是微服务架构下最经典的分布式一致性问题。用户下了单,订单服务需要做三件事:在自己数据库里创建订单、调用库存服务扣减库存、调用积分服务增加积分。在一个单体应用里,这三个操作包在一个数据库事务里即可——要么全成功,要么全回滚。ACID 特性天然保障。

但在微服务架构下,这三个操作发生在三个不同的服务、三个不同的数据库中。分布式事务不再像单机事务那样理所当然。如果订单创建成功、库存扣减成功,但积分增加失败——用户的积分没加上,账就对不上了。反过来,积分增加了但库存没扣——超卖的风险来了。

强一致性方案(如两阶段提交 2PC)可以实现跨服务的事务原子性,但代价是性能大幅下降,而且在网络分区时会阻塞所有参与者。更务实的做法是:接受短暂的不一致,通过异步机制最终达到一致。这种思路叫做最终一致性。

二、本地消息表:把消息当成数据库的一行记录

本地消息表的核心思路极其朴素:在业务数据库里,额外建一张消息表。业务操作和消息记录放在同一个本地事务里。订单创建成功的同时,一条"需要通知库存服务"的消息也写入了消息表。只要本地事务提交成功,订单和消息就一起持久化了。

之后的事情交给一个独立的定时任务。这个任务定期扫描消息表中"待发送"状态的记录,逐条发送给消息队列。发送成功后,更新消息状态为"已发送"。如果发送失败(网络故障、MQ 宕机),消息状态保持"待发送",下一个扫描周期会重新尝试。

这套方案的优点是零外部依赖——不需要支持事务消息的消息队列,任何支持 ACK 的 MQ 都能用。缺点是引入了一个定时扫描的延迟:在最坏情况下,消息要等到下一个扫描周期才能发出去,带来秒级的延迟。对积分增加这类对实时性要求不高的场景来说,这个延迟完全可接受。

三、事务消息:RocketMQ 的一步到位方案

如果觉得定时扫描的方式太"土",可以用 RocketMQ 的事务消息机制。它把"本地事务 + 消息发送"打包成一个原子操作,不需要额外的消息表。

工作流程是这样的:

  1. 生产者先发送一条"半消息"(half message)到 Broker,这条消息对消费者不可见。
  2. 生产者执行本地事务(比如创建订单)。
  3. 根据本地事务的结果,生产者向 Broker 发送 commit 或 rollback。
  4. 如果 Broker 长时间没收到确认(生产者宕机了),Broker 会主动回查生产者的本地事务状态。

事务消息消除了本地消息表的定时扫描延迟,但引入了新的复杂度:生产者必须实现回查接口。当 Broker 回查时,生产者要能判断对应的本地事务是否已经提交。这要求业务代码中保留查询事务状态的逻辑,比如根据订单 ID 去数据库查这条订单存在不存在。

/** * 使用 RocketMQ 事务消息实现最终一致性 * * 设计要点: * 1. 半消息确保消费者不会提前消费 * 2. 回查接口是兜底机制,处理生产者宕机的情况 * 3. 本地事务中只做关键业务操作,减少事务持有时间 */ @Component public class OrderTransactionProducer { @Resource private TransactionMQProducer producer; /** * 发送订单创建的事务消息 */ public void sendOrderCreatedMessage(Order order) { Message message = new Message( "ORDER_TOPIC", "OrderCreated", JSON.toJSONBytes(order) ); // 发送事务消息。executeLocalTransaction 回调中执行本地事务 TransactionSendResult result = producer.sendMessageInTransaction( message, order.getOrderId() // 用订单 ID 作为事务关联标识 ); if (result.getLocalTransactionState() == LocalTransactionState.ROLLBACK_MESSAGE) { throw new RuntimeException("订单创建失败,事务回滚"); } } } /** * 事务监听器:实现本地事务执行 + 回查 */ @Component public class OrderTransactionListener implements TransactionListener { @Resource private OrderService orderService; /** * 执行本地事务 * * 这里只做核心操作:创建订单 + 扣减库存的预占 * 其他非关键操作放在事务外异步处理 */ @Override public LocalTransactionState executeLocalTransaction( Message msg, Object arg) { String orderId = (String) arg; try { // 在本地事务中创建订单 orderService.createOrderInTransaction(orderId, msg); // 事务成功,通知 Broker 可以投递消息给消费者了 return LocalTransactionState.COMMIT_MESSAGE; } catch (Exception e) { log.error("订单创建失败, orderId={}", orderId, e); // 事务失败,消息也丢弃 return LocalTransactionState.ROLLBACK_MESSAGE; } } /** * 回查本地事务状态 * * Broker 在以下情况会回调此方法: * 1. executeLocalTransaction 返回了 UNKNOW * 2. 生产者宕机,没有返回任何状态 * * 这里的逻辑必须幂等且快速,因为 Broker 可能在短时间内多次回查 */ @Override public LocalTransactionState checkLocalTransaction( MessageExt msg) { String orderId = msg.getKeys(); // 查数据库:这笔订单创建成功了吗? Order order = orderService.getByOrderId(orderId); if (order != null) { // 订单存在 → 事务成功 → 消息可以投递 return LocalTransactionState.COMMIT_MESSAGE; } else { // 订单不存在 → 事务失败 → 消息丢弃 return LocalTransactionState.ROLLBACK_MESSAGE; } } }

回查逻辑是事务消息中最容易出错的地方。需要特别注意:回查可能在消息发送后的任意时间点触发(包括几分钟后),所以回查代码不能依赖本地事务上下文中的临时状态,只能通过查询数据库等持久化存储来判断。

四、两种方案的选型权衡

维度本地消息表事务消息
实时性秒级延迟(取决于扫描间隔)实时
依赖只需要普通 MQ需要支持事务消息的 MQ
复杂度需要维护消息表 + 定时任务需要实现回查接口
一致性保证最终一致性最终一致性
适用场景MQ 基础设施受限的团队已有 RocketMQ 等支持事务消息的中间件

选哪个方案,不取决于技术优越性,而取决于团队的现有基础设施。如果公司已经在用 RocketMQ,事务消息是更简洁的方案。如果用的是 Kafka 或 RabbitMQ(不支持事务消息),本地消息表就是务实的选择。

还有一个容易被忽略的问题:消费者的幂等性。无论用哪种方案,消息都可能被重复投递(网络重传、Broker 超时重试),消费者必须在消费逻辑上做幂等处理。最简单的幂等手段是:在消费者侧,根据消息中的业务唯一 ID(如订单 ID)做去重判断。

五、总结

分布式环境下的数据一致性,不可能做到和单机事务一样"强"。与其花大力气追逐强一致性,不如接受最终一致性的现实,然后选择合适的方案——本地消息表或事务消息——把不一致的窗口尽量缩短。两种方案各有适用的场景,关键是理解各自的前提条件和边界限制,在自己的系统约束下做出正确选择。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/17 15:33:37

Windows 10与Ubuntu 18.04双系统安装指南

1. 双系统安装方案概述 在Windows 10环境下安装Ubuntu 18.04.3双系统,是开发者、数据分析师和科研人员的常见需求。这种配置既能保留Windows的办公娱乐兼容性,又能获得Linux系统的高效开发环境。我最近在ThinkPad X1 Carbon上完成了这套配置&#xff0c…

作者头像 李华
网站建设 2026/7/17 15:30:37

macOS微信增强终极指南:3分钟实现消息防撤回与多开功能

macOS微信增强终极指南:3分钟实现消息防撤回与多开功能 【免费下载链接】WeChatTweak A command-line tool for tweaking WeChat - 首款微信 macOS 客户端撤回拦截与多开 🔨 项目地址: https://gitcode.com/gh_mirrors/we/WeChatTweak WeChatTwea…

作者头像 李华
网站建设 2026/7/17 15:28:58

5个颠覆性技巧:用Loop彻底改变你的macOS窗口管理工作流

5个颠覆性技巧:用Loop彻底改变你的macOS窗口管理工作流 【免费下载链接】Loop Window management made elegant. 项目地址: https://gitcode.com/GitHub_Trending/lo/Loop Loop是一款优雅的macOS窗口管理工具,通过直观的径向菜单和智能窗口定位系…

作者头像 李华
网站建设 2026/7/17 15:28:33

openEuler SBOM-website社区贡献指南:如何参与开源项目开发

openEuler SBOM-website社区贡献指南:如何参与开源项目开发 【免费下载链接】sbom-website A frontend service named sbom-website, designed for showing sbom secure supply chain governance process. 项目地址: https://gitcode.com/openeuler/sbom-website …

作者头像 李华