RocketMQ半消息对消费者不可见是如何实现的?——事务消息机制揭秘

首发于工号【BiggerBoy】,原文链接

------"半消息藏在这里,但为什么你偷看也没用?"


上篇《RocketMQ系列笔记(三):消息模型与高阶玩法,顺序事务消息拿捏指南》中提到"TBW102是RocketMQ预留的事务消息Topic",是什么意思?为什么消费者看不到半消息?是如何实现的?带着这些疑问,开启今天的探索之旅!


这句话隐藏的意思就是:生产者发送事务消息时实际Broker会把半消息存储到TBW102这个Topic。当生产者通知Broker提交时,Broker会将消息发送到生产者指定的Topic,此时消费者才能消费该消息。

以下是更详细的流程说明:

事务消息:一手交钱一手交货

再来回顾一下事务消息的流程:

发送半消息

  • 生产者发送事务消息时,首先将消息作为半消息发送到Broker
  • 这个半消息会被存储在RocketMQ的默认事务Topic TBW102
  • 此时,消息对消费者是不可见的,因为它还没有被正式提交

执行本地事务

  • 半消息提交成功后,生产者执行本地事务逻辑(例如数据库操作)
  • 本地事务的执行结果(成功或失败)决定了后续的操作

通知Broker提交或回滚

  • 提交 如果本地事务成功,生产者会通知Broker提交半消息。此时,Broker会将消息从 TBW102 移动到生产者指定的目标Topic,消息对消费者可见
  • 回滚 如果本地事务失败,生产者会通知Broker回滚半消息。此时,Broker会丢弃 TBW102 中的半消息,消息不会被发送到目标Topic

事务消息的奥秘

消息可见性

  • 半消息阶段 消息存储在 TBW102,对消费者不可见
  • 提交后消息移动到目标Topic,对消费者可见

为什么设计为不可见?

  • 事务消息的核心设计
    • RocketMQ的事务消息机制是为了保证消息的最终一致性
    • 在半消息阶段,消息对消费者不可见,是为了避免消费者处理到未确认的消息(可能导致数据不一致)
    • 只有在生产者明确提交事务后,消息才会被移动到目标Topic,对消费者可见

简单来说

TBW102里的消息就像"加密文件",只有事务完成后才会被"解密"到正确的位置。

配置

  • 自定义半消息Topic 可通过配置transactionTopic参数指定其他Topic
  • Broker配置 在Broker配置文件中设置transactionTopic参数

注意事项

  • 可以自定义 通过Broker配置transactionTopic参数,灵活指定事务消息的存储位置
  • 但没必要乱动 如果没有特殊需求,直接用默认的TBW102反而更省心
  • 核心原则事务Topic是RocketMQ内部机制,业务代码无需感知,你的重点仍是本地事务和最终消息的一致性
  • 避免冲突 不要在生产环境中使用TBW102作为业务Topic,以免冲突
  • 监控与维护需监控和维护事务Topic,确保事务消息机制正常运行

总结

  • 半消息 存储在 TBW102,对消费者不可见
  • 提交后消息移动到目标Topic,对消费者可见
  • 回滚消息被丢弃,不会发送到目标Topic

这种机制确保了事务消息的原子性,即消息的发送与本地事务的执行结果保持一致。


有的小伙伴就会好奇问了:如果消费者订阅TWB102呢,不就可以消费了吗?刚刚说"生产者通知Broker回滚消息,Broker会丢弃TWB102中的半消息",这里的丢弃是从Broker中删除吗?不会违背RocketMQ顺序写的特性吗?

这是个好问题!下面来揭秘一下:


消费者订阅 TBW102 的情况

  • 理论上可以订阅 消费者确实可以订阅 TBW102 这个Topic
  • 实际意义有限
    • TBW102是RocketMQ内部用于存储半消息的Topic,这些消息还未被确认提交或回滚
    • 即使消费者订阅了 TBW102,也只能看到未完成事务的半消息,这些消息的状态是不确定的(可能最终会被提交或回滚)
    • 从业务逻辑上来说,消费这些半消息是没有意义的,因为它们可能最终会被丢弃

划重点

哪怕你订阅了TBW102,看到的也只是"未确认状态"的半消息,它们可能下一秒就被删除或转移,毫无业务价值。


消息被丢弃的含义

  • 回滚时的处理
    • 当生产者通知Broker回滚事务时,Broker会将 TBW102 中对应的半消息标记为已回滚
    • 这些消息会被标记为已删除,消费者无法再访问这些消息

实际上被回滚的半消息还存储在Commitlog中,只是被标记为已删除,之所以没有物理删除和RocketMQ的存储机制有一定关系。

RocketMQ的存储机制

  • CommitLog
    • RocketMQ将所有消息(包括事务消息)顺序写入一个统一的文件,称为 CommitLog
    • 这种设计保证了高性能的写入,因为磁盘只需要顺序追加数据
  • ConsumeQueue
    • 每个Topic的消息索引存储在 ConsumeQueue 中,消费者通过 ConsumeQueue 快速定位消息
  • 事务消息的特殊性
    • 事务消息(包括半消息)也会写入 CommitLog,但在事务未完成时,不会将消息索引写入目标Topic的 ConsumeQueue

消息删除的实现原理

删除操作的本质:RocketMQ的"删除"并不是直接从 CommitLog 中物理擦除数据,而是通过以下方式实现:标记删除:对于回滚的事务消息,RocketMQ会将其标记为"已删除"跳过消费:消费者从ConsumeQueue读取消息时,会跳过被标记为删除的消息物理删除:RocketMQ会定期执行文件清理(默认72小时),将过期的文件(包括已删除的消息)从磁盘中删除顺序写的保持:

    • 删除操作不会影响 CommitLog 的顺序写特性,因为删除只是标记消息状态,而不是立即修改 CommitLog 文件
    • 物理删除是通过清理整个文件(而不是单独删除某条消息)来实现的,因此不会破坏顺序写的性能

结论

删除操作既不会破坏顺序写,也不会让磁盘"千疮百孔",RocketMQ早就想好了怎么优雅"扔垃圾"。

总结

  • 半消息 存储在 TBW102,对消费者不可见。
  • 提交后 消息移动到目标Topic,对消费者可见。
  • 回滚 消息被丢弃,不会发送到目标Topic。

因此,RocketMQ的事务消息机制通过 TBW102 和半消息的设计,确保了消息的可靠性和一致性,避免了消费者处理到未确认的消息。

好了,今天的分享就到这里啦!如果对你有帮助,辛苦转发➕关注,感谢支持~

相关推荐
埃泽漫笔2 天前
RabbitMQ 核心概念解析
java·mq
Savvy..3 天前
消息队列MQ
kafka·消息队列·rabbitmq·rocketmq·mq
埃泽漫笔5 天前
RabbitMQ四种交换机详解
python·mq
埃泽漫笔6 天前
消息队列延迟与过期问题的实战解决
java·mq
埃泽漫笔7 天前
如何避免消息丢失
mq
埃泽漫笔8 天前
mq的常见问题
java·mq
埃泽漫笔10 天前
消息顺序消费问题
java·mq
码luffyliu1 个月前
消息队列 :Kafka 核心要点总结
分布式·kafka·消息队列·mq
墨鸦_Cormorant2 个月前
Spring Boot 集成 Eclipse Mosquitto
spring boot·后端·mqtt·eclipse·mq
努力学习的明4 个月前
MQ解决高并发下订单问题,实现流量削峰
开发语言·后端·并发·mq