spring amqp AmqpTemplate(4)

与Spring框架和相关项目提供的许多其他高级抽象一样,Spring AMQP提供了一个“模板”,它扮演着核心角色。定义主要操作的接口称为AmqpTemplate。这些操作涵盖了发送和接收消息的一般行为。换句话说,它们对于任何实现都不是惟一的——因此名称中有“AMQP”。另一方面,该接口的一些实现与AMQP协议的实现绑定在一起。与JMS本身是接口级API不同,AMQP是一个线级协议。该协议的实现提供了它们自己的客户机库,因此模板接口的每个实现都依赖于特定的客户机库。目前,只有一个实现:RabbitTemplate。在下面的示例中,我们经常使用AmqpTemplate。但是,当您查看配置示例或模板实例化或调用setter的任何代码摘录时,您可以看到实现类型(例如,RabbitTemplate)。

如前所述,AmqpTemplate接口定义了发送和接收消息的所有基本操作。我们将分别探讨在发送消息和接收消息时的消息发送和接收。

see also AsyncRabbitTemplate。

 

 

添加重试功能

从1.3版开始,现在可以配置RabbitTemplate,使用RetryTemplate帮助处理代理连接问题。有关完整信息,请参见spring-retry项目。下面只是一个使用指数回退策略和默认SimpleRetryPolicy的示例,该策略在将异常抛出给调用者之前尝试了三次。

下面的示例使用XML名称空间:

<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 AmqpTemplate 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的第二个参数。RetryTemplate.execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback).

RecoveryCallback有一定的限制,因为retry上下文只包含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中。

 

发布是异步的——如何检测成功和失败

发布消息是一种异步机制,默认情况下,不能路由的消息由RabbitMQ删除。对于成功发布,可以接收异步确认,如Publisher confirm和Returns中所述。考虑两个失败场景:

•发布到一个exchange,但没有匹配的目标队列。

•发布到不存在的交易所。

第一种情况由发布者返回覆盖,如publisher confirm和returns中所述。

对于第二种情况,将删除消息,并且不生成任何返回。底层通道在异常情况下关闭。默认情况下,这个异常会被记录下来,但是您可以向CachingConnectionFactory注册一个ChannelListener来获取此类事件的通知。下面的例子展示了如何添加ConnectionListener:

this.connectionFactory.addConnectionListener(new ConnectionListener() {

@Override

        public void onCreate(Connection connection) {
        }

@Override

        public void onShutDown(ShutdownSignalException signal) {
            ...

} });

您可以检查信号的reason属性来确定发生的问题。

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

 

Publisher Confirms and Returns

AmqpTemplate的RabbitTemplate实现支持publisher确认和返回。

对于不可以路由需要回退的消息,模板的mandatory属性必须设置为true,或者对于特定的消息,mandatory-表达式的计算值必须为true。该特性需要一个CachingConnectionFactory,它将publisherReturns属性设置为true(请参阅Publisher confirm和Returns)。通过调用setReturnCallback(ReturnCallback回调)来注册一个 RabbitTemplate.ReturnCallback  监听器用来接受returns。回调必须实现以下方法:

void returnedMessage(Message message, int replyCode, String replyText,
              String exchange, String routingKey);

每个RabbitTemplate只支持一个ReturnCallback。参见回复超时。

对于回退的消息,我们只能打印日志 或者发邮件监听,因为他是不可以路由的。重新发送也没有意义。

 

对于发布者confirm(也称为发布者确认),模板需要一个CachingConnectionFactory,该工厂将其publisherconfirm属性设置为true。确认通过调用setConfirmCallback(ConfirmCallback回调)来注册一个RabbitTemplate.ConfirmCallback 监听器 用来接受确认消息。回调必须实现这个方法:

    void confirm(CorrelationData correlationData, boolean ack, String cause);

CorrelationData是客户机在发送原始消息时提供的对象。ack为true,nack为false。对于nack实例,如果在生成nack时可以找到原因,则原因可能包含nack的原因。例如,当向不存在的交换器发送消息时。在这种情况下,代理关闭通道。闭包的原因包括在原因中。原因是在1.4版中添加的。

