RabbitMQ系列三 (深刻消息队列)

消息持久化是 RabbitMQ 最为人津津乐道的特性之一, RabbitMQ 可以在付出最小的性能代价的基础上实现消息的持久化,最大的奥秘就在于 RabbitMQ 多层消息队列的设计上。下面,本文就从 MessageQueue 的设计和消息在 MessageQueue 的生命周期两个方面全面介绍  RabbitMQ 的消息队列。

   RabbitMQ彻底实现了AMQP协议,相似于一个邮箱服务。Exchange负责根据ExchangeTypeRoutingKey将消息投递到对应的消息队列中,消息队列负责在消费者获取消息前暂存消息。在RabbitMQ中,MessageQueue主要由两部分组成,一个为AMQQueue,主要负责实现AMQP协议的逻辑功能。另一个是用来存储消息的BackingQueue,本文重点关注的是BackingQueue的设计。 性能

   RabbitMQ系列三 (深刻消息队列) - 网易杭研后台技术中心 - 网易杭研后台技术中心的博客

    RabbitMQBackingQueue又由5个子队列组成:Q1Q2DeltaQ3Q4RabbitMQ中的消息一旦进入队列,不是固定不变的,它会随着系统的负载在队列中不断流动,消息的状态不断发生变化。RabbitMQ中的消息一共有5种状态: spa

   a)Alpha:消息的内容和消息索引都保存在内存中; 设计

   b)Beta:消息内容保存在磁盘上,消息索引保存在内存中; 索引

   c)Gamma:消息内容保存在磁盘上,消息索引在磁盘和内存都有; 生命周期

   d)Delta:消息内容和索引都在磁盘上; 队列

   注意:对于持久化的消息,消息内容和消息索引都必须先保存到磁盘上,才会处于上述状态中的一种,而Gamma状态的消息只有持久化的消息才会有该状态。 内存

      BackingQueue 中的 5 个子队列中的消息状态, Q1 Q4 对应的是 Alpha 状态, Q2 Q3 Beta 状态, Delta 对应的是 Delta 状态。上述就是 RabbitMQ 的多层队列结构的设计,咱们能够看出从 Q1 Q4 ,基本经历的是由 RAM DISK ,再到 RAM 的设计。这样的设计的好处就是当队列负载很高的状况下,可以经过将一部分消息由磁盘保存来节省内存空间,当负载下降的时候,这部分消息又渐渐回到内存,被消费者获取,使得整个队列有很好的弹性。下面咱们就来看一下,整个消息队列的工做流程。
     引发消息流动主要有两方面的因素:其一是消费者获取消息;其二是因为内存不足,引发消息的换出到磁盘上( Q1-.>Q2 Q2->Delta Q3->Delta Q4->Q3 )。 RabbitMQ 在系统运行时会根据消息传输的速度计算一个当前内存中可以保存的最大消息数量( Target_RAM_Count ),当内存中的消息数量大于该值时,就会引发消息的流动。进入队列的消息,通常会按着 Q1->Q2->Delta->Q3->Q4 的顺序进行流动,可是并非每条消息都必定会经历全部的状态,这个取决于当时系统的负载情况。
       当消费者获取消息时,首先会从 Q4 队列中获取消息,若是 Q4 获取成功,则返回,若是 Q4 为空,则尝试从 Q3 获取消息;首先,系统会判断 Q3 队列是否为空,若是为空,则直接返回队列为空,即此时队列中无消息(后续会论证)。若是不为空,则取出 Q3 的消息,而后判断此时 Q3 Delta 队列的长度,若是都为空,则可认为 Q2 Delta Q3 Q4 所有为空 ( 后续说明 ) ,此时将 Q1 中消息直接转移到 Q4 中,下次直接从 Q4 中获取消息。若是 Q3 为空, Delta 不空,则将 Delta 中的消息转移到 Q3 中;若是 Q3 非空,则直接下次从 Q3 中获取消息。在将 Delta 转移到 Q3 的过程当中, RabbitMQ 是按照索引分段读取的,首先读取某一段,直到读到的消息非空为止,而后判断读取的消息个数与 Delta 中的消息个数是否相等,若是相等,则判定此时 Delta 中已无消息,则直接将 Q2 和刚读到的消息一并放入 Q3 中。若是不相等,则仅将这次读到的消息转移到 Q3 中。这就是消费者引发的消息流动过程。
    RabbitMQ系列三 (深刻消息队列) - 网易杭研后台技术中心 - 网易杭研后台技术中心的博客
     下面咱们分析一下因为内存不足引发的消息换出。消息换出的条件是内存中保存的消息数量 + 等待 ACK 的消息的数量 >Target_RAM_Count 。当条件触发时,系统首先会判断若是当前进入等待 ACK 的消息的速度大于进入队列的消息的速度时,会先处理等待 ACK 的消息。步骤基本上 Q1->Q2 或者 Q3 移动,取决于 Delta 队列是否为空。 Q4->Q3 移动, Q2 Q3 Delta 移动。
    最后,咱们来分析一下前面遗留的两个问题,一个是为何 Q3 队列为空便可认定整个队列为空。试想若是 Q3 为空, Delta 不空,则在 Q3 取出最后一条消息时, Delta 上的消息就会被转移到 Q3 上,与 Q3 空矛盾。若是 Q2 不空,则在 Q3 取出最后一条消息,若是 Delta 为空时,会将 Q2 的消息并入 Q3 ,与 Q3 为空矛盾。若是 Q1 不空,则在 Q3 取出最后一条消息,若是 Delta Q3 均为空时,则将 Q1 的消息转移到 Q4 中,与 Q4 为空矛盾。这也解释了另一个问题,即为何 Q3 Delta 为空, Q2 就为空。
    上述就是整个消息在 RabbitMQ 队列中流动过程。从上述流程能够看出,消息若是可以被尽早消费掉,就不须要经历持久化的过程,由于这样会加系统的开销。若是消息被消费的速度过慢, RabbitMQ 经过换出内存的方式,防止内存溢出。
相关文章
相关标签/搜索