Spring AMQP 随笔 4 Broker & Admin & Declaration

0. 洗澡,锻炼,睡觉 很好的提神要领

Admin(RabbitAdmin, AmqpAdmin) 是客户端视角中 对broker的理解

为了 命名缺省的队列 的声明场景,虽然broker自身支持自动赋名,框架建议改用其提供的匿名队列

4.1.11. Configuring the Broker

The AMQP specification describes how the protocol can be used to configure queues, exchanges, and bindings on the broker.
These operations (which are portable from the 0.8 specification and higher) are present in the AmqpAdmin interface

public interface AmqpAdmin {
    // Exchange Operations

    void declareExchange(Exchange exchange);
    void deleteExchange(String exchangeName);

    // Queue Operations

    Queue declareQueue();
    String declareQueue(Queue queue);
    void deleteQueue(String queueName);
    void deleteQueue(String queueName, boolean unused, boolean empty);
    void purgeQueue(String queueName, boolean noWait);
    
    // Binding Operations
    
    void declareBinding(Binding binding);
    void removeBinding(Binding binding);
    Properties getQueueProperties(String queueName);
}

The no-arg declareQueue() method defines a queue on the broker with a name that is automatically generated.
The additional properties of this auto-generated queue are exclusive=true, autoDelete=true, and durable=false.

