为什么要使用RocketMQ半消息
1,什么是半消息?
半消息,也称为预备消息(Prepared Message),是 RocketMQ 实现分布式事务的一种核心机制。它指的是已经成功发送到 Broker,但是暂时对消费者不可见(即消费者无法消费到)的消息。
可以把它理解为一个“预提交”的状态。消息先被存起来,但先不“解锁”,直到后续的确认操作完成。
2,半消息的核心作用
半消息最主要、最核心的作用就是实现分布式事务,确保本地事务执行和消息发送这两个分布式操作之间的最终一致性。
1. 解决“上游事务成功,下游消息必达”的问题
在没有半消息的普通场景下,一个常见的难题是:
先执行本地数据库事务,然后发送MQ消息。
风险:数据库事务成功,但消息发送失败,导致下游系统无法感知状态变化。
先发送MQ消息,然后执行本地数据库事务。
风险:消息发送成功,但数据库事务失败或回滚,导致下游系统收到了一个“虚假”的消息。
半消息机制就是为了优雅地解决这个“先有鸡还是先有蛋”的难题。
3,半消息的工作流程(事务消息)
半消息是 RocketMQ 事务消息 实现的一部分。其完整的工作流程如下:
4,典型应用场景
5,总结
特性 | 说明 |
---|---|
本质 | 一种对消费者不可见的“预备消息”状态。 |
核心作用 | 作为 RocketMQ 事务消息的基石,解决分布式系统中的数据最终一致性问题。 |
关键机制 | 二阶段提交(发送半消息 -> 结束事务) + 状态回查(解决超时未决问题)。 |
最终目的 | 保证:只要下游系统消费到了消息,那上游的本地事务就一定执行成功了。 |
简单来说,半消息就是一个“预占位”操作,先把消息的位置占住,等本地事务出结果后,再决定是“确认生效”还是“取消作废”,从而完美地协调了数据库和消息队列之间的状态,是实现可靠消息传递的关键设计。
6,如果不使用半消息会有什么问题
发送半消息:
生产者向 Broker 发送一条消息。这条消息此时就是一个“半消息”。
关键:Broker 会接收并存储这条消息,但不会将其投递给任何消费者。它对消费者是“隐藏”的。
执行本地事务:
半消息发送成功后,生产者开始执行本地数据库事务(例如:扣减库存、生成订单等)。
结束事务(提交或回滚):
本地事务执行完成后,生产者会向 Broker 发送一个 结束事务 的请求(
commit
或rollback
)。提交(Commit):如果本地事务执行成功,Broker 会将这条半消息从“隐藏”状态改为“可投递”状态(即成为一个正常消息)。之后,消费者就可以正常消费这条消息了。
回滚(Rollback):如果本地事务执行失败,Broker 会直接删除这条半消息,就像它从来没发送过一样。下游系统自然也就收不到这条消息。
事务回查(Transaction Check):
这是一个非常重要的容错机制。如果在第3步,生产者因为宕机、网络异常等原因,一直没有向 Broker 发送
commit
或rollback
指令,这条消息就会一直处于“半消息”的悬而未决状态。RocketMQ 会定期(可配置)向生产者发起 事务状态回查。
生产者收到回查后,需要检查本地事务的最终执行结果(例如:查询本地数据库,看订单是否最终创建成功),并根据检查结果再次向 Broker 提交
commit
或rollback
指令。
为什么需要半消息?(带来的好处)
数据最终一致性:确保了业务操作(本地事务)和消息发送要么同时成功,要么同时失败。下游系统消费消息的前提,一定是上游业务逻辑正确执行完毕。
解耦:将分布式事务的复杂性封装在了 MQ 内部,业务开发者只需关注本地事务的执行和回查逻辑的实现,无需自己实现复杂的分布式事务协议(如两阶段提交)。
高可用:即使生产者在发送半消息后发生故障,RocketMQ 的事务回查机制也能最终决定消息的命运,避免了消息长时间处于“未知”状态,保证了系统的最终一致性。
避免脏读:消费者永远不会消费到“对应本地事务失败”的消息,保证了数据的正确性。
订单系统与积分系统:用户下单成功后,需要给用户增加积分。
订单服务创建订单(本地事务)。
同时,发送一条“订单已创建”的半消息。
如果订单创建成功,则提交半消息,积分系统消费到消息后为用户增加积分。
如果订单创建失败(例如库存不足),则回滚半消息,积分系统不会收到任何消息。
支付成功通知:支付成功后,通知其他系统(如发货、发券等)。
所有需要保证业务操作与事件通知强一致性的场景。
方案流程复述:
begin transaction;
(开启事务)
update order set status = 'paid' where id = 1;
(执行SQL)
mq.send(msg);
+ retry
(发送消息并重试直至成功)
根据第3步的结果:
成功:
commit transaction;
(提交事务)失败:
rollback transaction;
(回滚事务)
场景:在步骤3发送消息成功后,步骤4提交之前,应用程序宕机了。
结果:因为数据库事务还没有提交,所以数据库操作会随着连接断开而自动回滚。同时,消息虽然已经发送到Broker并被消费者可见,但对应的业务数据(订单状态)并没有真的更新。
数据库事务时间过长(性能问题)
问题:网络I/O(发送MQ)是非常耗时的操作。将这个操作放在数据库事务内部,会导致数据库连接占用时间非常长。
后果:数据库连接是宝贵的资源。长时间占用连接会显著降低数据库的吞吐量,成为系统性能的瓶颈。在高并发场景下,可能很快耗尽数据库连接池,导致系统无法响应。
极端情况下的不一致(虽然概率低,但存在)
场景:步骤3消息发送成功,步骤4执行
commit
时,数据库节点突然宕机。结果:这是一个非常极端的情况。数据库可能提交成功也可能回滚,生产者无法确定。这会导致和上面类似的问题:消息发出去了,但不确定数据库事务是否成功。这本质上是分布式事务问题,即如何保证两个系统(数据库和MQ)的操作具有原子性。
对业务代码的侵入性
业务逻辑必须关心消息发送的重试和事务的最终决策,代码结构被固定化了。
结论:这会导致下游系统消费到一条“虚假”的消息(例如,积分系统收到了支付成功消息,但订单库中该订单仍是“未支付”)。这虽然是不一致的,但比消息彻底丢失要好,因为消息还在,我们可以通过后续的对账、补偿机制来发现和修复这个不一致。而在消息丢失的方案中,我们连发现问题都很难。
它牺牲了数据库性能,并且仍然存在一个极小概率的不一致窗口。
而RocketMQ的半消息方案可以看作是对这个方案的标准化、产品化和优化:
性能优化:它将耗时的网络发送从数据库事务中剥离出去(先发半消息,再执行事务)。
可靠性强化:通过事务回查机制,作为一个可靠的兜底方案,主动地去消除那个“不一致窗口”,使得整个流程更加健壮和自动化。
因此,要构建一个高性能、高可靠、自动化的分布式系统,RocketMQ的半消息方案是更专业的选择。