RabbitTemplate只支持一个ConfirmCallback

对于发布失败的消息ack=false ,我们需要查看cause ,是为什么失败了,如果exchange 不存在或者不可以路由,则没有必要重发,

如果是其他原因,可以尝试重发。

 

 

当rabbittemplate发送操作完成时,通道关闭。这就导致在连接工厂缓存已满时无法正常接收确认或回退(当缓存中有空间时,通道没有物理关闭,而返回和确认将正常进行)。当缓存已满时,框架将关闭延迟至多5秒,以便有时间接收确认和返回。当使用confirm时,通道在接收到最后一个confirm时关闭。当只使用return时,通道将保持完整的5秒打开状态。我们通常建议将连接工厂的channelCacheSize设置为足够大的值,以便将发布消息的通道返回到缓存中,而不是关闭。您可以使用RabbitMQ管理插件监视通道使用情况。如果您看到通道被快速打开和关闭,您应该考虑增加缓存大小,以减少服务器上的开销。

在版本2.1之前,启用发布者确认的通道会在收到确认之前返回到缓存中。其他一些进程可以签出通道并执行一些导致通道关闭的操作——例如将消息发布到不存在的交换器。这可能导致确认丢失。版本2.1及以后不再返回通道到缓存,而确认是未完成的。每次操作之后,RabbitTemplate都会在通道上执行一个逻辑close()。通常,这意味着在一个通道上一次只有一个未完成的confirm。

 

从2.1版开始,CorrelationData对象有一个ListenableFuture,您可以使用它来获取结果,而不是在模板上使用ConfirmCallback。下面的例子展示了如何配置一个CorrelationData实例:

CorrelationData cd1 = new CorrelationData();
 cd1.setId("111");this.templateWithConfirmsEnabled.convertAndSend("exchange", queue.getName(), "foo", cd1);
assertTrue(cd1.getFuture().get(10, TimeUnit.SECONDS).isAck());

 

因为它是ListenableFuture<Confirm>,所以您可以在准备好时get()结果,或者为异步回调添加侦听器。Confirm对象是一个简单的bean,有两个属性:ack和reason(对于nack实例)。原因没有为broker生成的nack实例填充。它是为框架生成的nack实例填充的(例如,在ack实例未完成时关闭连接)。

此外,当确认和返回都启用时,将使用返回的消息填充CorrelationData。可以保证在使用ack设置未来之前发生这种情况。有关等待发布者确认的更简单机制,请参见范围操作。

 

Scoped Operations

通常,当使用模板时,通道会从缓存中签出(或创建),用于操作,然后返回到缓存中进行重用。在多线程环境中,不能保证下一个操作使用相同的通道。然而,有时您可能希望对通道的使用有更多的控制,并确保许多操作都在同一个通道上执行。

从2.0版本开始,将提供一个名为invoke的新方法,并提供一个OperationsCallback。在回调范围内以及在提供的RabbitOperations参数上执行的任何操作都使用相同的专用通道,该通道将在结束时关闭(不返回到缓存)。如果该通道是PublisherCallbackChannel,则在收到所有确认之后将其返回到缓存(请参阅Publisher confirm和Returns)。

@FunctionalInterface
public interface OperationsCallback<T> {
        T doInRabbit(RabbitOperations operations);
}

如果希望在底层通道上使用waitforconfirm()方法,则可能需要使用这种方法。如前所述,Spring API以前没有公开此方法,因为通道通常是缓存和共享的。RabbitTemplate现在提供waitforconfirm(长超时)和waitForConfirmsOrDie(长超时),它们是委托给OperationsCallback范围内使用的专用通道。由于明显的原因,这些方法不能在该范围之外使用。

请注意,其他地方提供了一个更高级别的抽象,它允许您将确认与请求关联起来(请参阅Publisher confirm和Returns)。如果您只想等待代理确认发货,可以使用如下示例所示的技术:

