0. 虽然这段过的乱七八糟,但也还算是在道上
- 首先,从很久之前就 非常之想要找一个 官方的,英文的,完整的 框架文档过一下
- 这对精进 英文语法,文档读写 都很有帮助
- 正好,前面 pajamas.starter.client 集成到了 mq的阶段
- 正好,spring amqp 相比 spring cloud stream 作为 MQ-client ,提供的支持更加具体(毕竟只支持RabbitMQ)
- 正好,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.
- One pool is for transactional channels
- 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,
- one for transactional channels
- 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 totrue
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.