Chapter 28. 消息分组

消息组是具有下列特性的消息集合:

消息组在需要同一个接收者按顺序处理某类消息的时候很有用。

一支股票的订购就是一个例子。某支股票的订单需要同一个接收者按顺序处理。于是可以每支股票有一个接收者 来处理(也可以用少一些的接收者),然后将每支股票的名字设在消息的_HQ_GROUP_ID参数中。

这样可以保证一支股票的消息只被同一个接收者处理。

28.1. 使用核心接口

用来标识一个消息组的参数是 "_HQ_GROUP_ID"" (或者相应的常量MessageImpl.HDR_GROUP_ID)。另一种方法是在SessionFactory 中将autogroup设置为true。这样做的话组id是随机给出的。

28.2. 使用JMS

用来标识一个消息组的参数是JMSXGroupID

 // send 2 messages in the same group to ensure the same
 // consumer will receive both
 Message message = ...
 message.setStringProperty("JMSXGroupID", "Group-0");
 producer.send(message);

 message = ...
 message.setStringProperty("JMSXGroupID", "Group-0");
 producer.send(message);          
       

另一个方法是将HornetQConnectonFactoryautogroup 属性设为true,或者在hornetq-jms.xml文件中进行配置:

<connection-factory name="ConnectionFactory">
      <connectors>
         <connector-ref connector-name="netty-connector"/>
      </connectors>
      <entries>
         <entry name="ConnectionFactory"/>
      </entries>
      <autogroup>true</autogroup>
</connection-factory>

还可以通过连接工厂来设置组id。来自这个连接工厂的所有的发送者(producer)发送的消息的JMSXGroupID将具有指定的值。这种方法需要在hornetq-jms.xml 文件中作如下配置:

         <connection-factory name="ConnectionFactory">
      <connectors>
         <connector-ref connector-name="netty-connector"/>
      </connectors>
      <entries>
         <entry name="ConnectionFactory"/>
      </entries>
      <group-id>Group-0</group-id>
   </connection-factory>
      

28.3. 例子

参见Section 11.1.29, “消息组”。这个例子展示的是在JMS中如何配置与使用消息组。

28.4. 例子

Section 11.1.30, “消息组(例2)”是另外一个消息组的例子,在这个例子中通过配置连接工厂 来使用消息组。

28.5. 集群中的消息组

在集群中使用消息组是相对比较复杂的。这是因在在集群中,一个消息组中的消息有可能被送到集群中的任一全节点, 这就要求每个节点都要知道这个消息是属于哪个节点上的哪个接收者(consumer)。一个消息组的消息往往会被发送到 集群中的一个节点,而该消息组的接收者在另一个节点上。每个节点都要知道这些细节以便能将消息正确路由到所属接收 者所在的节点上。

为了解决上述问题,我们使用了消息组处理器。每个节点都有一个自己的消息组处理器。当一个带有组id的消息收到时, 这些节点的消息组处理器就会协同作出决定该如何对这个消息进行路由。

消息组处理器有两种:本地消息组处理器和远程消息组处理器。在一个集群中要选择一个节点作为本地消息组处理器的 节点,集群中所有其它的节点都持有远程消息组处理器。在集群中由本地消息组处理器最終决定消息怎样路由,其它的远程 处理器配合本地处理器完成决策。消息组处理器的配置在hornetq-configuration.xml 文件中,下面就是一个例子:

   <grouping-handler name="my-grouping-handler">
      <type>LOCAL</type>
      <address>jms</address>
      <timeout>5000</timeout>
   </grouping-handler>

   <grouping-handler name="my-grouping-handler">
      <type>REMOTE</type>
      <address>jms</address>
      <timeout>5000</timeout>
   </grouping-handler>

address属性表示一个集群的连接以及它使用的地址。有关如何配置集群 参见集群章节。timeout属性代表做出路由决定所需要等待的时间。如果超过 了这个时间还没有做出决定,则会抛出异常。这可以保证严格的顺序。

收到消息的节点会首先提出一个消息路由的建议。它采用轮询方式来选择一个合适的路由。它首先选择一个本地的队列,之后 再选择一个有接收者的队列。如果这个建议被所有组处理器接受,消息就会被路由到所选的队列。如果被拒绝就提出另一个路 由方案,如此反复直到方案被接受为止。队列选择后所有其它的节点都将消息路由到这个队列。这样所有的消息组的消息在一个 节点上进行处理,也就是该节点上的接收者接收所有的同组的消息。

由于只有一个本地处理器,如果它的节点出现故障则无法做出决定。这时所有的消息将不能被传递,并且会抛出异常。 为了避免这一单点故障,本地处理器可以在备份节点上有一个复本。只要创建备份节点并配置一个相同的本地处理器即可。

28.5.1. 集群消息组的最佳使用惯例

下面是一些很好的建议:

  1. 尽可能使接收者均匀分布在不同的节点上。由于消息组的消息总是传递到同一个队列的同一个接收者, 如果你经常性地创建与关闭接收者,就可能出现消息由于没有接收者而传递不出去,造成消息在队列中 不断积累的情况。因此,尽量要避免关闭接收者,或者确保有足够数量的接收者。例如,如果你的集群 有3个节点,那么就创建3个接收者。

  2. 尽可能使用持久型队列。如果消息组的消息与一个队列绑定,一旦这个队列被删除,其它节点可能仍然 尝试向这个已删除的队列路由消息。为避免这种情况,要确保这个队列由发送消息的会话来删除。这样如果 下一个消息发出后发现原来的队列被删除,新的路由建议就会提出。另外一种方案是你可以重新使用一个不 同的组ID。

  3. 一定要确保本地的消息组处理器有备份。这样在有故障时消息组仍然可以正常工作。

28.5.2. 集群消息组例子

参见Section 11.1.6, “集群分组”,这个例子给出了如何在HornetQ集群中配置消息组。