Collection<?> messages = getMessagesToSend();
    Boolean result = this.template.invoke(t -> {
        messages.forEach(m -> t.convertAndSend(ROUTE, m));
        t.waitForConfirmsOrDie(10_000);
        return true;

});

 

如果希望在OperationsCallback的范围内在相同的通道上调用RabbitAdmin操作,则必须使用用于调用操作的相同的RabbitTemplate来构造admin。

 

如果模板操作已经在现有事务的范围内执行—例如,在已处理的侦听器容器线程上运行并在已处理的模板上执行操作时—上述讨论没有意义。在这种情况下,操作在该通道上执行,并在线程返回容器时提交。在该场景中没有必要使用invoke。

当以这种方式使用confirm时,实际上并不需要为将confirm关联到请求而设置的许多基础设施。从2.1版开始,连接工厂支持一个名为simplepublisherconfirmation的新属性。如果是这样,就可以避免基础设施,从而提高确认处理的效率。

此外,RabbitTemplate在发送的消息MessageProperties中设置publisherSequenceNumber属性。如果希望检查(或记录或以其他方式使用)特定的确认,可以使用重载的invoke方法,如下例所示:

public <T> T invoke(
OperationsCallback<T> action, 
com.rabbitmq.client.ConfirmCallback acks, 
com.rabbitmq.client.ConfirmCallback nacks
);

These ConfirmCallback objects (for ack and nack instances) are the Rabbit client callbacks, not the template callback.

下面的例子记录了ack和nack实例:

    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);

}));

 

 

 

消息集成

从1.4版开始,RabbitMessagingTemplate(构建在RabbitTemplate之上)提供了与Spring框架消息传递抽象(即org.springframework.messaging.Message)的集成。通过使用spring-messaging Message<?> 抽象。这个抽象被其他Spring项目使用,比如Spring Integration和Spring的STOMP支持。涉及到两个消息转换器:一个用于在spring-messaging 消息和spring amqp 消息之间转化,一个用于在Spring AMQP的消息抽象和底层RabbitMQ客户端库所需的格式之间进行转换。默认情况下,消息有效负载由提供的RabbitTemplate实例的消息转换器转换。或者,您可以将自定义MessagingMessageConverter与其他负载转换器一起注入,如下面的示例所示:

    MessagingMessageConverter amqpMessageConverter = new MessagingMessageConverter();
    amqpMessageConverter.setPayloadConverter(myPayloadConverter);
    rabbitMessagingTemplate.setAmqpMessageConverter(amqpMessageConverter);

 

Validated User Id

从1.6版开始,模板现在支持用户id表达式(使用Java配置时支持userIdExpression)。如果发送了一条消息,则在计算此表达式之后设置用户id属性(如果尚未设置)。求值的根对象是要发送的消息。

下面的例子展示了如何使用user-id-expression属性:

<rabbit:template ... user-id-expression="'guest'" />

<rabbit:template ... user-id-expression="@myConnectionFactory.username" />

第一个例子是文字表达式。第二种方法从应用程序上下文中的连接工厂bean获取username属性。

 

 

使用单独的连接

从版本2.0.2开始,您可以将usePublisherConnection属性设置为true,以便在可能的情况下使用与侦听器容器使用的连接不同的连接。这是为了避免在生产者因任何原因被阻塞时阻塞消费者。CachingConnectionFactory现在为此维护第二个内部连接工厂。如果rabbitmq在侦听器容器启动的事务中运行,则使用容器的通道,而不考虑此设置

 

一般情况下,不应该使用将此值设置为true的模板来使用RabbitAdmin。使用接受连接工厂的RabbitAdmin构造函数。如果使用另一个接受模板的构造函数,请确保模板的属性为false。这是因为通常使用admin来声明侦听器容器的队列。使用将属性设置为true的模板意味着将在与侦听器容器使用的不同连接上声明独占队列(例如AnonymousQueue)。在这种情况下,容器不能使用队列。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值