Spring.AMQP 随笔 0 Introduction

0. 虽然这段过的乱七八糟,但也还算是在道上

  1. 首先,从很久之前就 非常之想要找一个 官方的,英文的,完整的 框架文档过一下
  2. 这对精进 英文语法,文档读写 都很有帮助
  3. 正好,前面 pajamas.starter.client 集成到了 mq的阶段
  4. 正好,spring amqp 相比 spring cloud stream 作为 MQ-client ,提供的支持更加具体(毕竟只支持RabbitMQ)
  5. 正好,rabbitMQ 用的也少,刚好可以在应用层面 好好增进一下感情

本文只是节选了 原文档中的第4章节,即主要相关;

很多具体的取值,案例,配置 都隐去了;

更多章节,细节都可以在原文章中找到;

本文会尽可能保留相似的目录结构;

本文的正文以的英文为主(本来就是 用来熟悉 英文文档的佐料罢了);

https://docs.spring.io/spring-amqp/docs/2.4.17/reference/html/#reference

4.1. Using Spring AMQP

4.1.1. AMQP Abstractions

Spring AMQP consists of two modules (each represented by a JAR in the distribution): spring-amqp and spring-rabbit.

4.1.1.1 Message

The 0-9-1 AMQP specification does not define a Message class or interface.
the content is passed as a byte-array argument and additional properties are passed in as separate arguments.
Spring AMQP defines a Message class as part of a more general AMQP domain model representation.
The purpose of the Message class is to encapsulate the body and properties

public class Message {
    private final MessageProperties messageProperties;
    private final byte[] body;
}

The MessageProperties interface defines several common properties,
such as ‘messageId’, ‘timestamp’, ‘contentType’, and several more.
You can also extend those properties with user-defined ‘headers’
by calling the setHeader(String key, Object value) method.

if a message body is a serialized Serializable java object,
it is no longer deserialized (by default) when performing toString() operations (such as in log messages).
This is to prevent unsafe deserialization.
By default, only java.util and java.lang classes are deserialized.

A simple wildcard is supported, for example com.something., *.MyClass.

Bodies that cannot be deserialized are represented by byte[<size>] in log messages.

4.1.1.2 Exchange

The Exchange interface represents an AMQP Exchange,
which is what a Message Producer sends to.
Each Exchange within a virtual host of a broker has a unique name

public interface Exchange {
    String getName();
    String getExchangeType();
    boolean isDurable();
    boolean isAutoDelete();
    Map<String, Object> getArguments();
}

As you can see, an Exchange also has a ‘type’ represented by constants defined in ExchangeTypes.
The basic types are: direct, topic, fanout, and headers.
The behavior varies(不同) across these Exchange types in terms of how they handle bindings to queues.

The AMQP specification also requires that any broker provide a “default” direct exchange that has no name.
All queues that are declared are bound to that default Exchange with their names as routing keys.

4.1.1.3 Queue

The Queue class represents the component from which a message consumer receives messages.

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

Notice that the constructor takes the queue name. Depending on the implementation,
the admin template may provide methods for generating a uniquely named queue.
Such queues can be useful as a “reply-to” address or in other temporary situations.
For that reason, the exclusive and autoDelete properties of an auto-generated queue would both be set to true.

4.1.1.4 Binding

Given that a producer sends to an exchange and a consumer receives from a queue,
the bindings that connect queues to exchanges are critical for connecting those producers and consumers via messaging.

// You can bind a queue to a DirectExchange with a fixed routing key:
new Binding(someQueue, someDirectExchange, "foo.bar");

// You can bind a queue to a TopicExchange with a routing pattern
new Binding(someQueue, someTopicExchange, "foo.*");

// You can bind a queue to a FanoutExchange with no routing key
new Binding(someQueue, someFanoutExchange);

// We also provide a BindingBuilder to facilitate a “fluent API” style
Binding b = BindingBuilder.bind(someQueue).to(someTopicExchange).with("foo.*");

By itself, an instance of the Binding class only holds the data about a connection.
In other words, it is not an “active” component.
However, the AmqpAdmin class can use Binding instances to actually trigger the binding actions on the broker.
Also, you can define the Binding instances by using Spring’s @Bean annotations within @Configuration classes.

4.1.2. Connection and Resource Management

