虚拟目的地
虚拟目的地允许我们创建客户端可以使用来生产和消费的逻辑目的地,但这些目的地映射到一个或多个物理目的地。它使我们能够提供更灵活的松散耦合消息传递配置。
虚拟主题
发布订阅背后的想法非常好。允许生产者与消费者分离,这样他们甚至不知道有多少消费者对他们发布的消息感兴趣。JMS规范定义了对持久主题的支持,但正如我们将描述的那样,它们有一些限制。
JMS 持久主题的限制
JMS 持久订阅者 MessageConsumer 是使用唯一的 JMS clientID 和持久订阅者名称创建的。为了符合 JMS 规范,任何时候只能有一个 JMS 连接对一个 JMS clientID 处于活动状态,而且对于一个 clientID 和订阅者名称,只能有一个消费者处于活动状态。即,只有一个线程可以从给定的逻辑主题订阅者主动消费。这意味着我们无法实现
- 消息的负载均衡。
- 如果运行该消费者线程的单个进程死亡,则订阅者的快速故障转移。
现在,JMS 中的队列语义提供了以可靠的方式跨多个消费者负载均衡工作的能力——允许使用多个线程、进程和机器来处理消息。然后,我们有像消息组这样的复杂粘性负载均衡技术,可以在保持排序的同时负载均衡和并行化工作。
使用物理队列来代替每个逻辑主题订阅者带来的另一个好处是,我们可以通过JMX监控队列深度,以监控系统性能,同时还能浏览这些物理队列。
虚拟主题来拯救
虚拟主题背后的想法是,生产者以通常的 JMS 方式发送到主题。消费者可以继续使用 JMS 规范中的主题语义。但是,如果主题是虚拟的,消费者可以从逻辑主题订阅的物理队列消费,从而允许许多消费者在许多机器和线程上运行以负载均衡负载。
例如,假设我们有一个名为VirtualTopic.Orders的主题。(其中前缀 VirtualTopic. 表示它是虚拟主题)。从逻辑上讲,我们想将订单发送到系统 A 和 B。现在,使用常规持久主题,我们将为 clientID_A 和“A”以及 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,则使用执行器来扇出,以便发送并行执行。这允许日志对写入进行批处理,这将减少磁盘 I/O (5.12) |
transactedSend | false | 如果为 true,则对扇出发送使用事务,这样就只有一次磁盘同步。如果没有客户端事务,则会创建本地代理事务 (5.13) |
dropOnResourceLimit | false | 如果为 true,则忽略扇出过程中引发的任何 ResourceAllocationException(参见:sendFailIfNoSpace 策略条目)(5.16) |
setOriginalDestination | true | 如果为 true,则转发消息上的目的地将设置为消费者队列,而 originalDestination 消息属性会跟踪虚拟主题 (5.16) |
VirtualSelectorCacheBrokerPlugin
当 selectorAware=true 时,仅考虑活动消费者以进行选择器匹配。如果消费者断开连接并重新连接,它们将错过消息。selectorAware=true 的目的是防止消息累积。virtualSelectorCacheBrokerPlugin 提供了一个缓存,该缓存跟踪消费者与目的地关联的选择器,以便它们在没有该消费者的情况下应用。这样,只有选定的消息才会累积。现有的选择器集可以持久化,以便在重新启动时恢复。此插件以通常的方式应用于 plugins 部分。代码块
<plugins>
<virtualSelectorCacheBrokerPlugin persistFile="<some path>/selectorcache.data" />
</plugins>
注意:persistFile 选项使用 java 序列化,应使用适当的 jdk.serialFilter 锁定,该过滤器允许 ConcurrentHashMap
复合目的地
复合目的地允许对单个目的地进行一对多关系;主要用例是复合队列。例如,当消息发送到队列 A 时,您可能希望将其也转发到队列 B 和 C 以及主题 D。复合目的地是虚拟目的地到其他物理目的地集合的映射。在这种情况下,映射是在代理端,客户端不知道目的地之间的映射。这与客户端复合目的地不同,在客户端复合目的地中,客户端使用 URL 表示法来指定必须将消息发送到的实际物理目的地。
以下示例展示了如何在 XML 配置中设置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
队列上,供订阅者消费。
如果forwardOnly
属性未定义或设置为true
,则compositeQueue
和compositeTopic
之间没有逻辑上的区别——它们可以互换使用。只有当复合目的地通过使用forwardOnly
变为物理时,compositeTopic
/compositeQueue
的选择才会对行为产生影响。
使用过滤的目的地
从 Apache ActiveMQ Classic 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>
避免代理网络中的重复消息
TLDR: 桥接消费者队列或虚拟主题,不要两者都桥接。
通常,您会网络化消费者队列。在这种情况下,重要的是不要在虚拟主题上桥接任何普通的主题消费者,因为任何转发的消息都将再次扇出到网络代理上的消费者队列,导致重复。
也可以桥接虚拟主题,在这种情况下,有必要从任何网络连接器配置中排除消费者队列。
以下是如何排除虚拟主题消费者队列的示例
<networkConnectors> <networkConnector uri="static://([tcp://127.0.0.1:61617](tcp://127.0.0.1:61617))">
<excludedDestinations>
<queue physicalName="Consumer.*.VirtualTopic.>"/>
</excludedDestinations>
</networkConnector> </networkConnectors>