4.1. Spring AMQP
官网翻译 from TabTan
本章探讨了使用
Spring AMQP
开发应用程序所必需的接口和类。
4.1.1. AMQP Abstractions
(抽象)
Spring AMQP
由两个模块组成(每个模块在发行版中由一个JAR表示):Spring-AMQP
和Spring-rabbit
。spring-amqp
模块包含了org.springframework.amqp.core
包。在这个包中,你可以找到代表核心AMQP
“模型”的类。我们的目的是提供不依赖于任何特定AMQP
代理实现或客户端库的通用抽象。最终用户代码可以更容易地跨供应商实现进行移植,因为它可以仅针对抽象层进行开发。这些抽象然后由特定于代理的模块实现,例如spring-rabbit
。目前只有一个RabbitMQ
实现。但是,这些抽象已经在.net
中使用Apache Qpid
和RabbitMQ
进行了验证。由于AMQP
操作在协议级别,原则上,你可以将RabbitMQ
客户端与任何支持相同协议版本的代理一起使用,但我们目前没有测试任何其他代理。本文假设您已经熟悉AMQP
规范的基础知识。如果没有,请查看其他资源中列出的资源。
Message
0-9-1
AMQP
规范没有定义&**Message
类或接口。相反,在执行诸如basicPublish()
之类的操作时,内容作为字节数组参数传递,其他属性作为单独的参数传递。Spring AMQP
将Message
类定义为更通用的AMQP
域模型表示的一部分。Message
类的目的是将主体和属性封装在单个实例中,从而使API
变得更简单。下面的例子显示了Message
**类的定义:
public class Message {
private final MessageProperties messageProperties;
private final byte[] body;
public Message(byte[] body, MessageProperties messageProperties) {
this.body = body;
this.messageProperties = messageProperties;
}
public byte[] getBody() {
return this.body;
}
public MessageProperties getMessageProperties() {
return this.messageProperties;
}
}
MessageProperties
接口定义了几个常见的属性,比如'messageId'
、'timestamp'
、'contentType'
等等。
从版本1.5.7、1.6.11、1.7.4和2.0.0开始,如果消息体是序列化的
Serializable
java
对象,那么在执行toString()
操作(例如在日志消息中)时,将不再反序列化(默认情况下)。这是为了防止不安全的反序列化。默认情况下,只支持java.util
和java.lang
类是反序列化的。要恢复到前面的行为,您可以通过调用Message.addAllowedListPatterns(…)
来添加允许的类/包模式。支持一个简单的通配符,例如com.something.
*.MyClass
。不能反序列化的数据体在日志消息中用byte[]表示。
Exchange
Exchange
接口表示AMQP
Exchange
,( 哪个消息生产者发送给它)。代理的虚拟主机中的每个Exchange
都有唯一的名称和一些其他属性。Exchange
接口示例如下:
public interface Exchange {
String getName();
String getExchangeType();
boolean isDurable();
boolean isAutoDelete();
Map<String, Object> getArguments();
}
可以看到,Exchange
也有一个“类型”,由ExchangeTypes
中定义的常量表示。基本类型是:direct
、topic
、fanout
和headers
。在核心包(org.springframework.amqp.core
)中,可以找到每种类型的Exchange
接口的实现。这些Exchange
类型的行为因处理队列绑定的方式而异。例如,Direct Exchange
(直接交换机)允许队列被固定的路由键(通常是队列名)绑定。Topic
交换机 支持使用路由模式的绑定,这些模式可能分别包含’*‘和’#‘通配符,分别表示’exactly-one’和’zero-or-more’。Fanou
t交换机 向绑定到它的所有队列发布消息,而不考虑任何路由密钥。有关这些Exchange
类型和其他Exchange
类型的更多信息,请参阅其他参考资料。
AMQP
规范还要求任何代理提供一个没有名称的“默认”直接交换。声明的所有队列都绑定到默认Exchange
,其名称作为路由键。你可以在AmqpTemplate
中了解更多关于Spring AMQP
中默认Exchange
的用法。
Queue
Queue
表示消息使用者从中接收消息的组件。与各种Exchange
一样,我们的实现旨在成为这个核心AMQP
类型的抽象表示。下面的清单显示了Queue
:
public class Queue {
private final String name;
private volatile boolean durable;
private volatile boolean exclusive;
private volatile boolean autoDelete;
private volatile Map<String, Object> arguments;
/**
* The queue is durable, non-exclusive and non auto-delete.
* 队列是持久的、非排他性的、非自动删除的。
* @param name the name of the queue.
*/
public Queue(String name) {
this(name, true, false, false);
}
// Getters and Setters omitted for brevity
}
注意,构造函数接收队列名称。根据实现的不同,管理模板可以提供生成唯一命名队列的方法。这样的队列可以用作““reply-to”地址或其他临时情况。因此,自动生成队列的'exclusive'
和'autoDelete'
属性都将被设置为'true'
。
有关使用名称空间支持声明队列(包括队列参数)的信息,请 Configuring the Broker中关于队列的部分。
Binding
假设生产者向交换机发送信息,消费者从队列接收信息,那么将队列连接到交换机的绑定对于通过消息传递连接这些生产者和消费者至关重要。在Spring AMQP
中,我们定义了一个Binding
来表示这些连接。本节回顾将队列绑定到交换机的基本选项。
你可以用一个固定的路由键将一个队列绑定到一个DirectExchange
上,如下例所示:
new Binding(someQueue, someDirectExchange, "foo.bar");
您可以使用路由模式将队列绑定到TopicExchange
,如下面的示例所示:
new Binding(someQueue, someTopicExchange, "foo.*");
你可以将一个队列绑定到一个没有路由键的FanoutExchange
,如下面的例子所示:
new Binding(someQueue, someFanoutExchange);
我们还提供了一个BindingBuilder
来促进“流畅的API
”风格,如下例所示:
Binding b = BindingBuilder.bind(someQueue).to(someTopicExchange).with("foo.*");
为了清晰起见,前面的示例显示了
BindingBuilder
类,但是当为bind()
方法使用静态导入时,这种风格工作得很好。
就其本身而言,Binding类的实例仅保存有关连接的数据。换句话说,它不是一个“active”组件。但是,正如您稍后将在 Configuring the Broker中看到的,AmqpAdmin
类可以使用Binding
实例来实际触发代理上的绑定操作。此外,正如您在同一节中所看到的,您可以通过在@Configuration
配置类中使用Spring
的@Bean
注释来定义Binding
实例。还有一个方便的基类,可以进一步简化生成与AMQP
相关的bean
定义的方法,并识别queues
、exchanges
和bindings
,以便在应用程序启动时在AMQP
代理上声明它们。
AmqpTemplate
也定义在核心包中。作为实际AMQP
消息传递中涉及的主要组件之一,它将在单独的部分中详细讨论(见AmqpTemplate
)。
4.1.2. Connection and Resource Management(连接和资源管理器)
虽然我们在前一节中描述的AMQP
模型是通用的,适用于所有实现,但当我们进入资源管理时,细节是特定于代理实现的。因此,在本节中,我们将关注只存在于spring-rabbit
模块中的代码,因为在这一点上,RabbitMQ
是唯一受支持的实现。
管理到RabbitMQ
代理的**Connection
(连接)的中心组件是ConnectionFactory
接口。ConnectionFactory
**实现的职责是提供org.springframework.amqp.rabbit.connection
的实例Connection
,它是com.rabbitmq.client.Connection
的包装器。
Choosing a Connection Factory (选择连接工厂)
一下有三个连接工厂:
PooledChannelConnectionFactory
(版本2.3.+)ThreadChannelConnectionFactory
(版本2.3.+)CachingConnectionFactory
对于大多数用例,应该使用PooledChannelConnectionFactory
。如果您希望确保严格的消息排序,而不需要使用 Scoped Operations,则可以使用ThreadChannelConnectionFactory
。如果你想使用相关的发布者确认,或者如果你想通过它的CacheMode
打开多个连接,就应该使用CachingConnectionFactory
。
这三个工厂都支持简单的发布者确认。
当配置一个RabbitTemplate
使用一个单独的连接时,你现在可以从2.3.2
版本开始,将发布连接工厂配置为不同的类型。默认情况下,发布工厂是相同类型的,并且在主工厂上设置的任何属性也会传播到发布工厂。
PooledChannelConnectionFactory
该工厂基于Apache Pool2
管理单个连接和两个通道池。一个池用于事务channels
,另一个池用于非事务channels
。这些池是默认配置的GenericObjectPool
;提供了一个回调来配置池;更多信息请参考Apache
文档。
Apache common -pool2
jar
必须在类路径上才能使用此工厂。
@Bean
PooledChannelConnectionFactory pcf() throws Exception {
ConnectionFactory rabbitConnectionFactory = new ConnectionFactory();
rabbitConnectionFactory.setHost("localhost");
PooledChannelConnectionFactory pcf = new PooledChannelConnectionFactory(rabbitConnectionFactory);
pcf.setPoolConfigurer((pool, tx) -> {
if (tx) {
// configure the transactional pool配置事务连接池
} else {
// configure the non-transactional pool配置非事务连接池
}
});
return pcf;
}
ThreadChannelConnectionFactory
这个工厂管理一个collection
(连接)和两个ThreadLocal
,一个用于事务通道,另一个用于非事务通道。该工厂确保同一线程上的所有操作使用相同的channel
(只要通道保持打开状态)。这有助于严格的消息排序,而不需要 Scoped Operations。为了避免内存泄漏,如果应用程序使用了许多短期线程,则必须调用工厂的closeThreadChannel()
来释放通道资源。从版本2.3.7
开始,线程可以将其通道传输给另一个线程。See Strict Message Ordering in a Multi-Threaded Environment for more information.
CachingConnectionFactory
提供的第三个实现是CachingConnectionFactory
,默认情况下,它建立一个可以由应用程序共享的连接代理。共享连接是可能的,因为AMQP
的消息传递的“工作单元”实际上是一个channel
(在某些方面,这类似于JMS
中连接和会话之间的关系)。连接实例提供了一个createChanne
l方法。**CachingConnectionFactory
实现支持这些channels
的缓存,并且它根据channel
是否是事务性的为channel
维护单独的缓存。在创建CachingConnectionFactory
**实例时,可以通过构造函数提供hostname
。You should also provide the username
andpassword
properties(提供账号密码). 要配置通道缓存的大小(默认为25),可以调用setChannelCacheSize()
方法。
从版本1.3
开始,您可以配置CachingConnectionFactory
来缓存collection
和仅缓存channels
。在这种情况下,每次调用createConnection()
都会创建一个新连接(或从缓存中检索一个空闲连接)。关闭连接将其返回到缓存(如果还没有达到缓存大小)。在这样的collections
上创建的channels
也会被缓存。在某些环境中,使用单独的连接可能很有用,例如从HA
集群使用负载平衡器连接到不同的集群成员等。如果要缓存连接,请将cacheMode
设置为CacheMode.CONNECTION
。
这并没有限制连接的数量。相反,它指定允许有多少空闲打开的连接。
当缓存模式为
CONNECTION
时,不支持自动声明queues
队列和其他(参见Automatic Declaration of Exchanges, Queues, and Bindings)。另外,在撰写本文时,amqp-client
库默认情况下为每个连接创建一个固定的线程池(默认大小:Runtime.getRuntime(). availableprocessors() * 2
个线程)。当使用大量连接时,您应该考虑在CachingConnectionFactory
上设置一个自定义executor
。然后,所有连接都可以使用同一个executor
,并且可以共享它的线程。执行程序的线程池应该是无界的,或者为预期的用途进行了适当的设置(通常,每个连接至少有一个线程)。如果在每个连接上创建多个channel
,那么线程池大小会影响并发性,因此变量(或简单缓存的)线程池executor
将是最合适的。
重要的是要理解缓存大小(默认情况下)不是一个限制,而仅仅是可以缓存的channels
数量。如果缓存大小为10
,那么实际上可以使用任意数量的channel
。如果正在使用的channel
超过10
个,并且它们都返回到缓存中,那么缓存中就有10
个channel
。其余的都是封闭的。
从1.6
版开始,默认channel
缓存大小已从1
增加到25
。在大容量多线程环境中,小缓存意味着channel
的创建和关闭速度快。增加默认缓存大小可以避免这种开销。你应该通过RabbitMQ
管理UI
监控正在使用的channel
,如果你看到很多channel
被创建和关闭,可以考虑进一步增加缓存大小。缓存仅按需增长(以适应应用程序的并发需求),因此此更改不会影响现有的小容量应用程序。
从版本1.4.2
开始,CachingConnectionFactory
有一个名为channelCheckoutTimeout
的属性。当此属性大于零时,channelCacheSize
将成为对可以在连接上创建的channel
数量的限制。如果达到限制,则调用线程阻塞,直到channel
可用或达到该超时,在这种情况下抛出AmqpTimeoutException
。
框架中使用的通道(例如
RabbitTemplate
)被可靠地返回到缓存中。如果在框架之外创建channel
(例如,通过直接访问连接并调用createChannel()
),则必须可靠地(通过关闭)返回它们,可能是在finally
块中,以避免耗尽channel
。
创建新连接的示例如下:
@Bean
public CachingConnectionFactory cachingConnectionFactory(){
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("somehost");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setPort(5672);
return connectionFactory;
}
When using XML, the configuration might look like the following example:
<bean id="connectionFactory"
class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
<constructor-arg value="somehost"/>
<property name="username" value="guest"/>
<property name="password" value="guest"/>
</bean>
还有一个
SingleConnectionFactory
实现,仅在框架的单元测试代码中可用。它比CachingConnectionFactory
简单,因为它不缓存channel
,但是由于它缺乏性能和弹性,因此不适合在简单测试之外的实际使用。如果出于某种原因需要实现自己的ConnectionFactory
,AbstractConnectionFactory
基类可以提供一个很好的起点。
使用rabbit命名空间
可以快速方便地创建ConnectionFactory
,如下所示:(springboot
表示 low
-)
<rabbit:connection-factory id="connectionFactory"/>
在大多数情况下,这种方法是可取的,因为框架可以为您选择最佳的默认值。创建的实例是CachingConnectionFactory
。请记住,通道的默认缓存大小是25
。如果你想要缓存更多的channel
,可以通过设置channelCacheSize
属性设置一个更大的值。在XML
中,它看起来如下所示:
<bean id="connectionFactory"
class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
<constructor-arg value="somehost"/>
<property name="username" value="guest"/>
<property name="password" value="guest"/>
<property name="channelCacheSize" value="50"/>
</bean>
同样,对于命名空间,你可以添加channel-cache-size
属性,如下所示:
<rabbit:connection-factory
id="connectionFactory" channel-cache-size="50"/>
默认的缓存模式是CHANNEL
,但是您可以将其配置为缓存连接。在下面的例子中,我们使用connection-cache-size
:
<rabbit:connection-factory
id="connectionFactory" cache-mode="CONNECTION" connection-cache-size="25"/>
可以通过命名空间提供主机和端口属性,具体如下:
<rabbit:connection-factory
id="connectionFactory" host="somehost" port="5672"/>
或者,如果运行在集群环境中,你可以使用addresses
属性,如下所示:
<rabbit:connection-factory
id="connectionFactory" addresses="host1:5672,host2:5672" address-shuffle-mode="RANDOM"/>
有关address-shuffle-mode
的信息,请参见 Connecting to a Cluster。
下面是一个自定义线程工厂的例子,它在线程名前面加上了rabbitmq-
:
<rabbit:connection-factory id="multiHost" virtual-host="/bar" addresses="host1:1234,host2,host3:4567"
thread-factory="tf"
channel-cache-size="10" username="user" password="password" />
<bean id="tf" class="org.springframework.scheduling.concurrent.CustomizableThreadFactory">
<constructor-arg value="rabbitmq-" />
</bean>
在配置类中它看起来如下所示:
@Bean
public CachingConnectionFactory cachingConnectionFactory(){
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("host");
connectionFactory.setUsername("guest");// username
connectionFactory.setPassword("guest");// password
connectionFactory.setPort(15673);
connectionFactory.setChannelCacheSize(50);// 设置缓存channel数为50
return connectionFactory;
}
AddressResolver
从版本2.1.15
开始,您现在可以使用AddressResolver
来解析连接地址。这将覆盖addresses
和host/port
属性的任何设置。
Naming Connections
从版本1.7
开始,为注入AbstractionConnectionFactory
提供了一个ConnectionNameStrategy
。生成的名称用于目标RabbitMQ
连接的特定于应用程序的标识。如果RabbitMQ
服务器支持,则在管理界面显示连接名。这个值不必是唯一的,也不能用作连接标识符——例如,在HTTP API
请求中。这个值应该是人类可读的,是connection_name
键下ClientProperties
的一部分。你可以使用简单的Lambda
,如下所示:
@Bean
public CachingConnectionFactory cachingConnectionFactory(){
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("host");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setPort(15673);
connectionFactory.setChannelCacheSize(50);
connectionFactory.setConnectionNameStrategy(cachingConnectionFactory -> "MY_CONNECTION");// 自定义连接名称
return connectionFactory;
}

