Spring AMQP 官网翻译

4.1. Spring AMQP 官网翻译 from TabTan

本章探讨了使用Spring AMQP开发应用程序所必需的接口和类。

4.1.1. AMQP Abstractions(抽象)

Spring AMQP由两个模块组成(每个模块在发行版中由一个JAR表示):Spring-AMQPSpring-rabbitspring-amqp模块包含了org.springframework.amqp.core包。在这个包中,你可以找到代表核心AMQP“模型”的类。我们的目的是提供不依赖于任何特定AMQP代理实现或客户端库的通用抽象。最终用户代码可以更容易地跨供应商实现进行移植,因为它可以仅针对抽象层进行开发。这些抽象然后由特定于代理的模块实现,例如spring-rabbit。目前只有一个RabbitMQ实现。但是,这些抽象已经在.net中使用Apache QpidRabbitMQ进行了验证。由于AMQP操作在协议级别,原则上,你可以将RabbitMQ客户端与任何支持相同协议版本的代理一起使用,但我们目前没有测试任何其他代理。本文假设您已经熟悉AMQP规范的基础知识。如果没有,请查看其他资源中列出的资源

Message

0-9-1 AMQP规范没有定义&**Message类或接口。相反,在执行诸如basicPublish()之类的操作时,内容作为字节数组参数传递,其他属性作为单独的参数传递。Spring AMQPMessage类定义为更通用的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.utiljava.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中定义的常量表示。基本类型是:directtopicfanoutheaders。在核心包(org.springframework.amqp.core)中,可以找到每种类型的Exchange接口的实现。这些Exchange类型的行为因处理队列绑定的方式而异。例如,Direct Exchange(直接交换机)允许队列被固定的路由键(通常是队列名)绑定Topic交换机 支持使用路由模式的绑定,这些模式可能分别包含’*‘和’#‘通配符,分别表示’exactly-one’和’zero-or-more’。Fanout交换机 向绑定到它的所有队列发布消息,而不考虑任何路由密钥。有关这些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定义的方法,并识别queuesexchangesbindings,以便在应用程序启动时在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中连接和会话之间的关系)。连接实例提供了一个createChannel方法。**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个,并且它们都返回到缓存中,那么缓存中就有10channel。其余的都是封闭的。

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来解析连接地址。这将覆盖addresseshost/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参数可用于根据某些逻辑区分目标连接名称。默认情况下,AbstractConnectionFactorybeanName(表示对象的十六进制字符串)和一个内部计数器用于生成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实现分别发出ConnectionBlockedEventConnectionUnblockedEvent。它们允许您提供应用程序逻辑,以对代理上的问题作出适当的反应,并(例如)采取一些纠正操作。

当应用程序配置了单个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上设置等效属性时,会传递许多配置属性(例如hostportusernamepasswordrequestedHeartBeatconnectionTimeout)。要设置其他属性(例如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文档。省略keyStoretrustStore配置,通过SSL进行连接而不进行证书验证。下一个示例展示如何提供密钥和信任存储配置。

sslPropertiesLocation属性是一个Spring资源,指向一个包含以下键的属性文件:

keyStore=file:/secret/keycert.p12
trustStore=file:/secret/trustStore
keyStore.passPhrase=secret
trustStore.passPhrase=secret

keyStoretruststore库是指向存储的Spring Resources。通常,该属性文件由具有读访问权的操作系统保护。

Spring AMQP 1.5版开始,您可以直接在工厂bean上设置这些属性。如果同时提供了离散属性和sslPropertiesLocation,则后者中的属性将覆盖离散值。

从版本2.0开始,默认情况下对服务器证书进行验证,因为它更安全。如果出于某种原因希望跳过此验证,请将工厂beanskipServerCertificateValidation属性设置为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。下面的例子展示了如何在XMLJava中配置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());// 解除绑定
    }
}

重要的是在使用完资源后解除绑定。有关更多信息,请参阅AbstractRoutingConnectionFactoryJavaDoc

1.4版开始,RabbitTemplate支持SpEL sendconnectionfactoryselectoreexpressionreceiveconnectionfactoryselectoreexpression属性,它们在每个AMQP协议交互操作(send, sendAndReceive, receivereceiveAndReply)上进行评估,解析为提供的AbstractRoutingConnectionFactorylookupKey值。您可以在表达式中使用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和监听队列thing2container,可以使用thing1[thing2]来注册目标连接工厂的查找键。

目标连接工厂(如果提供了默认连接工厂)必须具有相同的发布者确认和返回设置。见Publisher Confirms and Returns

