我们希望 RabbitMQ的集群能够解决单点故障,一旦节点异常自动重连到正常的节点;希望RabbitMQ 集群可以完成负载均衡,可以保证消息不丢失,希望可以快速增加节点到集群,单个节点当掉不会给集群带来灾难影响..... 哦,停下来,我们似乎把构建健壮地可扩展系统的所有梦想都寄托在RabbitMQ之上了.事实又是怎样呢?我们不能臆测软件对一个功能的实现,最好的办法就是用一下;我们近距离看一下.

 

RabbitMQ Cluster设计目标

 

   RabbitMQ in Action 里面提到RabbitMQ两个设计目标,需要自己琢磨一下:

 

  •  allowing consumers and producers to keep running in the event one Rabbit node dies

  •  linearly scaling messaging throughput by adding more nodes. 

  即:在集群中的一个Rabbit节点当掉的时候允许Consumer和producer继续运行;以及通过增加节点的线性扩展方式增加消息吞吐能力.

 

 

   RabbitMQ会提供一些机制比如把exchange queue设置为durable,persistent mode设置为2,等等来尽可能保证消息不丢失,但是这种保证是有限的.即使你该做的都做了,RabbitMQ节点当掉还是有可能出现消息丢失的情况.这是因为RabbitMQ并没有在节点之间做数据复制(Data Replicate).没有特殊的配置(后面会提到Mirrored Queue),一个队列的数据只会出现在Queue所属的节点.

 

Message Data & Metadata

 

   如果我们把RabbitMQ看作一个处理消息的机器,那么RabbitMQ内部拥有若干基础的零件(Queue Exchange Binding Vhost),它是如何将这些概念组合起来构成一个消息组件的呢?这种问题的思考有两种方式:从软件构成的基础设施入手,看基础设施之间的配合方式;另外一种方式就是从被处理的对象入手,看对象如何被处理的;这样都可以了解软件的内部机制.对于RabbitMQ它处理的对象就是消息数据(Message)和消息路由必须的规则数据.

 

  对于RabbitMq集群我们可以从数据的视角切入,集群需要解决两种类型数据的存储:Message Data和Metadata;我们传递的消息就是 Message Data,而和消息路由相关的配置和规则型的数据被称为meta data.

 

Message Data

  

  之前提到过Message在RabbitMQ实际存储位置是Queue, 所以当我们谈Message data的时候,实际上更多的时候我们在谈Queue.这一节的题目也可以是:"How Queues behave in a cluster". 并不是所有的节点都拥有集群内某个Queue的完整副本.在单节点环境中,所有的数据元数据都会在节点内维护,all-in-one. 如果是在集群中创建一个Queue,RabbitMQ集群只会在一个节点维护该Queue的完整信息,Client连接到哪个节点就会在哪个节点上.其它节点仅仅拥有Queue的元数据和一个Queue实际数据所在节点的指针.

  所以,集群中的节点当掉,该节点的queues和相关的bingdings都会消失,连接到这个Queue的Consumer失去了订阅,任何模式匹配发送到该Queue的新消息都会被扔进黑洞.不要担心,有一些额外的消息确认策略来防止这种情况带来的消息丢失.

 

  如果Queue所在的节点当掉了,怎么办?

 

  我们可能会想:让consumer重新连接到集群重建队列不就可以了? 这样做是有条件限制的,只有当queue没有标记为durable的时候可以.如果一个Queue被声明为durable的,那么在新的节点重建这个queue会得到一个404 NOT_FOUND错误(见下图).这种集群处理策略是为了保证这个Queue中的消息在加入集群后不消失:如果一个队列之前在node1,如果允许在节点node2重建那么后续RabbitMQ就会认为这个队列在所在的位置是在node2,之前node1未被处理的消息就不会被处理了.把这个queue加入集群的唯一方法就是等待它所在的节点恢复正常加入集群.

 

 

   如果要重建的不是durable的,重新声明会成功.

 
   为什么RabbitMQ不在各节点复制所有的队列的消息内容和状态?
 
   这个首先很容易想到两个原因: [1] 额外的存储成本  [2] 性能 集群中的某个node只负责特定队列的消息,如果消息冗余就要将消息分发到所有节点,这里会有性能消耗;如果消息需要持久化仅仅是一个节点所在的机器有磁盘IO消耗,如果是多个节点都要复制所有的消息,这个消耗将出现在所有的节点上而且每增加一个节点都会加剧这个负担.

 

 

相比具体message data,meta data更像是一种"组织概念",对于这种概念首先要明确的就是其包含哪些东西:

 

metadata

  

  RabbitMQ在内部维护基础零件(Queue Exchange Binding Vhost)的元数据(metadata),在构成RabbitMQ集群之后还有集群的元数据.

 

  • Queue metadata—Queue names and their properties (are they durable or auto-delete?)

  • Exchange metadata—The exchange’s name, the type of exchange it is, and what the properties are (durable and so on)

  • Binding metadata—A simple table showing how to route messages to queues

  • Vhost metadata—Namespacing and security attributes for the queues, exchanges,and bindings within a vhost 

  • Rabbit node location 

 

   单节点的情况,RabbitMQ会在内存中维护一份元数据信息,并把标记为Durable的Queue和Exchange(以及相关bingding)持久化到磁盘.

 

   引入了集群之后,会新增几种元数据信息:集群节点位置信息(Erlang节点名包含这部分信息),以及节点之间的关系信息.集群环境中你还会面临一个选择是存储元数据到RAM Only还是Disk 磁盘,在单节点情况由于没有额外的数据冗余存在所以这个选择默认是持久化到磁盘.

 

 

 

最后小图一张,Gal Gadot 2004年度以色列小姐 速度与激情4,5惊艳全场,我们俩都是金牛座的 : )