The central component for managing a connection to the RabbitMQ broker is the ConnectionFactory interface.
The responsibility of a ConnectionFactory implementation is to provide an instance of org.springframework.amqp.rabbit.connection.Connection,
which is a wrapper for com.rabbitmq.client.Connection.

Choosing a Connection Factory

  • PooledChannelConnectionFactory

For most use cases

This factory manages a single connection and two pools of channels, based on the Apache Pool2.

  1. One pool is for transactional channels
  2. the other is for non-transactional channels.

  • ThreadChannelConnectionFactory

if you want to ensure strict message ordering without the need to use Scoped Operations.

This factory manages a single connection and two ThreadLocal s,

  1. one for transactional channels
  2. the other for non-transactional channels.

This factory ensures that all operations on the same thread use the same channel (as long as it remains open).

To avoid memory leaks, if your application uses many short-lived threads, you must call the factory’s closeThreadChannel() to release the channel resource.
A thread can transfer its channel(s) to another thread.


  • CachingConnectionFactory

If you want to use correlated publisher confirmations
or if you wish to open multiple connections, via its CacheMode.

By default, establishes a single connection proxy that can be shared by the application.
Sharing of the connection is possible since the “unit of work” for messaging with AMQP is actually a “channel”

The connection instance provides a createChannel method.
The CachingConnectionFactory implementation supports caching of those channels,
and it maintains separate caches for channels based on whether they are transactional.

You can configure the CachingConnectionFactory to cache connections as well as only channels.
In this case, each call to createConnection() creates a new connection (or retrieves an idle one from the cache).
Closing a connection returns it to the cache (if the cache size has not been reached).
Channels created on such connections are also cached.
To cache connections, set the cacheMode to CacheMode.CONNECTION.

This does not limit the number of connections.
Rather, it specifies how many idle open connections are allowed.

When the cache mode is CONNECTION, automatic declaration of queues and others is NOT supported.

Also, the amqp-client library by default creates a fixed thread pool for each connection.

When using a large number of connections, you should consider setting a custom executor on the CachingConnectionFactory.
Then, the same executor can be used by all connections and its threads can be shared.
The executor’s thread pool should be unbounded or set appropriately for the expected use (usually, at least one thread per connection).
If multiple channels are created on each connection, the pool size affects the concurrency,
so a variable (or simple cached) thread pool executor would be most suitable.

Channels used within the framework (for example, RabbitTemplate) are reliably returned to the cache.
If you create channels outside of the framework, (for example, by accessing the connections directly and invoking createChannel()),
you must return them (by closing) reliably, perhaps in a finally block, to avoid running out of channels.


Simple publisher confirmations are supported by all three factories.

When configuring a RabbitTemplate to use a separate connection, you can configure the publishing connection factory to be a different type.
By default, the publishing factory is the same type and any properties set on the main factory are also propagated to the publishing factory.

There is also a SingleConnectionFactory implementation that is available only in the unit test code of the framework.
It is simpler than CachingConnectionFactory, since it does not cache channels,
but it is not intended(打算) for practical usage outside of simple tests due to its lack of performance and resilience.

If you need to implement your own ConnectionFactory
for some reason, the AbstractConnectionFactory base class may provide a nice starting point.

how to create a new connection

CachingConnectionFactory connectionFactory = new CachingConnectionFactory("somehost");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");

Connection connection = connectionFactory.createConnection();

AddressResolver

you can now use an AddressResover to resolve the connection address(es).
This will override any settings of the addresses and host/port properties.

Naming Connections

A ConnectionNameStrategy is provided for the injection into the AbstractionConnectionFactory.
The connection name is displayed in the management UI.

This value does not have to be unique and cannot be used as a connection identifier
for example, in HTTP API requests.

Blocked Connections and Resource Constraints

The connection might be blocked for interaction from the broker that corresponds to the Memory Alarm.

Configuring the Underlying Client Connection Factory

The CachingConnectionFactory uses an instance of the Rabbit client ConnectionFactory.

A number of configuration properties are passed through
(host, port, userName, password, requestedHeartBeat, and connectionTimeout for example)
when setting the equivalent property on the CachingConnectionFactory.
To set other properties (clientProperties, for example),
you can define an instance of the Rabbit factory
and provide a reference to it by using the appropriate constructor of the CachingConnectionFactory.
When using the namespace (as described earlier),
you need to provide a reference to the configured factory in the connection-factory attribute.

