RabbitMQ 源码剖析:消息存储与协议实现(一)

一、引言

**

在当今分布式系统和微服务架构盛行的时代,消息队列作为一种关键的中间件技术,发挥着举足轻重的作用。它承担着系统间异步通信、解耦、削峰填谷等重要职责,是构建高可用、高性能、可扩展系统的基石。RabbitMQ 作为开源消息队列领域的佼佼者,凭借其成熟稳定的特性、丰富的功能集以及活跃的社区支持,被广泛应用于各类企业级项目中 。无论是互联网初创公司,还是大型金融机构,都能看到 RabbitMQ 的身影,它已成为消息队列领域的事实标准之一。

深入探究 RabbitMQ 的消息存储与协议实现源码,就如同打开了一扇通往其核心运作机制的大门。通过剖析这部分内容,我们不仅能够深入理解消息在队列中的存储方式、持久化机制,以及如何高效地进行读写操作,还能明白 RabbitMQ 是如何基于特定协议与客户端进行通信,确保消息的准确传输和处理。这对于开发者来说,在进行性能优化、故障排查以及根据业务场景定制化开发时,都能提供至关重要的依据。同时,在面对复杂多变的业务需求和高并发的挑战时,通过掌握源码知识,能够更加灵活地调整和优化 RabbitMQ 的配置,使其更好地服务于业务系统。接下来,就让我们一同开启 RabbitMQ 源码剖析之旅,探寻其消息存储与协议实现的奥秘。

二、RabbitMQ 消息存储机制详解

2.1 存储架构概览

在 RabbitMQ 中,消息的可靠存储依赖于其精心设计的存储架构,其中持久层是核心概念。持久层从逻辑上可细分为两个关键部分:队列索引(rabbit_queue_index)和消息存储(rabbit_msg_store) 。

队列索引,犹如一本精确的目录,每个队列都有专属的队列索引与之对应。它详细记录了队列中落盘消息的各类关键信息,诸如消息的存储位置,这就像图书馆中书籍的书架编号,能快速定位消息所在;消息是否已被交付给消费者,类似包裹是否已送达收件人;以及是否已被消费者确认接收(ack),好比收件人是否已签收包裹。通过这些信息,RabbitMQ 能够高效地管理和调度消息,确保消息的流转有序进行。

消息存储则是以键值对的形式,将消息进行存储,并且它被所有队列共享,在每个节点中仅有一个。从技术实现角度,消息存储又可进一步划分为两类:msg_store_persistent 负责持久化消息的存储,即使 RabbitMQ 节点重启,这些消息也不会丢失,如同保险柜中的重要文件,安全可靠;msg_store_transient 负责非持久化消息的临时存储,一旦节点重启,这些消息就会消失,类似临时便签上的信息。

这两部分紧密协作,队列索引为消息存储提供了精准的索引和管理,使得在存储海量消息时,依然能够快速准确地进行消息的读写操作。当生产者发送消息时,RabbitMQ 会根据消息的属性和配置,决定将消息存储在队列索引还是消息存储中,并在队列索引中记录相关信息。而在消费者获取消息时,队列索引则能迅速定位到消息的存储位置,从消息存储中读取消息,实现高效的消息传递。

2.2 消息的写入流程

RabbitMQ 中,消息的写入流程根据消息是否持久化而有所不同,但最终都能实现消息的有效存储。

对于持久化消息,当消息到达队列时,它会被立即写入磁盘,以确保消息在 RabbitMQ 节点重启或发生故障时不会丢失。同时,为了提高消息处理的效率,在内存中也会保存一份备份。这样,在内存充足的情况下,后续对该消息的操作可以直接在内存中进行,减少磁盘 I/O 操作,提高系统性能。然而,当内存资源紧张时,内存中的备份会被清除,以释放内存空间,而磁盘上的消息依然存在,保证了消息的持久性。在消息写入磁盘的过程中,RabbitMQ 会在 ETS(Erlang Term Storage)表中记录消息在文件中的位置映射和文件的相关信息,这就像在地图上标记宝藏的位置,方便后续快速查找和读取消息。

非持久化消息一般首先只保存在内存中,因为内存的读写速度远快于磁盘,这样可以极大地提高消息的处理速度。但当内存吃紧时,为了避免系统因内存不足而出现性能问题或崩溃,这些非持久化消息会被换入到磁盘中,以节省内存空间。这种机制在保证消息快速处理的同时,也确保了系统在高负载情况下的稳定性。与持久化消息一样,非持久化消息在写入磁盘时,也会在 ETS 表中记录相关信息,以便后续操作。

此外,消息(包括消息体、属性和 headers)的存储位置还与消息的大小有关。默认情况下,可以通过配置参数 queue_index_embed_msgs_below 来界定消息大小。当一个消息的整体大小(包括消息体、属性及 headers)小于设定的大小阈值(默认值为 4096B)时,该消息可以直接存储在 rabbit_queue_index 中,这样可以减少一次磁盘 I/O 操作,提高性能;而当消息大小超过这个阈值时,消息会被存储在 rabbit_msg_store 中。

2.3 消息的读取过程

当消费者请求获取消息时,RabbitMQ 会依据消息的 ID(msg_id)来查找对应的存储文件,这就如同在图书馆中根据书籍编号查找书籍一样。如果对应的文件存在且未被其他操作锁住,RabbitMQ 会直接打开该文件,并从文件中指定的位置读取消息的内容,高效地将消息传递给消费者。