ConnectionFactory
参数可用于根据某些逻辑区分目标连接名称。默认情况下,AbstractConnectionFactory
的beanName
(表示对象的十六进制字符串)和一个内部计数器用于生成connection_name
。<rabbit:connection-factory>
命名空间组件还使用connection-name-strategy
属性提供。
SimplePropertyValueConnectionNameStrategy
的实现将连接名称设置为应用程序属性。您可以将其声明为@Bean
并将其注入到连接工厂中,如下例所示:
@Bean
public SimplePropertyValueConnectionNameStrategy cns() {
return new SimplePropertyValueConnectionNameStrategy("spring.application.name");//该属性必须存在于应用程序上下文的Environment中。
}
@Bean
public ConnectionFactory rabbitConnectionFactory(ConnectionNameStrategy cns) {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
...
connectionFactory.setConnectionNameStrategy(cns);
return connectionFactory;
}
spring:
application:
name: AMQP-demo

当使用
Spring Boot
及其自动配置的连接工厂时,您只需要声明ConnectionNameStrategy
@Bean
。引导将自动检测bean
并将其连接到工厂。@Bean public ConnectionNameStrategy connectionNameStrategy(){ return new SimplePropertyValueConnectionNameStrategy("spring.application.name"); } // @Bean // public CachingConnectionFactory cachingConnectionFactory(){ // CachingConnectionFactory connectionFactory = new CachingConnectionFactory("host"); // connectionFactory.setUsername("guest");// username // connectionFactory.setPassword("guest");// pawd // connectionFactory.setPort(15673);//端口 // connectionFactory.setChannelCacheSize(50);// 缓存channel连接数 connectionFactory.setConnectionNameStrategy(cachingConnectionFactory -> "MY_CONNECTION");// 自定义连接名称 // connectionFactory.setConnectionNameStrategy(cns());// 自定义连接名称 // return connectionFactory; // }
![]()
Blocked Connections and Resource Constraints(连接阻塞和资源限制)
连接可能因为与内存警报对应的代理的交互而被阻塞。从2.0
版本开始,org.springframework.amqp.rabbit.connection.Connection
可以提供com.rabbitmq.client.BlockedListener
实例,以便在连接阻塞和未阻塞事件时得到通知。此外,AbstractConnectionFactory
通过其内部的BlockedListener
实现分别发出ConnectionBlockedEvent
和ConnectionUnblockedEvent
。它们允许您提供应用程序逻辑,以对代理上的问题作出适当的反应,并(例如)采取一些纠正操作。
当应用程序配置了单个
CachingConnectionFactory
时(缺省情况下使用Spring Boot
自动配置),当连接被Broker
阻塞时,应用程序将停止工作。当它被Broker
阻止时,它的任何客户端都停止工作。如果在同一个应用程序中有生产者和消费者,当生产者阻塞连接(因为Broker
上不再有资源)而消费者无法释放它们(因为连接被阻塞)时,我们可能会导致死锁。为了缓解这个问题,我们建议使用一个单独的CachingConnectionFactory
实例,它具有相同的选项——一个用于生产者,一个用于消费者。对于在使用者线程上执行的事务生产者来说,单独的CachingConnectionFactory
是不可能的,因为它们应该重用与使用者事务关联的channel
。
从2.0.2
版本开始,RabbitTemplate
有一个配置选项,可以自动使用第二个连接工厂,除非正在使用事务。有关详细信息,请参见 Using a Separate Connection。发布者连接的ConnectionNameStrategy
与在调用方法的结果中附加.publisher
的主策略相同。
从版本1.7.7
开始,提供了AmqpResourceNotAvailableException
,当SimpleConnection.createChannel()
无法创建channel
时(例如,因为达到channelMax
限制并且缓存中没有可用channel
)抛出该异常。您可以在**RetryPolicy
中使用此异常来在一些回退之后恢复操作**。
Configuring the Underlying Client Connection Factory(配置基础客户端连接工厂)
CachingConnectionFactory
使用了Rabbit
clinet
ConnectionFactory
的实例。在CachingConnectionFactory
上设置等效属性时,会传递许多配置属性(例如host
、port
、username
、password
、requestedHeartBeat
和connectionTimeout
)。要设置其他属性(例如clientProperties
),您可以定义一个Rabbit
工厂的实例,并通过使用CachingConnectionFactory
的适当构造器提供对它的引用。当使用名称空间(如前所述)时,您需要在connection-factory
属性中提供对已配置工厂的引用。为了方便起见,提供了一个工厂bean
来帮助在Spring
应用程序上下文中配置连接工厂,这将在下一节中讨论。
<rabbit:connection-factory
id="connectionFactory" connection-factory="rabbitConnectionFactory"/>
4.0.x
客户端默认启用自动恢复功能。虽然与此特性兼容,但Spring AMQP
有自己的恢复机制,通常不需要客户端恢复特性。我们建议禁用amqp-client
自动恢复,以避免在代理可用但连接尚未恢复时获得AutoRecoverConnectionNotCurrentlyOpenException
实例。您可能会注意到这个异常,例如,当RabbitTemplate
中配置了RetryTemplate
时,甚至当故障转移到集群中的另一个代理时。由于自动恢复连接是在计时器上恢复的,因此使用Spring AMQP
的恢复机制可以更快地恢复连接。从1.7.1
版本开始,Spring AMQP
禁用AMQP-client
自动恢复,除非你显式地创建自己的RabbitMQ
连接工厂并将其提供给CachingConnectionFactory
。由RabbitConnectionFactoryBean
创建的RabbitMQ ConnectionFactory
实例默认也禁用该选项。
RabbitConnectionFactoryBean
and Configuring SSL(RabbitConnectionFactoryBean and 配置SSL)
从1.4
版开始,提供了一个方便的RabbitConnectionFactoryBean
,通过使用依赖注入,可以方便地配置底层客户端连接工厂上的SSL
属性。其他设置器委托给底层工厂。以前,您必须以编程方式配置SSL
选项。下面的例子展示了如何配置RabbitConnectionFactoryBean
:
<rabbit:connection-factory id="rabbitConnectionFactory"
connection-factory="clientConnectionFactory"
host="${host}"
port="${port}"
virtual-host="${vhost}"
username="${username}" password="${password}" />
<bean id="clientConnectionFactory"
class="org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean">
<property name="useSSL" value="true" />
<property name="sslPropertiesLocation" value="file:/secrets/rabbitSSL.properties"/>
</bean>
有关配置SSL
的信息,请参阅RabbitMQ Documentation文档。省略keyStore
和trustStore
配置,通过SSL
进行连接而不进行证书验证。下一个示例展示如何提供密钥和信任存储配置。
sslPropertiesLocation
属性是一个Spring
资源,指向一个包含以下键的属性文件:
keyStore=file:/secret/keycert.p12
trustStore=file:/secret/trustStore
keyStore.passPhrase=secret
trustStore.passPhrase=secret
keyStore
和truststore
库是指向存储的Spring Resources
。通常,该属性文件由具有读访问权的操作系统保护。
从Spring AMQP 1.5
版开始,您可以直接在工厂bean
上设置这些属性。如果同时提供了离散属性和sslPropertiesLocation
,则后者中的属性将覆盖离散值。
从版本
2.0
开始,默认情况下对服务器证书进行验证,因为它更安全。如果出于某种原因希望跳过此验证,请将工厂bean
的skipServerCertificateValidation
属性设置为true
。从2.1
版本开始,RabbitConnectionFactoryBean
现在默认调用enableHostnameVerification()
。要恢复到前面的行为,请将enableHostnameVerification
属性设置为false
。
从
2.2.5
版本开始,工厂bean
默认情况下将始终使用TLS v1.2
;以前,它在某些情况下使用v1.1
,在其他情况下使用v1.2
(取决于其他属性)。如果出于某种原因需要使用v1.1
,请设置sslAlgorithm
属性:setSslAlgorithm("TLSv1.1")
。
Connecting to a Cluster(连接集群)
要连接到集群,在CachingConnectionFactory
上配置addresses
属性:
@Bean
public CachingConnectionFactory ccf() {
CachingConnectionFactory ccf = new CachingConnectionFactory();
ccf.setAddresses("host1:5672,host2:5672,host3:5672");
return ccf;
}
从3.0
版开始,每当建立一个新连接时,底层连接工厂将尝试通过选择一个随机地址连接到一个主机。要恢复到之前尝试从第一个连接到最后一个连接的行为,请将addressShuffleMode
属性设置为AddressShuffleMode.NONE
。
从2.3
版开始,添加了INORDER
shuffle模式,这意味着在创建连接后,第一个地址被移动到末尾。你可能希望在RabbitMQ Sharding Plugin
使用这种模式。如果您希望从所有节点上的所有分片消费,则提供CacheMode.CONNECTION
和适当的并发。
@Bean
public CachingConnectionFactory ccf() {
CachingConnectionFactory ccf = new CachingConnectionFactory();
ccf.setAddresses("host1:5672,host2:5672,host3:5672");
ccf.setAddressShuffleMode(AddressShuffleMode.INORDER);// `INORDER` shuffle模式
return ccf;
}
Routing Connection Factory(路由连接工厂)
从1.3
版开始,已经引入了AbstractRoutingConnectionFactory
。该工厂提供了一种机制来为多个ConnectionFactory
配置映射,并在运行时通过某个lookupKey
确定目标ConnectionFactory
。通常,该实现检查线程绑定上下文。为了方便起见,Spring AMQP
提供了SimpleRoutingConnectionFactory
,它从SimpleResourceHolder
获取当前线程绑定的lookupKey
。下面的例子展示了如何在XML
和Java
中配置SimpleRoutingConnectionFactory
:
<bean id="connectionFactory"
class="org.springframework.amqp.rabbit.connection.SimpleRoutingConnectionFactory">
<property name="targetConnectionFactories">
<map>
<entry key="#{connectionFactory1.virtualHost}" ref="connectionFactory1"/>
<entry key="#{connectionFactory2.virtualHost}" ref="connectionFactory2"/>
</map>
</property>
</bean>
<rabbit:template id="template" connection-factory="connectionFactory" />
public class MyService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void service(String vHost, String payload) {
SimpleResourceHolder.bind(rabbitTemplate.getConnectionFactory(), vHost);// 绑定
rabbitTemplate.convertAndSend(payload);// 发送消息
SimpleResourceHolder.unbind(rabbitTemplate.getConnectionFactory());// 解除绑定
}
}
重要的是在使用完资源后解除绑定。有关更多信息,请参阅AbstractRoutingConnectionFactory
的 JavaDoc
。
从1.4
版开始,RabbitTemplate
支持SpEL sendconnectionfactoryselectoreexpression
和receiveconnectionfactoryselectoreexpression
属性,它们在每个AMQP
协议交互操作(send
, sendAndReceive
, receive
或receiveAndReply
)上进行评估,解析为提供的AbstractRoutingConnectionFactory
的lookupKey
值。您可以在表达式中使用bean
引用,例如@vHostResolver.getVHost(#root)
。对于发送操作,要发送的消息是根计算对象。对于接收操作,queueName
是根计算对象。
路由算法如下:如果选择器表达式为空或被求值为空,或者所提供的ConnectionFactory
不是AbstractRoutingConnectionFactory
的实例,一切都像以前一样工作,依赖于所提供的ConnectionFactory
实现。如果计算结果不为空,但是没有该lookupKey
的目标ConnectionFactory
,并且AbstractRoutingConnectionFactory
配置为lenientFallback = true
,也会发生同样的情况。在AbstractRoutingConnectionFactory
的情况下,它会回退到基于determineCurrentLookupKey()
的路由实现。但是,如果lenientFallback = false
,则抛出IllegalStateException
。
命名空间支持还在<rabbit:template>
组件上提供了send-connection-factory-selector-expression
-receive-connection-factory-selector-expression
属性。
此外,从版本1.4
开始,您可以在listener container
中配置routing connection factory
。在这种情况下,将使用队列名称列表作为查找键。例如,如果使用setQueueNames("thing1", "thing2")
配置容器,则查找键为[thing1,thing]"
(注意键中没有空格)。
从版本1.6.9
开始,您可以通过在listener container
上使用setLookupKeyQualifier
向查找键添加限定符。例如,这样做可以监听具有相同名称但位于不同虚拟主机中的queue
(在该虚拟主机中您将为每个queue
设置connection factory
)。
例如,使用查找键限定符thing1
和监听队列thing2
的container
,可以使用thing1[thing2]
来注册目标连接工厂的查找键。
目标连接工厂(如果提供了默认连接工厂)必须具有相同的发布者确认和返回设置。见Publisher Confirms and Returns。
从版本2.4.4
开始,可以禁用这种验证。如果确认和返回之间的值需要不相等,可以使用AbstractRoutingConnectionFactory#setConsistentConfirmsReturns
来切换验证。注意,添加到AbstractRoutingConnectionFactory
的第一个连接工厂将确定confirms
和returns
的一般值。
如果你有一个情况,你会检查某些消息confirms
/returns
,而其他不检查,这可能是有用的。例如:
@Bean
public RabbitTemplate rabbitTemplate() {
final com.rabbitmq.client.ConnectionFactory cf = new com.rabbitmq.client.ConnectionFactory();
cf.setHost("localhost");
cf.setPort(5672);
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(cf);
cachingConnectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
PooledChannelConnectionFactory pooledChannelConnectionFactory = new PooledChannelConnectionFactory(cf);
final Map<Object, ConnectionFactory> connectionFactoryMap = new HashMap<>(2);
connectionFactoryMap.put("true", cachingConnectionFactory);
connectionFactoryMap.put("false", pooledChannelConnectionFactory);
final AbstractRoutingConnectionFactory routingConnectionFactory = new SimpleRoutingConnectionFactory();
routingConnectionFactory.setConsistentConfirmsReturns(false);//这行报错了 可能版本不一样
routingConnectionFactory.setDefaultTargetConnectionFactory(pooledChannelConnectionFactory);
routingConnectionFactory.setTargetConnectionFactories(connectionFactoryMap);
final RabbitTemplate rabbitTemplate = new RabbitTemplate(routingConnectionFactory);
final Expression sendExpression = new SpelExpressionParser().parseExpression(
"messageProperties.headers['x-use-publisher-confirms'] ?: false");
rabbitTemplate.setSendConnectionFactorySelectorExpression(sendExpression);
}
这样,带有x-use-publisher-confirm: true
标题的消息将通过缓存连接发送,并且您可以确保消息的传递。有关确保消息传递的更多信息,请参见Publisher Confirms and Returns。
Queue Affinity and the LocalizedQueueConnectionFactory
(队列的亲和力)
在集群中使用HA
队列时,为了获得最佳性能,您可能希望连接到领先队列所在的物理代理。CachingConnectionFactory
可以配置多个代理地址。这是为了故障转移,客户端尝试按顺序连接。LocalizedQueueConnectionFactory
使用管理插件提供的REST API
来确定哪个节点是队列的先导。然后,它创建(或从缓存中检索)仅连接到该节点的CachingConnectionFactory
。如果连接失败,则确定新的领先节点并将使用者连接到该节点。LocalizedQueueConnectionFactory
配置了一个默认连接工厂,以防无法确定队列的物理位置,在这种情况下,它正常地连接到集群。
LocalizedQueueConnectionFactory
是一个RoutingConnectionFactory
, SimpleMessageListenerContainer
使用队列名作为查找键,如上面的路由连接工厂中所讨论的那样。
出于这个原因(使用队列名进行查找),
LocalizedQueueConnectionFactory
只能在容器被配置为监听单个队列时使用。
每个节点都必须启用
RabbitMQ
管理插件
此连接工厂用于长期连接,例如
SimpleMessageListenerContainer
所使用的连接。它不打算用于短连接的使用,例如与RabbitTemplate
,因为在建立连接之前调用REST API
的开销。此外,对于发布操作,队列是未知的,消息无论如何都要发布到所有集群成员,因此查找节点的逻辑没有什么价值。
下面的配置示例展示了如何配置工厂:
@Autowired
private ConfigurationProperties props;
@Bean
public CachingConnectionFactory defaultConnectionFactory() {
CachingConnectionFactory cf = new CachingConnectionFactory();
cf.setAddresses(this.props.getAddresses());
cf.setUsername(this.props.getUsername());
cf.setPassword(this.props.getPassword());
cf.setVirtualHost(this.props.getVirtualHost());
return cf;
}
@Bean
public LocalizedQueueConnectionFactory queueAffinityCF(
@Qualifier("defaultConnectionFactory") ConnectionFactory defaultCF) {
return new LocalizedQueueConnectionFactory(defaultCF,
StringUtils.commaDelimitedListToStringArray(this.props.getAddresses()),
StringUtils.commaDelimitedListToStringArray(this.props.getAdminUris()),
StringUtils.commaDelimitedListToStringArray(this.props.getNodes()),
this.props.getVirtualHost(), this.props.getUsername(), this.props.getPassword(),
false, null);
}
注意,前三个参数是adresses
、adminuri
和nodes
的数组。这些是定位,当容器试图连接到队列时,它使用管理API
来确定哪个节点是队列的先导,并连接到与该节点位于相同数组位置的地址。
从
3.0
版开始,RabbitMQ http-clien
t不再用于访问Rest API
。相反,默认情况下,如果Spring - Webflux
在类路径上,则使用来自Spring Webflux
的WebClient
;否则使用RestTemplate
。
将WebFlux添加到类路径:
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
您还可以通过实现LocalizedQueueConnectionFactory
来使用其他REST
技术。NodeLocator
和覆盖它的createClient
, restCall
,还有可选的close
方法。
lqcf.setNodeLocator(new NodeLocator<MyClient>() {
@Override
public MyClient createClient(String userName, String password) {
...
}
@Override
public HashMap<String, Object> restCall(MyClient client, URI uri) {
...
});
});
框架提供了WebFluxNodeLocator
和RestTemplateNodeLocator
,默认值如上所述。
Publisher Confirms and Returns
通过将CachingConnectionFactory
属性publisherConfirmType
设置为ConfirmType.CORRELATED
和publisherReturns
属性为true
,可以支持已确认(带有相关性)和返回的消息。
设置了这些选项后,工厂创建的channel
实例被包装在PublisherCallbackChannel
中,该实例用于方便回调。当获得这样的channel
时,客户端可以给channel
注册PublisherCallbackChannel.Listener
。PublisherCallbackChannel
实现包含将确认或返回路由到适当监听器的逻辑。下面几节将进一步解释这些特性。
请参见 Scoped Operations中的simplepublisherconfirm
。
要了解更多背景信息,请参阅
RabbitMQ
团队的博客文章《 Introducing Publisher Confirms》。
Connection and Channel Listeners
连接工厂支持注册ConnectionListener
和ChannelListener
实现。这允许您接收连接和通道相关事件的通知。(ConnectionListener
是RabbitAdmin
用来在连接建立时执行声明的——更多信息请参见Automatic Declaration of Exchanges, Queues, and Bindings)。下面的清单显示了ConnectionListener
接口定义:
@FunctionalInterface
public interface ConnectionListener {
void onCreate(Connection connection);
default void onClose(Connection connection) {
}
default void onShutDown(ShutdownSignalException signal) {
}
}
从2.0
版本开始,org.springframework.amqp.rabbit.connection.Connection
对象可以提供com.rabbitmq.client.BlockedListener
实例,用于通知连接阻塞和未阻塞事件。下面的例子显示了ChannelListener
接口的定义:
@FunctionalInterface
public interface ChannelListener {
void onCreate(Channel channel, boolean transactional);
default void onShutDown(ShutdownSignalException signal) {
}
}
See Publishing is Asynchronous — How to Detect Successes and Failures for one scenario where you might want to register a ChannelListener
.
Logging Channel Close Events(记录channel关闭事件)
1.5
版引入了一种机制,允许用户控制日志级别。
CachingConnectionFactory
使用默认策略记录通道关闭,如下所示:
- 没有记录正常通道关闭(200 OK)。
- 如果由于被动队列声明失败而关闭通道,则将其记录在调试级别。
- 如果一个通道被关闭,因为’ basic。由于独占消费者条件而被拒绝,它被记录在INFO级别。
- 其他所有日志都记录在ERROR级别。
要修改这个行为,你可以在CachingConnectionFactory
的closeExceptionLogger
属性中注入一个定制的ConditionalExceptionLogger
。
See also Consumer Events.
Runtime Cache Properties(运行时缓存属性)
从1.6
版开始,CachingConnectionFactory
现在通过getCacheProperties()
方法提供缓存统计信息。这些统计信息可用于优化缓存,以在生产环境中优化它。例如,高水位标记可用于确定是否应该增加缓存大小。如果它等于缓存大小,您可能需要考虑进一步增加。CacheMode
的描述如下表所示。通道属性:
Table 1. Cache properties for CacheMode.CHANNEL
Property | 解释 |
---|---|
connectionName | 由ConnectionNameStrategy 生成的连接的名称。 |
channelCacheSize | 当前配置的允许空闲的最大通道数。 |
localPort | 连接的本地端口(如果可用)。这可以用来关联RabbitMQ 管理界面上的连接和通道。 |
idleChannelsTx | 当前空闲(缓存)的事务通道数。 |
idleChannelsNotTx | 当前空闲(缓存)的非事务通道的数量。 |
idleChannelsTxHighWater | 并发空闲(缓存)的事务通道的最大数量。 |
idleChannelsNotTxHighWater | 并发空闲(缓存)的非事务通道的最大数量。 |
The following table describes the CacheMode.CONNECTION
properties:
Table 2. Cache properties for CacheMode.CONNECTION
Property | Meaning |
---|---|
connectionName:<localPort> | 由ConnectionNameStrategy 生成的连接的名称。 |
openConnections | 表示到代理的连接的连接对象的数量。 |
channelCacheSize | 当前配置的允许空闲的最大通道数。 |
connectionCacheSize | 当前配置的允许空闲的最大连接数。 |
idleConnections | 当前空闲的连接数。 |
idleConnectionsHighWater | 并发空闲的最大连接数。 |
idleChannelsTx:<localPort> | 此连接当前空闲(缓存)的事务通道数。你可以使用属性名的localPort 部分来关联RabbitMQ 管理界面上的连接和通道。 |
idleChannelsNotTx:<localPort> | 当前为此连接空闲(缓存)的非事务通道数。属性名的localPort 部分可以用来关联RabbitMQ 管理界面上的连接和通道。 |
idleChannelsTxHighWater:<localPort> | 并发空闲(缓存)的事务通道的最大数量。属性名的localPort 部分可以用来关联RabbitMQ Admin UI 上的连接和通道。 |
idleChannelsNotTxHighWater:<localPort> | 并发空闲(缓存)的非事务通道的最大数量。你可以使用属性名的localPort 部分来关联RabbitMQ 管理界面上的连接和通道。 |
The cacheMode
property (CHANNEL
or CONNECTION
) is also included.