The 4.0.x client enables automatic recovery by default. While compatible with this feature,
Spring AMQP has its own recovery mechanisms and the client recovery feature generally is not needed.
We recommend disabling amqp-client automatic recovery,
to avoid getting AutoRecoverConnectionNotCurrentlyOpenException instances
when the broker is available but the connection has not yet recovered.
You may notice this exception, for example, when a RetryTemplate is configured in a RabbitTemplate,
even when failing over to another broker in a cluster.
Since the auto-recovering connection recovers on a timer,
the connection may be recovered more quickly by using Spring AMQP’s recovery mechanisms.
Spring AMQP disables amqp-client automatic recovery
unless you explicitly create your own RabbitMQ connection factory and provide it to the CachingConnectionFactory.
RabbitMQ ConnectionFactory instances created by the RabbitConnectionFactoryBean also have the option disabled by default.

RabbitConnectionFactoryBean and Configuring SSL

see the source document, please

Connecting to a Cluster

see the source document, please

Routing Connection Factory

This factory provides a mechanism to configure mappings for several ConnectionFactories
and determine a target ConnectionFactory by some lookupKey at runtime.

Typically, the implementation checks a thread-bound context.
For convenience, Spring AMQP provides the SimpleRoutingConnectionFactory,
which gets the current thread-bound lookupKey from the SimpleResourceHolder

SimpleResourceHolder.bind(rabbitTemplate.getConnectionFactory(), vHost);
rabbitTemplate.convertAndSend(payload);
SimpleResourceHolder.unbind(rabbitTemplate.getConnectionFactory());

It is important to unbind the resource after use

Queue Affinity and the LocalizedQueueConnectionFactory

When using HA queues in a cluster, for the best performance,
you may want to connect to the physical broker where the lead queue resides.
The CachingConnectionFactory can be configured with multiple broker addresses.
This is to fail over(故障转移) and the client attempts to connect in order.
The LocalizedQueueConnectionFactory uses the REST API provided by the management plugin
to determine which node is the lead for the queue.
It then creates (or retrieves from a cache) a CachingConnectionFactory that connects to just that node.
If the connection fails, the new lead node is determined and the consumer connects to it.
The LocalizedQueueConnectionFactory is configured with a default connection factory,
in case the physical location of the queue cannot be determined, in which case it connects as normal to the cluster.

The LocalizedQueueConnectionFactory is a RoutingConnectionFactory
and the SimpleMessageListenerContainer uses the queue names as the lookup key

Publisher Confirms and Returns

Confirmed (with correlation, 相关) and returned messages are supported by setting the CachingConnectionFactory property
publisherConfirmType to ConfirmType.CORRELATED and the publisherReturns property to true.

When these options are set, Channel instances created by the factory are wrapped in an PublisherCallbackChannel,
which is used to facilitate(促进) the callbacks.
When such a channel is obtained, the client can register a PublisherCallbackChannel.Listener with the Channel.
The PublisherCallbackChannel implementation contains logic to route a confirm or return to the appropriate(合适的) listener.

Connection and Channel Listeners

The connection factory supports registering ConnectionListener and ChannelListener implementations.
This allows you to receive notifications for connection and channel related events.

Logging Channel Close Events

a mechanism to enable users to control logging levels.

Runtime Cache Properties

  • Cache properties for CacheMode.CHANNEL
  • Cache properties for CacheMode.CONNECTION

具体的表格

RabbitMQ Automatic Connection/Topology(拓扑) recovery

Spring AMQP has provided its own connection and channel recovery in the event of a broker failure.
Also, as discussed in Configuring the Broker,
the RabbitAdmin re-declares any infrastructure beans (queues and others) when the connection is re-established.
It therefore does not rely on the auto-recovery that is now provided by the amqp-client library.
The amqp-client, has auto recovery enabled by default.
There are some incompatibilities between the two recovery mechanisms so,
by default, Spring sets the automaticRecoveryEnabled property on the underlying RabbitMQ connectionFactory to false.
Even if the property is true,
Spring effectively disables it, by immediately closing any recovered connections.

4.1.3 Adding Custom Client Connection Properties

The CachingConnectionFactory now lets you access the underlying connection factory to allow,
for example, setting custom client properties.