然而,当遇到文件不存在或者文件被锁住的情况时,RabbitMQ 会将读取请求发送给 rabbit_msg_store 进行处理。rabbit_msg_store 会协调相关资源,解决文件不可用的问题。如果文件不存在,可能是由于消息存储的文件结构发生了变化,比如文件被合并或者删除,rabbit_msg_store 会根据 ETS 表中的记录和其他相关信息,重新定位消息可能存储的位置;如果文件被锁住,说明当前有其他操作正在对该文件进行读写,rabbit_msg_store 会等待文件解锁,或者采取其他策略,如从备份文件中读取消息,以确保消费者能够尽快获取到所需消息。

在这个过程中,ETS 表中的记录起着至关重要的作用。它不仅记录了消息在文件中的位置映射,还包含了文件的相关信息,如文件的大小、创建时间、修改时间等。通过这些信息,RabbitMQ 能够快速准确地定位和读取消息,即使在复杂的存储环境下,也能保证消息读取的高效性和可靠性。

2.4 消息的删除机制

在 RabbitMQ 中,消息的删除操作并非简单地直接从存储文件中移除消息,而是一个更为精细的过程,涉及 ETS 表和文件层面的协同操作。

当执行消息删除操作时,首先会从 ETS 表中删除指定消息的相关信息,这就好比从图书馆的目录中删除某本书籍的记录。同时,会更新消息对应的存储文件的相关信息,标记该消息在文件中的位置为可覆盖,即标记为垃圾数据。但此时,消息在存储文件中依然存在,并没有被真正删除,这样做是为了避免频繁的文件写入操作,提高系统性能。

当一个文件中所有的消息都被标记为垃圾数据时,说明这个文件已经没有任何有效信息,此时可以将该文件删除,释放磁盘空间。此外,RabbitMQ 还会定期检测存储文件的情况。当检测到前后两个文件中的有效数据可以合并在一个文件中,并且所有垃圾数据的大小和所有文件(至少有 3 个文件存在的情况下)的数据大小的比值超过设置的阈值 GARBAGE_FRACTION(默认值为 0.5)时,就会触发垃圾回收机制,将这两个文件合并。在合并过程中,RabbitMQ 会首先锁定这两个文件,防止其他操作干扰,然后先对前面文件中的有效数据进行整理,再将后面文件的有效数据写入到前面的文件中,同时更新消息在 ETS 表中的记录,确保索引的准确性,最后删除后面的文件,完成文件的合并和垃圾回收。

2.5 性能优化策略

为了提升消息存储的性能,RabbitMQ 采用了一系列行之有效的策略:

  • 操作引用计数:RabbitMQ 为每个消息维护一个引用计数。当消息被多个消费者订阅或者在复杂的路由规则下被多次转发时,引用计数会相应增加。通过引用计数,RabbitMQ 可以准确地判断消息何时可以被真正删除。只有当引用计数降为 0 时,才会执行删除操作,避免了不必要的删除操作和数据不一致的问题,提高了系统的稳定性和性能。
  • 并发读:在高并发场景下,多个消费者可能同时请求读取消息。RabbitMQ 通过优化文件读取机制,支持并发读操作。它采用了多线程或者异步 I/O 等技术,使得多个读取请求可以同时进行,而不会相互阻塞。例如,在读取消息存储文件时,会采用高效的文件读取算法,如预读机制,提前将可能需要读取的数据加载到内存中,减少磁盘 I/O 等待时间,提高并发读的性能。
  • 消息缓存:RabbitMQ 设置了消息缓存机制。对于频繁访问的消息,会将其缓存到内存中,这样当再次请求这些消息时,可以直接从内存中获取,大大减少了磁盘 I/O 操作,提高了消息的读取速度。同时,会根据消息的访问频率和时间等因素,采用合适的缓存淘汰策略,如 LRU(最近最少使用)算法,确保缓存中的消息都是最有可能被再次访问的,有效地利用内存资源,提升整体性能。
相关推荐
退役小学生呀7 小时前
十九、云原生分布式存储 CubeFS
分布式·docker·云原生·容器·kubernetes·k8s
smileNicky7 小时前
Kafka 为什么具有高吞吐量的特性?
分布式·kafka
小白不想白a13 小时前
【Hadoop】HDFS 分布式存储系统
hadoop·分布式·hdfs
随心............15 小时前
Spark面试题
大数据·分布式·spark
Hello.Reader17 小时前
用一根“数据中枢神经”串起业务从事件流到 Apache Kafka
分布式·kafka·apache
在未来等你19 小时前
RabbitMQ面试精讲 Day 27:常见故障排查与分析
中间件·面试·消息队列·rabbitmq
找不到、了20 小时前
常用的分布式ID设计方案
java·分布式
奔跑吧邓邓子1 天前
RabbitMQ深度剖析:从基础到高级进阶实战
rabbitmq·高级进阶
AKAMAI1 天前
在分布式计算区域中通过VPC搭建私有网络
人工智能·分布式·云计算
你我约定有三2 天前
RabbitMQ--消费端异常处理与 Spring Retry
spring·rabbitmq·java-rabbitmq