异步消费者
Spring AMQP 还通过使用 @RabbitListener 注解支持注解式监听端点,并提供了一个开放的基础架构来以编程方式注册端点。这是迄今为止设置异步消费者最方便的方式。更多细节请参阅注解驱动的监听端点。
prefetch 的默认值过去是 1,这可能导致高效消费者得不到充分利用。从 2.0 版开始,默认 prefetch 值现在是 250,这在大多数常见场景下应能让消费者保持忙碌,从而提高吞吐量。
尽管如此,在某些场景下 prefetch 值应当设得较低:
- 对于大消息,尤其是处理较慢时(消息在客户端进程中可能累积占用大量内存)
- 当需要严格的消息顺序时(这种情况下应把 prefetch 值设回 1)
- 其他特殊情况
此外,对于低容量消息以及多个消费者(包括单个监听容器实例内的并发),你可能希望减小 prefetch,以让消息在各消费者之间分布更均匀。
请参阅消息监听容器配置。
有关 prefetch 的更多背景,请参阅这篇关于 RabbitMQ 中消费者利用率 的文章,以及这篇关于排队论的文章。
消息监听器
对于异步 Message 接收,涉及的是一个专用组件(而非 AmqpTemplate)。该组件是一个承载 Message 消费回调的容器。我们将在本节后面讨论容器及其属性。不过首先,我们应当看看回调,因为你的应用代码正是在那里与 messaging 系统集成。回调有几种选择,首先是 MessageListener 接口的实现,如下清单所示:
public interface MessageListener {
void onMessage(Message message);
}
如果你的回调逻辑因任何原因依赖于 AMQP Channel 实例,则可以改用 ChannelAwareMessageListener。它看起来类似,但多一个参数。下面的清单展示了 ChannelAwareMessageListener 接口定义:
public interface ChannelAwareMessageListener {
void onMessage(Message message, Channel channel) throws Exception;
}
在 2.1 版中,此接口从包 o.s.amqp.rabbit.core 移到了 o.s.amqp.rabbit.listener.api。
MessageListenerAdapter
如果你更喜欢在应用逻辑与 messaging API 之间保持更严格的分离,则可以依赖框架提供的一个适配器实现。这通常被称为"消息驱动 POJO"支持。
1.5 版为 POJO messaging 引入了一种更灵活的机制,即 @RabbitListener 注解。更多信息请参阅注解驱动的监听端点。
使用该适配器时,你只需提供适配器本身应当调用的实例的引用。下面的示例展示了如何这样做:
MessageListenerAdapter listener = new MessageListenerAdapter(somePojo);
listener.setDefaultListenerMethod("myMethod");
你可以继承该适配器并提供 getListenerMethodName() 的实现,以根据消息动态选择不同的方法。该方法有两个参数:originalMessage 和 extractedMessage,后者是任何转换的结果。默认配置了一个 SimpleMessageConverter。更多信息以及其他可用转换器的信息,请参阅 SimpleMessageConverter。
从 1.4.2 版开始,原始消息具有 consumerQueue 和 consumerTag 属性,可用于确定消息是从哪个队列接收的。
从 1.5 版开始,你可以配置一个由消费者队列或 tag 到方法名的映射,以动态选择要调用的方法。如果映射中没有条目,则回退到默认监听方法。默认监听方法(若未设置)是 handleMessage。
从 2.0 版开始,提供了一个便捷的 FunctionalInterface。下面的清单展示了 FunctionalInterface 的定义:
@FunctionalInterface
public interface ReplyingMessageListener<T, R> {
R handleMessage(T t);
}
这个接口便于使用 Java 8 lambda 来便捷地配置适配器,如下例所示:
new MessageListenerAdapter((ReplyingMessageListener<String, String>) data -> {
...
return result;
}));
从 2.2 版开始,buildListenerArguments(Object) 已被废弃,取而代之引入了新的 buildListenerArguments(Object, Channel, Message)。新方法帮助监听器获得 Channel 和 Message 参数以做更多事情,例如在手动确认模式下调用 channel.basicReject(long, boolean)。下面的清单展示了最基本的示例:
public class ExtendedListenerAdapter extends MessageListenerAdapter {
@Override
protected Object[] buildListenerArguments(Object extractedMessage, Channel channel, Message message) {
return new Object[]{extractedMessage, channel, message};
}
}
现在,如果你需要接收 "channel" 和 "message",就可以像配置 MessageListenerAdapter 一样配置 ExtendedListenerAdapter。监听器的参数应当按 buildListenerArguments(Object, Channel, Message) 的返回值来设置,如下例监听器所示:
public void handleMessage(Object object, Channel channel, Message message) throws IOException {
...
}
容器
既然你已经了解了监听 Message 的回调的各种选择,我们现在可以把注意力转向容器。基本上,容器处理"主动"的职责,从而让监听回调保持"被动"。容器是一个"生命周期"组件的例子。它提供启动和停止的方法。配置容器时,你本质上是在一个 AMQP 队列与 MessageListener 实例之间架起桥梁。你必须提供 ConnectionFactory 的引用,以及该监听器应当从中消费消息的队列名或 Queue 实例。
在 2.0 版之前,只有一个监听容器,即 SimpleMessageListenerContainer。现在有了第二个容器,即 DirectMessageListenerContainer。容器之间的差异以及你在选择使用哪一个时可能采用的标准,在选择容器中描述。
下面的清单展示了最基本的示例,它通过使用 SimpleMessageListenerContainer 来工作:
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(rabbitConnectionFactory);
container.setQueueNames("some.queue");
container.setMessageListener(new MessageListenerAdapter(somePojo));
作为一个"主动"组件,最常见的做法是用 bean 定义来创建监听容器,以便它能在后台运行。下面的示例展示了用 XML 这样做的一种方式:
<rabbit:listener-container connection-factory="rabbitConnectionFactory">
<rabbit:listener queues="some.queue" ref="somePojo" method="handle"/>
</rabbit:listener-container>
下面的清单展示了用 XML 这样做的另一种方式:
<rabbit:listener-container connection-factory="rabbitConnectionFactory" type="direct">
<rabbit:listener queues="some.queue" ref="somePojo" method="handle"/>
</rabbit:listener-container>
前面两个示例都创建了一个 DirectMessageListenerContainer(注意 type 属性——它默认为 simple)。
或者,你可能更喜欢使用 Java 配置,它看起来与前面的代码片段类似:
@Configuration
public class ExampleAmqpConfiguration {
@Bean
public SimpleMessageListenerContainer messageListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(rabbitConnectionFactory());
container.setQueueName("some.queue");
container.setMessageListener(exampleListener());
return container;
}
@Bean
public CachingConnectionFactory rabbitConnectionFactory() {
CachingConnectionFactory connectionFactory =
new CachingConnectionFactory("localhost");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
return connectionFactory;
}
@Bean
public MessageListener exampleListener() {
return new MessageListener() {
public void onMessage(Message message) {
System.out.println("received: " + message);
}
};
}
}
消费者优先级
从 RabbitMQ 3.2 版开始,broker 现在支持消费者优先级(请参阅在 RabbitMQ 中使用消费者优先级)。这是通过在消费者上设置 x-priority 参数来启用的。SimpleMessageListenerContainer 现在支持设置消费者参数,如下例所示:
container.setConsumerArguments(Collections.
<String, Object> singletonMap("x-priority", Integer.valueOf(10)));
为方便起见,命名空间在 listener 元素上提供了 priority 属性,如下例所示:
<rabbit:listener-container connection-factory="rabbitConnectionFactory">
<rabbit:listener queues="some.queue" ref="somePojo" method="handle" priority="10" />
</rabbit:listener-container>
从 1.3 版开始,你可以在运行时修改容器监听的队列。请参阅监听容器队列。
auto-delete 队列
当容器被配置为监听 auto-delete 队列、队列带有 x-expires 选项、或在 Broker 上配置了存活时间(Time-To-Live)策略时,当容器停止(即最后一个消费者被取消)时,队列会被 broker 移除。在 1.3 版之前,由于队列缺失,容器无法重启。RabbitAdmin 仅在连接关闭或打开时才自动重新声明队列等,而容器停止和启动时并不会发生这种情况。
从 1.3 版开始,容器在启动期间使用一个 RabbitAdmin 来重新声明任何缺失的队列。
你还可以将条件声明(请参阅条件声明)与一个 auto-startup="false" 的 admin 一起使用,以把队列声明推迟到容器启动时。下面的示例展示了如何这样做:
<rabbit:queue id="otherAnon" declared-by="containerAdmin" />
<rabbit:direct-exchange name="otherExchange" auto-delete="true" declared-by="containerAdmin">
<rabbit:bindings>
<rabbit:binding queue="otherAnon" key="otherAnon" />
</rabbit:bindings>
</rabbit:direct-exchange>
<rabbit:listener-container id="container2" auto-startup="false">
<rabbit:listener id="listener2" ref="foo" queues="otherAnon" admin="containerAdmin" />
</rabbit:listener-container>
<rabbit:admin id="containerAdmin" connection-factory="rabbitConnectionFactory"
auto-startup="false" />
在这种情况下,队列和交换机由 containerAdmin 声明,它带有 auto-startup="false",以便这些元素不在上下文初始化期间被声明。同样由于这个原因,容器也不会被启动。当容器稍后启动时,它使用自己对 containerAdmin 的引用来声明这些元素。