RabbitMQ Automatic Connection/Topology recovery (RabbitMQ自动连接/拓扑恢复)
从Spring AMQP
的第一个版本开始,该框架在代理失败时提供了自己的连接和通道恢复。另外,正如在Configuring the Broker中讨论的,当连接重新建立时,RabbitAdmin
会重新声明任何基础设施bean
(队列和其他)。因此,它不依赖于amqp-client
库现在提供的自动恢复功能。amqp-client
默认开启自动恢复功能。这两种恢复机制之间存在一些不兼容性,因此,默认情况下,Spring
将底层RabbitMQ connectionFactory
上的automaticRecoveryEnabled
属性设置为false
。即使该属性为true
, Spring
也会立即关闭任何恢复的连接,从而有效地禁用它。
默认情况下,只有定义为bean的元素(队列、交换、绑定)在连接失败后才会重新声明。有关如何更改该行为,请参阅Recovering Auto-Delete Declarations。
4.1.3. Adding Custom Client Connection Properties
CachingConnectionFactory
现在允许您访问底层连接工厂,例如,允许设置自定义客户端属性。下面的例子展示了如何这样做:
connectionFactory.getRabbitConnectionFactory().getClientProperties().put("thing1", "thing2");
当查看连接时,这些属性会出现在RabbitMQ
管理员界面中。
4.1.4. AmqpTemplate
与Spring
框架和相关项目提供的许多其他高级抽象一样,Spring AMQP
提供了一个扮演核心角色的“模板”。定义主要操作的接口称为AmqpTemplate
。这些操作涵盖了发送和接收消息的一般行为。换句话说,它们对任何实现都不是唯一的——因此AMQP
出现在名称中。另一方面,该接口的实现与AMQP
协议的实现绑定在一起。与JMS
本身是一个接口级API
不同,AMQP
是一个线路级协议。该协议的实现提供了自己的客户端库,因此模板接口的每个实现都依赖于特定的客户端库。目前,只有一个实现:RabbitTemplate
。在下面的例子中,我们经常使用AmqpTemplate
。然而,当您查看配置示例或模板实例化或setter
调用的任何代码摘录时,您可以看到实现类型(例如RabbitTemplate
)。
如前所述,AmqpTemplate
接口定义了用于发送和接收消息的所有基本操作。我们将在Sending Messages和Receiving Messages中分别探讨消息发送和接收。
See also Async Rabbit Template.
Adding Retry Capabilities(添加重试功能)
从1.3
版本开始,你现在可以配置RabbitTemplate
来使用RetryTemplate
来帮助处理代理连接的问题。有关完整信息,请参阅spring-retry项目。下面只是一个使用指数退出策略和默认SimpleRetryPolicy
的示例,该策略在将异常抛出给调用者之前进行三次尝试。
The following example uses the XML namespace:
<rabbit:template id="template" connection-factory="connectionFactory" retry-template="retryTemplate"/>
<bean id="retryTemplate" class="org.springframework.retry.support.RetryTemplate">
<property name="backOffPolicy">
<bean class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
<property name="initialInterval" value="500" />
<property name="multiplier" value="10.0" />
<property name="maxInterval" value="10000" />
</bean>
</property>
</bean>
The following example uses the @Configuration
annotation in Java:
@Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
RetryTemplate retryTemplate = new RetryTemplate();
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(500);
backOffPolicy.setMultiplier(10.0);
backOffPolicy.setMaxInterval(10000);
retryTemplate.setBackOffPolicy(backOffPolicy);
template.setRetryTemplate(retryTemplate);
return template;
}
从1.4
版开始,除了retryTemplate
属性外,RabbitTemplate
还支持recoveryCallback
选项。它被用作RetryTemplate
的第二个参数。execute(RetryCallback<T, E> RetryCallback, RecoveryCallback<T> RecoveryCallback)
。
RecoveryCallback
有点受限,因为重试上下文只包含lastThrowable
字段。对于更复杂的用例,您应该使用外部的RetryTemplate
,这样您就可以通过上下文的属性向RecoveryCallback
传递额外的信息。下面的例子展示了如何这样做:
retryTemplate.execute(
new RetryCallback<Object, Exception>() {
@Override
public Object doWithRetry(RetryContext context) throws Exception {
context.setAttribute("message", message);
return rabbitTemplate.convertAndSend(exchange, routingKey, message);
}
}, new RecoveryCallback<Object>() {
@Override
public Object recover(RetryContext context) throws Exception {
Object message = context.getAttribute("message");
Throwable t = context.getLastThrowable();
// Do something with message
return null;
}
});
}
在这种情况下,你不会将RetryTemplate
注入到RabbitTemplate
中。
Publishing is Asynchronous — How to Detect Successes and Failures(发布是异步的——如何检测成功和失败)
发布消息是一种异步机制,默认情况下,RabbitMQ
将丢弃不能路由的消息。为了成功发布,您可以收到异步确认,如Correlated Publisher Confirms and Returns中所述。考虑两种失败场景:
- 发布到交换机,但没有匹配的目标队列。
- 发布到一个不存在的交换。
第一种情况被publisher
返回涵盖,如 Correlated Publisher Confirms and Returns中所述。
对于第二种情况,消息被删除并且不生成返回。底层channel
被异常关闭。默认情况下,会记录此异常,但是您可以向CachingConnectionFactory
注册ChannelListener
以获取此类事件的通知。下面的示例显示如何添加ConnectionListener
:
this.connectionFactory.addConnectionListener(new ConnectionListener() {
@Override
public void onCreate(Connection connection) {
}
@Override
public void onShutDown(ShutdownSignalException signal) {
...
}
});
您可以检查信号的reason
属性以确定发生的问题。
要检测发送线程上的异常,你可以在RabbitTemplate
上setchanneltransact (true)
,然后在txCommit()
上检测异常。然而,事务会严重影响性能,因此在仅为这一个用例启用事务之前要仔细考虑这一点。
Correlated Publisher Confirms and Returns(相关Publisher 确认和返回)
AmqpTemplate
的RabbitTemplate
实现支持发布者确认和返回。
对于返回的消息,模板的mandatory
属性必须设置为true
,或者对于特定的消息,mandatory
表达式必须计算为true
。这个特性需要一个CachingConnectionFactory
,它的publisherReturns
属性设置为true
(参见Publisher Confirms and Returns)。客户端通过注册RabbitTemplate
将返回值发送给客户端。通过调用setReturnsCallback(ReturnsCallback callback)
来返回。回调必须实现以下方法:
void returnedMessage(ReturnedMessage returned);
ReturnedMessage
有以下属性:
message
-返回的消息本身replyCode
- 表示返回原因的代码replyText
-返回的文本原因。例如:“NO_ROUTE”exchange
- 消息被发送到的交换机routingKey
- 使用的路由键
每个RabbitTemplate
只支持一个ReturnsCallback
。See also Reply Timeout.
对于发布者确认,模板需要一个CachingConnectionFactory
,该CachingConnectionFactory
将其publisherConfirm
属性设置为ConfirmType.CORRELATED
。客户端通过注册RabbitTemplate
向客户端发送确认。通过调用setConfirmCallback(ConfirmCallback callback)
来确认。回调必须实现这个方法:
void confirm(CorrelationData correlationData, boolean ack, String cause);
CorrelationData
是客户端在发送原始消息时提供的对象。ack
为true
, nack
为false
。对于nack
实例,如果nack
生成时可用,则原因可能包含nack
的原因。一个例子是向不存在的交换发送消息。在这种情况下,代理将关闭通道。关闭的原因包含在原因中。原因是在1.4
版中添加的。
一个RabbitTemplate
只支持一个ConfirmCallback
。
当
RabbitTemplate
发送操作完成时,channel
关闭。这就排除了在连接工厂缓存已满时接收确认或返回(当缓存中有空间时,channel
没有物理关闭,返回和确认正常进行)。当缓存已满时,框架将关闭延迟最多5秒,以便有时间接收确认和返回。使用确认时,当接收到最后一次确认时,channel
将关闭。当只使用返回时,channel
将保持打开整整5秒。我们通常建议将连接工厂的channelCacheSize
设置为足够大的值,以便将发布消息的channel
返回到缓存,而不是关闭。你可以使用RabbitMQ
管理插件来监控通道的使用情况。如果您看到channel
快速地打开和关闭,您应该考虑增加缓存大小以减少服务器上的开销。
在
2.1
版本之前,为发布者确认启用的channel
会在收到确认之前返回到缓存。其他进程可以签出channel
并执行一些导致channel
关闭的操作——例如向不存在的交换器发布消息。这可能会导致确认丢失。版本2.1
及更高版本在确认未完成时不再将channel
返回到缓存。RabbitTemplate
在每次操作之后都会在channel
上执行一个逻辑close()
。通常,这意味着一个channel
上一次只有一个未完成的确认。
从
2.2
版开始,回调是在连接工厂的一个executor
线程上调用的。这是为了避免在回调中执行Rabbit
操作时出现潜在的死锁。在以前的版本中,回调直接在amqp-client
连接I/O
线程上调用;如果您执行一些RPC
操作(例如打开一个新channel
),这将导致死锁,因为I/O
线程阻塞等待结果,但结果需要由I/O
线程本身处理。在这些版本中,有必要将工作(例如发送消息)传递给回调中的另一个线程。这不再是必要的,因为框架现在将回调调用传递给执行程序。
只要返回回调在60秒或更短的时间内执行,在
ack
之前接收返回消息的凭证仍然保持。确认被安排在返回回调退出后或60秒后传递,以先到者为准。
CorrelationData
对象有一个CompletableFuture
,您可以使用它来获得结果,而不是在模板上使用ConfirmCallback
。下面的例子展示了如何配置CorrelationData
实例:
CorrelationData cd1 = new CorrelationData();
this.templateWithConfirmsEnabled.convertAndSend("exchange", queue.getName(), "foo", cd1);
assertTrue(cd1.getFuture().get(10, TimeUnit.SECONDS).isAck());
由于它是一个CompletableFuture<Confirm>
,你可以在准备就绪时get()
结果,也可以使用whenComplete()
进行异步回调。Confirm
对象是一个简单的bean
,具有两个属性:ack
和reason
(用于nack
实例)。没有为代理生成的nack
实例填充原因。它是为框架生成的nack
实例填充的(例如,在ack
实例未完成时关闭连接)。
此外,当确认和返回都被启用时,只要CorrelationData
有唯一的id
,就会用返回的消息填充CorrelationData
;默认情况下,从版本2.3
开始总是这样。它保证在使用ack
设置future
之前设置返回的消息。
See also Scoped Operations for a simpler mechanism for waiting for publisher confirms.
Scoped Operations(作用域操作)
通常,在使用模板时,会从缓存中取出(或创建)Channel
,用于操作,并返回缓存以供重用。在多线程环境中,不能保证下一个操作使用相同的channel
。但是,有时您可能希望对channel
的使用有更多的控制,并确保在同一channel
上执行许多操作。
从版本2.0
开始,提供了一个名为invoke
的新方法,它带有一个OperationsCallback
。任何在回调范围内执行的操作和对提供的RabbitOperations
参数使用相同的专用channel
,该channel
将在结束时关闭(不返回到缓存中)。如果channel
是PublisherCallbackChannel
,则在接收到所有确认后将其返回到缓存(请参阅Correlated Publisher Confirms and Returns)。
@FunctionalInterface
public interface OperationsCallback<T> {
T doInRabbit(RabbitOperations operations);
}
如果您希望在底层Channel
上使用waitforconfirm()
方法,则可能需要此方法的一个示例。这个方法以前没有由Spring API
公开,因为channel
通常是缓存和共享的,正如前面讨论的那样。RabbitTemplate
现在提供了waitforconfirm(long timeout)
和waitForConfirmsOrDie(long timeout)
,它们委托给OperationsCallback
范围内使用的专用channel
。由于显而易见的原因,这些方法不能在该范围之外使用。
请注意,在其他地方提供了让您将确认与请求关联起来的更高级别抽象(请参阅相关 Correlated Publisher Confirms and Returns)。如果您只想等待代理确认交付,您可以使用以下示例所示的技术:
Collection<?> messages = getMessagesToSend();
Boolean result = this.template.invoke(t -> {
messages.forEach(m -> t.convertAndSend(ROUTE, m));
t.waitForConfirmsOrDie(10_000);
return true;
});
如果你希望在OperationsCallback
作用域中的同一个channel
上调用RabbitAdmin
操作,管理必须使用用于调用操作的同一个RabbitTemplate
来构造。
如果模板操作已经在现有事务的范围内执行——例如,在事务处理的
listener container
线程上运行并在事务处理的模板上执行操作时,上述讨论就没有意义了。在这种情况下,操作在该channel
上执行,并在线程返回容器时提交。在该场景中没有必要使用调用。
当以这种方式使用confirm
时,实际上并不需要为将confirm
与请求关联而设置的许多基础设施(除非也启用了返回)。从2.2
版开始,连接工厂支持一个名为publisherConfirmType
的新属性。设置为ConfirmType.SIMPLE
,避免了基础设施,确认处理可以更有效。
此外,RabbitTemplate
在发送的消息MessageProperties
中设置了publisherSequenceNumber
属性。如果您希望检查(或记录或以其他方式使用)特定的confirm
,您可以使用重载invoke
方法来完成,如下面的示例所示:
public <T> T invoke(OperationsCallback<T> action, com.rabbitmq.client.ConfirmCallback acks,
com.rabbitmq.client.ConfirmCallback nacks);
这些
ConfirmCallback
对象(ack
和nack
实例)是Rabbit client
的回调,不是模板回调。
The following example logs ack
and nack
instances:
Collection<?> messages = getMessagesToSend();
Boolean result = this.template.invoke(t -> {
messages.forEach(m -> t.convertAndSend(ROUTE, m));
t.waitForConfirmsOrDie(10_000);
return true;
}, (tag, multiple) -> {
log.info("Ack: " + tag + ":" + multiple);
}, (tag, multiple) -> {
log.info("Nack: " + tag + ":" + multiple);
}));
有作用域的操作绑定到线程。有关多线程环境中的严格排序的讨论,请参见Strict Message Ordering in a Multi-Threaded Environment。
Strict Message Ordering in a Multi-Threaded Environment(多线程环境中的严格消息排序)
Scoped Operations中的讨论仅适用于在同一个线程上执行的操作。
考虑以下情况:
thread-1
将消息发送到队列,并将工作交给thread-2
thread-2
向同一队列发送消息
由于RabbitMQ
的异步特性和缓存channel
的使用;不能确定是否使用相同的channel
,因此不能保证消息到达队列的顺序。(在大多数情况下,它们会有序到达,但无序交付的可能性并非为零)。要解决这个问题,您可以使用大小为1
的有界通道缓存(以及channelCheckoutTimeout
),以确保消息总是在同一channel
上发布,并且顺序将得到保证。为此,如果连接工厂有其他用途,例如使用者,则应该为模板使用专用连接工厂,或者将模板配置为使用嵌入在主连接工厂中的发布者连接工厂(请参阅Using a Separate Connection)。
This is best illustrated with a simple Spring Boot Application:
@SpringBootApplication
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
TaskExecutor exec() {
ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
exec.setCorePoolSize(10);
return exec;
}
@Bean
CachingConnectionFactory ccf() {
CachingConnectionFactory ccf = new CachingConnectionFactory("localhost");
CachingConnectionFactory publisherCF = (CachingConnectionFactory) ccf.getPublisherConnectionFactory();
publisherCF.setChannelCacheSize(1);
publisherCF.setChannelCheckoutTimeout(1000L);
return ccf;
}
@RabbitListener(queues = "queue")
void listen(String in) {
log.info(in);
}
@Bean
Queue queue() {
return new Queue("queue");
}
@Bean
public ApplicationRunner runner(Service service, TaskExecutor exec) {
return args -> {
exec.execute(() -> service.mainService("test"));
};
}
}
@Component
class Service {
private static final Logger LOG = LoggerFactory.getLogger(Service.class);
private final RabbitTemplate template;
private final TaskExecutor exec;
Service(RabbitTemplate template, TaskExecutor exec) {
template.setUsePublisherConnection(true);
this.template = template;
this.exec = exec;
}
void mainService(String toSend) {
LOG.info("Publishing from main service");
this.template.convertAndSend("queue", toSend);
this.exec.execute(() -> secondaryService(toSend.toUpperCase()));
}
void secondaryService(String toSend) {
LOG.info("Publishing from secondary service");
this.template.convertAndSend("queue", toSend);
}
}
尽管发布是在两个不同的线程上执行的,但它们都将使用相同的channel
,因为缓存被限制在单个channel
上。
从2.3.7
版本开始,ThreadChannelConnectionFactory
支持使用prepareContextSwitch
和switchContext
方法将一个线程的channel
传输到另一个线程。第一个方法返回一个上下文,该上下文被传递给调用第二个方法的第二个线程。线程可以绑定一个非事务性channel
,也可以绑定一个事务性channel
(或两者中的一个);不能单独传输它们,除非使用两个连接工厂。示例如下:
@SpringBootApplication
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
TaskExecutor exec() {
ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
exec.setCorePoolSize(10);
return exec;
}
@Bean
ThreadChannelConnectionFactory tccf() {
ConnectionFactory rabbitConnectionFactory = new ConnectionFactory();
rabbitConnectionFactory.setHost("localhost");
return new ThreadChannelConnectionFactory(rabbitConnectionFactory);
}
@RabbitListener(queues = "queue")
void listen(String in) {
log.info(in);
}
@Bean
Queue queue() {
return new Queue("queue");
}
@Bean
public ApplicationRunner runner(Service service, TaskExecutor exec) {
return args -> {
exec.execute(() -> service.mainService("test"));
};
}
}
@Component
class Service {
private static final Logger LOG = LoggerFactory.getLogger(Service.class);
private final RabbitTemplate template;
private final TaskExecutor exec;
private final ThreadChannelConnectionFactory connFactory;
Service(RabbitTemplate template, TaskExecutor exec,
ThreadChannelConnectionFactory tccf) {
this.template = template;
this.exec = exec;
this.connFactory = tccf;
}
void mainService(String toSend) {
LOG.info("Publishing from main service");
this.template.convertAndSend("queue", toSend);
Object context = this.connFactory.prepareSwitchContext();
this.exec.execute(() -> secondaryService(toSend.toUpperCase(), context));
}
void secondaryService(String toSend, Object threadContext) {
LOG.info("Publishing from secondary service");
this.connFactory.switchContext(threadContext);
this.template.convertAndSend("queue", toSend);
this.connFactory.closeThreadChannel();
}
}
一旦调用了prepareSwitchContext
,如果当前线程执行任何操作,它们将在一个新的通道上执行。当不再需要线程绑定通道时,关闭该通道非常重要。
Messaging Integration
从1.4
版开始,RabbitMessagingTemplate
(构建在RabbitTemplate
之上)提供了与Spring
框架消息抽象的集成——也就是org.springframework.messaging.Message
。这允许您通过使用spring
消息传递Message<?>
抽象。这个抽象被其他Spring
项目使用,比如Spring Integration
和Spring
的STOMP
支持。这里涉及到两个消息转换器:一个用于在spring
消息传递message <?>
和Spring AMQP
的消息抽象,以及在Spring AMQP
的消息抽象和底层RabbitMQ
客户端库所需的格式之间进行转换。默认情况下,消息有效负载由提供的RabbitTemplate
实例的消息转换器进行转换。或者,您可以使用其他有效负载转换器注入自定义MessagingMessageConverter
,如下例所示:
MessagingMessageConverter amqpMessageConverter = new MessagingMessageConverter();
amqpMessageConverter.setPayloadConverter(myPayloadConverter);
rabbitMessagingTemplate.setAmqpMessageConverter(amqpMessageConverter);
Validated User Id
从1.6
版开始,模板现在支持user-id-expression
(使用Java
配置时支持userIdExpression
)。如果发送了消息,则在计算此表达式后设置用户id
属性(如果尚未设置)。计算的root object
是要发送的消息。
The following examples show how to use the user-id-expression
attribute:
<rabbit:template ... user-id-expression="'guest'" />
<rabbit:template ... user-id-expression="@myConnectionFactory.username" />
第一个例子是文字表达式。第二个从应用程序上下文中的连接工厂bean获取username
属性。
Using a Separate Connection(使用单独连接)
从版本2.0.2
开始,您可以将usePublisherConnection
属性设置为true
,以便在可能的情况下使用与侦听器容器使用的不同的连接。这是为了避免当生产者因任何原因被阻止时,消费者被阻止。连接工厂为此目的维护第二个内部连接工厂;默认情况下,它与主工厂类型相同,但如果您希望使用不同的工厂类型进行发布,则可以设置显式。如果RabbitTemplate
运行在由监听器容器启动的事务中,则使用该容器的channel
,无论此设置如何。
一般来说,你不应该在
RabbitAdmin
模板中把这个设置为true
。使用接受连接工厂的RabbitAdmin
构造函数。如果使用其他接受模板的构造函数,请确保模板的属性为false
。这是因为,通常使用管理员来为监听器容器声明队列。使用将该属性设置为true
的模板意味着独占队列(例如AnonymousQueue
)将在与监听器容器使用的连接不同的连接上声明。在这种情况下,容器不能使用队列。
持续更新中…