NMS.ActiveMQ 虚拟目的地
虚拟目的地 允许我们创建逻辑目的地,客户端可以使用这些目的地进行生产和消费,但这些目的地映射到一个或多个 物理目的地。它使我们能够提供更灵活的松散耦合消息传递配置。
虚拟主题
发布订阅 背后的理念很棒。允许生产者与消费者分离,以便他们甚至不知道有多少消费者对他们发布的消息感兴趣。JMS 规范定义了对持久主题的支持,但它们有一些局限性,我们将在下面描述......
JMS 持久主题的局限性
一个 JMS 持久订阅者 MessageConsumer 是使用唯一的 JMS 客户端 ID 和持久订阅者名称创建的。为了符合 JMS 规范,在任何时间点,对于一个 JMS 客户端 ID,只能有一个 JMS 连接处于活动状态,并且对于一个客户端 ID 和订阅者名称,只能有一个消费者处于活动状态。即,只有一个线程可以从给定的逻辑主题订阅者主动消费。这意味着我们无法实现
- 消息的负载均衡。
- 如果运行那个消费者线程的进程死亡,订阅者的快速故障转移。
现在,JMS 中的队列语义提供了一种可靠的方式,可以在多个消费者之间负载均衡工作 - 允许许多线程、进程和机器用于处理消息。然后,我们拥有像 消息组 这样的复杂粘性负载均衡技术,以在保持顺序的同时负载均衡和并行化工作。
对于每个逻辑主题订阅者都有物理队列的另一个好处是,我们可以通过 JMX 监控队列深度,以监控系统性能,同时还可以浏览这些物理队列。
虚拟主题来拯救
虚拟主题背后的理念是,生产者以通常的 JMS 方式发送到主题。消费者可以继续使用 JMS 规范中的主题语义。但是,如果主题是虚拟的,消费者可以从逻辑主题订阅的物理队列中消费,允许许多消费者在许多机器和线程上运行以负载均衡负载。
例如,假设我们有一个名为 VirtualTopic.Orders
的主题。(其中前缀 VirtualTopic. 表示它是一个虚拟主题)。并且我们逻辑上想要将订单发送到系统 A 和 B。现在,使用常规的持久主题,我们将为 clientID_A 和“A”创建一个 JMS 消费者,以及为 clientID_B 和“B”创建一个 JMS 消费者。
使用虚拟主题,我们只需直接消费到队列 Consumer.A.VirtualTopic.Orders
即可成为系统 A 的消费者,或者消费到 Consumer.B.VirtualTopic.Orders
即可成为系统 B 的消费者。
现在,我们可以为每个系统拥有一个消费者池,然后这些消费者竞争系统 A 或 B 的消息,以便为系统 A 处理的所有消息都恰好处理一次,同样地,为系统 B 处理的所有消息都恰好处理一次。
自定义开箱即用的默认值
开箱即用的默认值如上所述。即,唯一可用的虚拟主题必须在 VirtualTopic.>
命名空间内,并且消费者队列的命名方式为 Consumer.*.VirtualTopic.>
。
您可以配置它以使用您想要的任何命名约定。以下 示例 显示了如何使所有主题都成为虚拟主题。以下示例使用名称 >
来表示“匹配所有主题”。您可以使用此通配符在不同的层次结构中应用不同的虚拟主题策略。
<destinationInterceptors>
<virtualDestinationInterceptor>
<virtualDestinations>
<virtualTopic name=">" prefix="VirtualTopicConsumers.*." selectorAware="false"/>
</virtualDestinations>
</virtualDestinationInterceptor>
</destinationInterceptors>
请注意,将主题设为虚拟主题确实会在将消息发送到主题时增加少量的 CPU 负载,但这相对较小。
选项 | 默认值 | 描述 |
---|---|---|
selectorAware | false | 只有与现有订阅者之一匹配的消息才会实际被分发。使用此选项可以防止在独占消费者使用选择器时累积不匹配的消息 |
local | false | 如果为 true,则不要将通过网络接收的消息进行扇出 |
concurrentSend | false | 如果为 true,则使用执行器进行扇出,以便发送并行发生。这允许日志文件批处理写入,从而减少磁盘 IO(5.12) |
transactedSend | false | 如果为 true,则对扇出发送使用事务,以便只有一个磁盘同步。如果不存在客户端事务,则会创建一个本地代理事务(5.13) |
复合目的地
复合目的地允许对单个目的地进行一对多关系;主要用例是用于 复合队列。例如,当消息发送到队列 A 时,您可能希望将其也转发到队列 B 和 C 以及主题 D。然后,复合目的地是将虚拟目的地映射到其他物理目的地集合。在这种情况下,映射是在代理端完成的,客户端不知道目的地之间的映射。这与客户端侧的 复合目的地 不同,在客户端侧的复合目的地中,客户端使用 URL 符号来指定必须将消息发送到的实际物理目的地。
以下 示例 显示了如何在 XML 配置中设置 <compositeQueue/>
元素,以便当消息发送到 MY.QUEUE
时,它实际上被转发到物理队列 FOO
和主题 BAR
。
<destinationInterceptors>
<virtualDestinationInterceptor>
<virtualDestinations>
<compositeQueue name="MY.QUEUE">
<forwardTo>
<queue physicalName="FOO" />
<topic physicalName="BAR" />
</forwardTo>
</compositeQueue>
</virtualDestinations>
</virtualDestinationInterceptor>
</destinationInterceptors>
默认情况下,订阅者无法直接从复合队列或主题消费消息 - 它只是一个逻辑结构。鉴于上述配置,订阅者只能从 FOO
和 BAR
消费消息;但不能从 MY.QUEUE
消费消息。
此行为可以更改,以实现诸如通过将相同的消息发送到通知主题(线路监听)来监视队列之类的用例,方法是将可选设置的 forwardOnly
属性设置为 false。
<compositeQueue name="IncomingOrders" forwardOnly="false">
<forwardTo>
<topic physicalName="Notifications" />
</forwardTo>
</compositeQueue>
发送到 IncomingOrders
的消息都将被复制并转发到 Notifications
,然后放置在物理 IncomingOrders
队列中,供订阅者消费。
在未定义或设置为 true
的 forwardOnly
属性的情况下,compositeQueue
和 compositeTopic
之间没有逻辑区别 - 它们可以互换使用。只有当复合目的地通过使用 forwardOnly
成为物理目的地时,compositeTopic
/compositeQueue
的选择才会对行为产生影响。
使用过滤的目的地
从 Apache ActiveMQ 4.2 开始,您现在可以使用选择器来定义虚拟目的地。
您可能希望创建一个虚拟目的地,它将消息转发到多个目的地,但首先应用选择器来决定消息是否真的必须转到特定目的地。
以下示例显示了发送到虚拟目的地 MY.QUEUE
的消息将在选择器匹配时被转发到 FOO
和 BAR
<destinationInterceptors>
<virtualDestinationInterceptor>
<virtualDestinations>
<compositeQueue name="MY.QUEUE">
<forwardTo>
<filteredDestination selector="odd = 'yes'" queue="FOO"/>
<filteredDestination selector="i = 5" topic="BAR"/>
</forwardTo>
</compositeQueue>
</virtualDestinations>
</virtualDestinationInterceptor>
</destinationInterceptors>
避免代理网络中的重复消息
您必须确保在使用基于队列的订阅者和非基于队列的订阅者(即,如果对虚拟主题有正常的主题订阅者)同时订阅虚拟主题时,发送到 Consumer.*.VirtualTopic.>
目的地的消息不会被转发。如果您在代理网络中使用虚拟主题,则在使用默认网络配置时,您可能会收到重复的消息。这是因为网络节点不仅会转发发送到虚拟主题的消息,还会转发关联的物理队列。要解决此问题,您应该禁用转发关联物理队列上的消息。
以下是如何执行此操作的示例
<networkConnectors>
<networkConnector uri="static://(tcp://127.0.0.1:61617)">
<excludedDestinations>
<queue physicalName="Consumer.*.VirtualTopic.>"/>
</excludedDestinations>
</networkConnector>
</networkConnectors>