从版本2.4.4开始,可以禁用这种验证。如果确认和返回之间的值需要不相等,可以使用AbstractRoutingConnectionFactory#setConsistentConfirmsReturns来切换验证。注意,添加到AbstractRoutingConnectionFactory的第一个连接工厂将确定confirmsreturns的一般值。

如果你有一个情况,你会检查某些消息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);
}

注意,前三个参数是adressesadminurinodes的数组。这些是定位,当容器试图连接到队列时,它使用管理API来确定哪个节点是队列的先导,并连接到与该节点位于相同数组位置的地址。

3.0版开始,RabbitMQ http-client不再用于访问Rest API。相反,默认情况下,如果Spring - Webflux在类路径上,则使用来自Spring WebfluxWebClient;否则使用RestTemplate

将WebFlux添加到类路径:

<dependency>
  <groupId>org.springframework.amqp</groupId>
  <artifactId>spring-rabbit</artifactId>
</dependency>

您还可以通过实现LocalizedQueueConnectionFactory来使用其他REST技术。NodeLocator和覆盖它的createClientrestCall,还有可选的close方法。

lqcf.setNodeLocator(new NodeLocator<MyClient>() {
    @Override
    public MyClient createClient(String userName, String password) {
        ...
    }
    @Override
    public HashMap<String, Object> restCall(MyClient client, URI uri) {
        ...
    });
});

框架提供了WebFluxNodeLocatorRestTemplateNodeLocator,默认值如上所述。

Publisher Confirms and Returns

通过将CachingConnectionFactory属性publisherConfirmType设置为ConfirmType.CORRELATEDpublisherReturns属性为true,可以支持已确认(带有相关性)和返回的消息。

设置了这些选项后,工厂创建的channel实例被包装在PublisherCallbackChannel中,该实例用于方便回调。当获得这样的channel时,客户端可以给channel注册PublisherCallbackChannel.ListenerPublisherCallbackChannel实现包含将确认或返回路由到适当监听器的逻辑。下面几节将进一步解释这些特性。

请参见 Scoped Operations中的simplepublisherconfirm

要了解更多背景信息,请参阅RabbitMQ团队的博客文章《 Introducing Publisher Confirms》。

Connection and Channel Listeners

连接工厂支持注册ConnectionListenerChannelListener实现。这允许您接收连接和通道相关事件的通知。(ConnectionListenerRabbitAdmin用来在连接建立时执行声明的——更多信息请参见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级别。

要修改这个行为,你可以在CachingConnectionFactorycloseExceptionLogger属性中注入一个定制的ConditionalExceptionLogger

See also Consumer Events.

Runtime Cache Properties(运行时缓存属性)

1.6版开始,CachingConnectionFactory现在通过getCacheProperties()方法提供缓存统计信息。这些统计信息可用于优化缓存,以在生产环境中优化它。例如,高水位标记可用于确定是否应该增加缓存大小。如果它等于缓存大小,您可能需要考虑进一步增加。CacheMode的描述如下表所示。通道属性:

Table 1. Cache properties for CacheMode.CHANNEL

Property解释
connectionNameConnectionNameStrategy生成的连接的名称。
channelCacheSize当前配置的允许空闲的最大通道数。
localPort连接的本地端口(如果可用)。这可以用来关联RabbitMQ管理界面上的连接和通道。
idleChannelsTx当前空闲(缓存)的事务通道数。
idleChannelsNotTx当前空闲(缓存)的非事务通道的数量。
idleChannelsTxHighWater并发空闲(缓存)的事务通道的最大数量。
idleChannelsNotTxHighWater并发空闲(缓存)的非事务通道的最大数量。

The following table describes the CacheMode.CONNECTION properties:

Table 2. Cache properties for CacheMode.CONNECTION

PropertyMeaning
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 MessagesReceiving 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属性以确定发生的问题。

要检测发送线程上的异常,你可以在RabbitTemplatesetchanneltransact (true),然后在txCommit()上检测异常。然而,事务会严重影响性能,因此在仅为这一个用例启用事务之前要仔细考虑这一点。

Correlated Publisher Confirms and Returns(相关Publisher 确认和返回)

AmqpTemplateRabbitTemplate实现支持发布者确认返回

对于返回的消息,模板的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是客户端在发送原始消息时提供的对象。acktrue, nackfalse。对于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,具有两个属性:ackreason(用于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将在结束时关闭(不返回到缓存中)。如果channelPublisherCallbackChannel,则在接收到所有确认后将其返回到缓存(请参阅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对象(acknack实例)是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支持使用prepareContextSwitchswitchContext方法将一个线程的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 IntegrationSpringSTOMP支持。这里涉及到两个消息转换器:一个用于在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)将在与监听器容器使用的连接不同的连接上声明。在这种情况下,容器不能使用队列。

持续更新中…

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值