connectionFactory.getRabbitConnectionFactory().getClientProperties().put("thing1", "thing2");

These properties appear in the RabbitMQ Admin UI when viewing the connection.

4.1.4. AmqpTemplate

As with many other high-level abstractions provided by the Spring Framework and related projects,
Spring AMQP provides a “template” that plays a central role.
The interface that defines the main operations is called AmqpTemplate.
Those operations cover the general behavior for sending and receiving messages.

Adding Retry Capabilities

You can now configure the RabbitTemplate to use a RetryTemplate to help with handling problems with broker connectivity.
The following is only one example that uses an exponential(指数级) back off policy and the default SimpleRetryPolicy,
which makes three tries before throwing the exception to the caller.

Usage:

  • As a RetryTemplate instance in RabbitTemplate
@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;
}
  • wrapped by RetryTemplate

The RecoveryCallback is somewhat limited, in that the retry context contains only the lastThrowable field.
For more sophisticated use cases, you should use an external RetryTemplate
so that you can convey additional information to the RecoveryCallback through the context’s attributes.

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

Publishing is Asynchronous - How to Detect Successes and Failures

The message is dropped and no return is generated. The underlying channel is closed with an exception.
By default, this exception is logged, but you can register a ChannelListener with the CachingConnectionFactory to obtain notifications of such events.
The following example shows how to add a ConnectionListener:

this.connectionFactory.addConnectionListener(new ConnectionListener() {
    @Override
    public void onCreate(Connection connection) {}
    @Override
    public void onShutDown(ShutdownSignalException signal) { ... }
});

You can examine the signal’s reason property to determine the problem that occurred.

To detect the exception on the sending thread,
you can setChannelTransacted(true) on the RabbitTemplate and the exception is detected on the txCommit().
However, transactions significantly(显著地) impede(阻碍) performance,
so consider this carefully before enabling transactions for just this one use case.

Scoped Operations

Normally, when using the template, a Channel is checked out of the cache (or created),
used for the operation, and returned to the cache for reuse.
In a multi-threaded environment, there is no guarantee(保证) that the next operation uses the same channel.
There may be times, however, where you want to have more control over the use of a channel
and ensure that a number of operations are all performed on the same channel.

Scoped operations are bound to a thread.

Strict Message Ordering in a Multi-Threaded Environment

The discussion in Scoped Operations applies only when the operations are performed on the same thread.

see the source document, please

Messaging Integration

RabbitMessagingTemplate (built on top of RabbitTemplate) provides an integration
with the Spring Framework messaging abstraction that is, org.springframework.messaging.Message.
This lets you send and receive messages by using the spring-messaging Message<?> abstraction.
This abstraction is used by other Spring projects,
such as Spring Integration and Spring’s STOMP support.
There are two message converters involved:

  • convert between a spring-messaging Message<?> and Spring AMQP’s Message abstraction
  • convert between Spring AMQP’s Message abstraction and the format required by the underlying RabbitMQ client library.
    By default, the message payload is converted by the provided RabbitTemplate instance’s message converter.
org.springframework.amqp.support.converter.MessagingMessageConverter amqpMessageConverter = new MessagingMessageConverter();
amqpMessageConverter.setPayloadConverter(myPayloadConverter);
// RabbitMessagingTemplate extends org.springframework.amqp.rabbit.core.AbstractMessagingTemplate<String>
rabbitMessagingTemplate.setAmqpMessageConverter(amqpMessageConverter);

Validated User Id

Using a Separate Connection

You can set the usePublisherConnection property to true to use a different connection to that used by listener containers, when possible.
This is to avoid consumers being blocked when a producer is blocked for any reason.
The connection factories maintain a second internal connection factory for this purpose;
by default it is the same type as the main factory, but can be set explicity if you wish to use a different factory type for publishing.
If the rabbit template is running in a transaction started by the listener container, the container’s channel is used, regardless of this setting.

In general, you should not use a RabbitAdmin with a template that has this set to true.
Use the RabbitAdmin constructor that takes a connection factory.
If you use the other constructor that takes a template, ensure the template’s property is false.
This is because, often, an admin is used to declare queues for listener containers.
Using a template that has the property set to true would mean that exclusive queues (such as AnonymousQueue) would be declared on a different connection to that used by listener containers.
In that case, the queues cannot be used by the containers.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值