The declareQueue(Queue queue) method takes a Queue object and returns the name of the declared queue.
If the name property of the provided Queue is an empty String, the broker declares the queue with a generated name.
That name is returned to the caller. That name is also added to the actualName property of the Queue.
You can use this functionality(方式) programmatically only by invoking the RabbitAdmin directly.
When using auto-declaration by the admin when defining a queue declaratively in the application context, you can set the name property to """ (the empty string).
The broker then creates the name. listener containers can use queues of this type.

This is in contrast(差异) to an AnonymousQueue where the framework generates a unique (UUID) name and sets durable to false and exclusive, autoDelete to true.
A <rabbit:queue/> with an empty (or missing) name attribute always creates an AnonymousQueue.

When the CachingConnectionFactory cache mode is CHANNEL (the default),
the RabbitAdmin implementation does automatic lazy declaration of queues, exchanges, and bindings declared in the same ApplicationContext.
These components are declared as soon as a Connection is opened to the broker.
There are some namespace features that make this very convenient — for example, in the Stocks sample application, we have the following:

<rabbit:queue id="tradeQueue"/>

<rabbit:queue id="marketDataQueue"/>

<fanout-exchange name="broadcast.responses"
                 xmlns="http://www.springframework.org/schema/rabbit">
    <bindings>
        <binding queue="tradeQueue"/>
    </bindings>
</fanout-exchange>

<topic-exchange name="app.stock.marketdata"
                xmlns="http://www.springframework.org/schema/rabbit">
    <bindings>
        <binding queue="marketDataQueue" pattern="${stocks.quote.pattern}"/>
    </bindings>
</topic-exchange>

You can provide both id and name attributes. This lets you refer to the queue (for example, in a binding) by an ID that is independent(独立) of the queue name.
It also allows standard Spring features (such as property placeholders and SpEL expressions for the queue name).
These features are not available when you use the name as the bean identifier.

The RabbitMQ broker does not allow declaration of a queue with mismatched arguments.
For example, if a queue already exists with no time to live argument, and you attempt to declare it with (for example) key=“x-message-ttl” value=“100”, an exception is thrown.

By default, the RabbitAdmin immediately stops processing all declarations when any exception occurs.
This could cause downstream issues, such as a listener container failing to initialize because another queue (defined after the one in error) is not declared.

This behavior can be modified by setting the ignore-declaration-exceptions attribute to true on the RabbitAdmin instance.
This option instructs the RabbitAdmin to log the exception and continue declaring other elements.
When configuring the RabbitAdmin using Java, this property is called ignoreDeclarationExceptions.
This is a global setting that applies to all elements. Queues, exchanges, and bindings have a similar property that applies to just those elements.

In addition, any declaration exceptions result in the publishing of a DeclarationExceptionEvent,
which is an ApplicationEvent that can be consumed by any ApplicationListener in the context.

Headers Exchange

You can configure Exchanges with an internal flag (defaults to false) and such an Exchange is properly configured on the Broker through a RabbitAdmin.
If the internal flag is true for an exchange, RabbitMQ does not let clients use the exchange.
This is useful for a dead letter exchange or exchange-to-exchange binding, where you do not wish the exchange to be used directly by publishers.

@Configuration
public abstract class AbstractStockAppRabbitConfiguration {
    @Bean
    public CachingConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory =
            new CachingConnectionFactory("localhost");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        return connectionFactory;
    }
    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate template = new RabbitTemplate(connectionFactory());
        template.setMessageConverter(jsonMessageConverter());
        configureRabbitTemplate(template);
        return template;
    }
    @Bean
    public Jackson2JsonMessageConverter jsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }
    @Bean
    public TopicExchange marketDataExchange() {
        return new TopicExchange("app.stock.marketdata");
    }
    // additional code omitted for brevity
}

@Configuration
public class RabbitServerConfiguration extends AbstractStockAppRabbitConfiguration  {
    @Bean
    public Queue stockRequestQueue() {
        return new Queue("app.stock.request");
    }
}

@Configuration
public class RabbitClientConfiguration extends AbstractStockAppRabbitConfiguration {
    @Value("${stocks.quote.pattern}")
    private String marketDataRoutingKey;
    @Bean
    public Queue marketDataQueue() {
        return amqpAdmin().declareQueue();
    }
    /**
     * Binds to the market data exchange.
     * Interested in any stock quotes
     * that match its routing key.
     */
    @Bean
    public Binding marketDataBinding() {
        // It binds that queue to the market data exchange with a routing pattern that is externalized in a properties file
        return BindingBuilder.bind(
                marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey);
    }
    // additional code omitted(省略) for brevity(简洁)
}

Builder API for Queues and Exchanges

@Bean
public Queue queue() {
    return QueueBuilder.nonDurable("foo")
        .autoDelete()
        .exclusive()
        .withArgument("foo", "bar")
        .build();
}
@Bean
public Exchange exchange() {
  return ExchangeBuilder.directExchange("foo")
      .autoDelete()
      .internal()
      .withArgument("foo", "bar")
      .build();
}

@Bean
public Queue allArgs1() {
    return QueueBuilder.nonDurable("all.args.1")
            .ttl(1000)
            .expires(200_000)
            .maxLength(42)
            .maxLengthBytes(10_000)
            .overflow(Overflow.rejectPublish)
            .deadLetterExchange("dlx")
            .deadLetterRoutingKey("dlrk")
            .maxPriority(4)
            .lazy()
            .leaderLocator(LeaderLocator.minLeaders)
            .singleActiveConsumer()
            .build();
}

@Bean
public DirectExchange ex() {
    return ExchangeBuilder.directExchange("ex.with.alternate")
            .durable(true)
            .alternate("alternate")
            .build();
}

Declaring Collections of Exchanges, Queues, and Bindings

You can wrap collections of Declarable objects (Queue, Exchange, and Binding) in Declarables objects.
The RabbitAdmin detects such beans (as well as discrete Declarable beans) in the application context,
and declares the contained objects on the broker whenever a connection is established (initially and after a connection failure)

@Configuration
public static class Config {

    @Bean
    public CachingConnectionFactory cf() {
        return new CachingConnectionFactory("localhost");
    }

    @Bean
    public RabbitAdmin admin(ConnectionFactory cf) {
        return new RabbitAdmin(cf);
    }

    @Bean
    public DirectExchange e1() {
        return new DirectExchange("e1", false, true);
    }

    @Bean
    public Queue q1() {
        return new Queue("q1", false, false, true);
    }

    @Bean
    public Binding b1() {
        return BindingBuilder.bind(q1()).to(e1()).with("k1");
    }

    @Bean
    public Declarables es() {
        return new Declarables(
                new DirectExchange("e2", false, true),
                new DirectExchange("e3", false, true));
    }

    @Bean
    public Declarables qs() {
        return new Declarables(
                new Queue("q2", false, false, true),
                new Queue("q3", false, false, true));
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Declarables prototypes() {
        return new Declarables(new Queue(this.prototypeQueueName, false, false, true));
    }

    @Bean
    public Declarables bs() {
        return new Declarables(
                new Binding("q2", DestinationType.QUEUE, "e2", "k2", null),
                new Binding("q3", DestinationType.QUEUE, "e3", "k3", null));
    }

    @Bean
    public Declarables ds() {
        return new Declarables(
                new DirectExchange("e4", false, true),
                new Queue("q4", false, false, true),
                new Binding("q4", DestinationType.QUEUE, "e4", "k4", null));
    }

}

In versions prior to 2.1, you could declare multiple Declarable instances by defining beans of type Collection.
This can cause undesirable(不良的) side effects(副作用) in some cases, because the admin has to iterate over all Collection<?> beans.

The getDeclarablesByType() method to Declarables; this can be used as a convenience,

public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
        Declarables mixedDeclarables, MessageListener listener) {
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
    container.setQueues(mixedDeclarables.getDeclarablesByType(Queue.class).toArray(new Queue[0]));
    container.setMessageListener(listener);
    return container;
}

Conditional Declaration

By default, all queues, exchanges, and bindings are declared by all RabbitAdmin instances
(assuming they have auto-startup="true") in the application context.

the RabbitAdmin has a new property explicitDeclarationsOnly (which is false by default);
when this is set to true, the admin will only declare beans that are explicitly configured to be declared by that admin.

you can conditionally declare these elements. This is particularly useful when an application connects to multiple brokers
and needs to specify with which brokers a particular element should be declared.

The classes representing these elements implement Declarable, which has two methods: shouldDeclare() and getDeclaringAdmins().
The RabbitAdmin uses these methods to determine whether a particular instance should actually process the declarations on its Connection.

By default, the auto-declare attribute is true and, if the declared-by is not supplied (or is empty),
then all RabbitAdmin instances declare the object
(as long as the admin’s auto-startup attribute is true, the default, and the admin’s explicit-declarations-only attribute is false).

@Bean
public RabbitAdmin admin1() {
    return new RabbitAdmin(cf1());
}
@Bean
public RabbitAdmin admin2() {
    return new RabbitAdmin(cf2());
}
@Bean
public Queue queue() {
    Queue queue = new Queue("foo");
    queue.setAdminsThatShouldDeclare(admin1());
    return queue;
}
@Bean
public Exchange exchange() {
    DirectExchange exchange = new DirectExchange("bar");
    exchange.setAdminsThatShouldDeclare(admin1());
    return exchange;
}
@Bean
public Binding binding() {
    Binding binding = new Binding("foo", DestinationType.QUEUE, exchange().getName(), "foo", null);
    binding.setAdminsThatShouldDeclare(admin1());
    return binding;
}

A Note On the id and name Attributes

The name attribute on <rabbit:queue/> and <rabbit:exchange/> elements reflects the name of the entity in the broker.
For queues, if the name is omitted, an anonymous queue is created.

In versions prior to 2.0, the name was also registered as a bean name alias (similar to name on <bean/> elements).
This caused two problems:

  • It prevented the declaration of a queue and exchange with the same name.
  • The alias was not resolved if it contained a SpEL expression (#{…}).

Starting with version 2.0, if you declare one of these elements with both an id and a name attribute,
the name is no longer declared as a bean name alias.
If you wish to declare a queue and exchange with the same name, you must provide an id.

There is no change if the element has only a name attribute. The bean can still be referenced by the name — for example, in binding declarations.
However, you still cannot reference it if the name contains SpEL — you must provide an id for reference purposes.

AnonymousQueue

In general, when you need a uniquely-named, exclusive, auto-delete queue,
we recommend that you use the AnonymousQueue instead of broker-defined queue names
(using "" as a Queue name causes the broker to generate the queue name).

This is because:

  1. The queues are actually declared when the connection to the broker is established.
    This is long after the beans are created and wired together. Beans that use the queue need to know its name.
    In fact, the broker might not even be running when the application is started.
  2. If the connection to the broker is lost for some reason, the admin re-declares the AnonymousQueue with the same name.
    If we used broker-declared queues, the queue name would change.

You can control the format of the queue name used by AnonymousQueue instances.

By default, the queue name is prefixed by spring.gen- followed by a base64 representation of the UUID —
for example: spring.gen-MRBv9sqISkuCiPfOYfpo4g.

You can provide an AnonymousQueue.NamingStrategy implementation in a constructor argument.

@Bean
public Queue anon1() {
    // generates a queue name prefixed by spring.gen - followed by a base64 representation of the UUID - 
    // for example: spring.gen-MRBv9sqISkuCiPfOYfpo4g
    return new AnonymousQueue();
}
@Bean
public Queue anon2() {
    // generates a queue name prefixed by something- followed by a base64 representation of the UUID.
    return new AnonymousQueue(new AnonymousQueue.Base64UrlNamingStrategy("something-"));
}
@Bean
public Queue anon3() {
    // The third bean generates a name by using only the UUID (no base64 conversion)
    return new AnonymousQueue(AnonymousQueue.UUIDNamingStrategy.DEFAULT);
}

Anonymous queues are declared with argument Queue.X_QUEUE_LEADER_LOCATOR set to client-local by default.
This ensures that the queue is declared on the node to which the application is connected.
You can revert to the previous behavior by calling queue.setLeaderLocator(null) after constructing the instance.

Recovering Auto-Delete Declarations

Normally, the RabbitAdmin (s) only recover queues/exchanges/bindings that are declared as beans in the application context;
if any such declarations are auto-delete, they will be removed by the broker if the connection is lost.
When the connection is re-established, the admin will redeclare the entities.
Normally, entities created by calling admin.declareQueue(…), admin.declareExchange(…) and admin.declareBinding(…) will not be recovered.

the admin has a new property redeclareManualDeclarations; when true, the admin will recover these entities in addition to the beans in the application context.

Recovery of individual declarations will not be performed if deleteQueue(…), deleteExchange(…) or removeBinding(…) is called.
Associated bindings are removed from the recoverable entities when queues and exchanges are deleted.

Finally, calling resetAllManualDeclarations() will prevent the recovery of any previously declared entities.

4.1.12. Broker Event Listener

When the Event Exchange Plugin is enabled, if you add a bean of type BrokerEventListener to the application context,
it publishes selected broker events as BrokerEvent instances, which can be consumed with a normal Spring ApplicationListener or @EventListener method.
Events are published by the broker to a topic exchange amq.rabbitmq.event with a different routing key for each event type.
The listener uses event keys, which are used to bind an AnonymousQueue to the exchange so the listener receives only selected events.
Since it is a topic exchange, wildcards can be used.

@Bean
public BrokerEventListener eventListener() {
    return new BrokerEventListener(connectionFactory(), "user.deleted", "channel.#", "queue.#");
}

@EventListener(condition = "event.eventType == 'queue.created'")
public void listener(BrokerEvent event) {
    ...
}

4.1.13. Delayed Message Exchange

The plugin is currently marked as experimental but has been available for over a year (at the time of writing).
This functionality was tested with RabbitMQ 3.6.0 and version 0.0.1 of the plugin.

To use a RabbitAdmin to declare an exchange as delayed, you can set the delayed property on the exchange bean to true.
The RabbitAdmin uses the exchange type (Direct, Fanout, and so on) to set the x-delayed-type argument and declare the exchange with type x-delayed-message.

To send a delayed message, you can set the x-delay header through MessageProperties

MessageProperties properties = new MessageProperties();
properties.setDelay(15000);
template.send(exchange, routingKey,
        MessageBuilder.withBody("foo".getBytes()).andProperties(properties).build());

rabbitTemplate.convertAndSend(exchange, routingKey, "foo", new MessagePostProcessor() {
    @Override
    public Message postProcessMessage(Message message) throws AmqpException {
        message.getMessageProperties().setDelay(15000);
        return message;
    }
});

To check if a message was delayed, use the getReceivedDelay() method on the MessageProperties.
It is a separate property to avoid unintended(意外的) propagation to an output message generated from an input message.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值