文章目录
AMQP Component
Message
Message
是消息体body[]与消息属性MessageProperties的封装,如下所示:
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;
}
}
Exchange
Exchange
是收发消息的中转站,其中属性定义如下所示,见名知意:
public interface Exchange {
String getName();
String getExchangeType();
boolean isDurable();
boolean isAutoDelete();
Map<String, Object> getArguments();
}
Queue
Queue
是接受消息的组件,Exchange
通过对应的Routing key
路由到响应的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
}
Binding
Binding
是将Exchange
和Queue
绑定起来的组件,如下所示:
new Binding(someQueue, someDirectExchange, "foo.bar");
同时还提供了BindingBuilder
,使用链式语法的形式来构造Binding
,如下所示
Binding b = BindingBuilder.bind(someQueue).to(someTopicExchange).with("foo.*");
Connection Factory
Spring提供了三个连接工厂供我们选择,常用的
CachingConnectionFactory
。这三个工厂都支持publisher confirmations
。对于大多数用例,应该使用PooledChannelConnectionFactory。如果您想确保严格的消息排序而不需要使用范围操作,那么可以使用ThreadChannelConnectionFactory。如果你想使用相关的发布者确认,或者你想通过它的CacheMode打开多个连接,应该使用CachingConnectionFactory。
PooledChannelConnectionFactory
该工厂基于Apache Pool2管理一个连接和两个通道池。 一个池用于事务性通道,另一个池用于非事务性通道。 池是具有默认配置的GenericObjectPool。 提供回调以配置池; 有关更多信息,请参考Apache文档。
@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
这个工厂管理一个连接和两个ThreadLocal,一个用于事务性通道,另一个用于非事务性通道。这个工厂确保同一个线程上的所有操作使用相同的通道(只要通道保持打开)。这促进了严格的消息排序,而不需要作用域操作。为了避免内存泄漏,如果您的应用程序使用了许多短命线程,您必须调用工厂的closeThreadChannel()来释放通道资源。
CachingConnectionFactory
第三个工厂是CachingConnectionFactory
,这也是常用的工厂,见名知意,它具有缓存的功能,通过CacheMode
来设置,它有以下两个缓存模式:
- CHANNEL 缓存通道,默认缓存模式。
- CONNECTION 缓存连接和每个连接中的通道。
默认通道缓存大小为25
DEFAULT_CHANNEL_CACHE_SIZE = 25
CachingConnectionFactory
还提供了一个connectionLimit
属性用于限制连接数的大小。当超出了最大连接数时,通过等待channelCheckoutTimeout
毫秒获取连接。
private Connection connectionFromCache() {
...
if (cachedConnection == null && countOpenConnections() >= this.connectionLimit) {
cachedConnection = waitForConnection(now);
}
...
}
private ChannelCachingConnectionProxy waitForConnection(long now) {
...
if (countOpenConnections() >= this.connectionLimit) {
try {
this.connectionMonitor.wait(this.channelCheckoutTimeout);
cachedConnection = findIdleConnection();
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new AmqpException("Interrupted while waiting for a connection", e);
}
}
...
}
缓存的大小不是限制channel
的数量,它只是限制能缓存的数量。如果在RabbitMQ Admin UI
界面看到许多channel
正在快速的创建和关闭,则需要扩大channel
的缓存大小。
当channelCheckoutTimeout
参数大于0时,它会限制channel
的数量,如果达到限制,则调用线程阻塞,直到通道可用或达到此超时,在这种情况下将引发 AmqpTimeoutException
。在缓存模式为Connection
时,会通过Semaphore
来控制获取channel
的数量。
this.checkoutPermits.put(cachedConnection, new Semaphore(this.channelCacheSize));
在获取channel
中会判断channelCheckoutTimeout
是否大于0,获取许可证。
private Channel getChannel(ChannelCachingConnectionProxy connection, boolean transactional) {
...
if (this.channelCheckoutTimeout > 0) {
permits = obtainPermits(connection);
}
...
if (channel == null) {
try {
channel = getCachedChannelProxy(connection, channelList, transactional);
}
catch (RuntimeException e) {
if (permits != null) {
permits.release();
if (logger.isDebugEnabled()) {
logger.debug("Could not get channel; released permit for " + connection + ", remaining:"
+ permits.availablePermits());
}
}
throw e;
}
}
return channel
}
private Semaphore obtainPermits(ChannelCachingConnectionProxy connection) {
Semaphore permits;
permits = this.checkoutPermits.get(connection);
if (permits != null) {
try {
if (!permits.tryAcquire(this.channelCheckoutTimeout, TimeUnit.MILLISECONDS)) {
throw new AmqpTimeoutException("No available channels");
}
if (logger.isDebugEnabled()) {
logger.debug(
"Acquired permit for " + connection + ", remaining:" + permits.availablePermits());
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new AmqpTimeoutException("Interrupted while acquiring a channel", e);
}
}
else {
throw new IllegalStateException("No permits map entry for " + connection);
}
return permits;
}
框架中使用的通道(例如RabbitTemplate)会可靠地返回到缓存中。如果您在框架之外创建通道(例如,通过直接访问连接并调用createChannel()),则必须(通过关闭)可靠地返回它们,在finally块中,以避免通道耗尽。
连接工厂的配置
设置连接名称
//方式一
connectionFactory.setConnectionNameStrategy(connectionFactory -> "MY_CONNECTION");
//方式二
@Bean
public SimplePropertyValueConnectionNameStrategy cns() {
return new SimplePropertyValueConnectionNameStrategy("spring.application.name");
}
@Bean
public ConnectionFactory rabbitConnectionFactory(ConnectionNameStrategy cns) {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
...
connectionFactory.setConnectionNameStrategy(cns);
return connectionFactory;
}
监听连接阻塞事件
对于内存告警和磁盘报警,使用BlockedListener
进行监听。AbstractConnectionFactory
通过其内部的 BlockedListener
实现,分别发出 connectionblockeddevent
和 connectionunblockeddevent
。
connectionFactory.addConnectionListener(new ConnectionListener() {
@Override
public void onCreate(Connection connection) {
connection.addBlockedListener(new BlockedListener() {
@Override
public void handleBlocked(String s) throws IOException {
System.out.println("我被阻塞了");
}
@Override
public void handleUnblocked() throws IOException {
System.out.println("我未被阻塞");
}
});
System.out.println("我被创建了");
}
}
模拟内存告警
使用rabbitmqctl set_vm_memory_high_watermark 0.01
命令将内存阈值从0.4调回0.01,当发送一条消息,会在Rabbit Admin UI
界面在看到对应节点的内存信息已经报红了。此时会触发connectionblockeddevent
。
模拟磁盘告警
使用rabbitmqctl set_disk_free_limit 170G
设置空闲磁盘大小如果小于该值对应Rabbit Admin Ui
界面节点就会报红。
从版本1.7.7开始,提供了一个
AmqpResourceNotAvailableException
,当SimpleConnection.createChannel()
不能创建通道时抛出该异常(例如,因为达到了channelMax
限制并且缓存中没有可用的通道)。您可以在RetryPolicy
中使用此异常来恢复某些回退之后的操作。
自动恢复
4.0.x客户端默认启用自动恢复。虽然与此特性兼容,但
Spring AMQP
有自己的恢复机制,通常不需要客户机恢复特性。我们建议禁用amqp-client自动恢复,以避免在代理可用但连接尚未恢复时获得AutoRecoverConnectionNotCurrentlyOpenException
实例。你可能会注意到这个异常,例如,当在RabbitTemplate
中配置了一个RetryTemplate
,甚至当故障转移到集群中的另一个broker
时。由于自动恢复连接是在计时器上恢复的,因此使用Spring AMQP
的恢复机制可以更快地恢复连接。从版本1.7.1开始,Spring AMQP
禁用AMQP-Client
自动恢复,除非你显式地创建自己的RabbitMQ连接工厂,并将其提供给CachingConnectionFactory
。默认情况下,由RabbitConnectionFactoryBean
创建的RabbitMQ ConnectionFactory
实例也禁用了该选项。
多连接工厂配置
通过SimpleRoutingConnectionFactory
配置多个连接工厂:
// 连接工厂配置
@Bean("connectionFactory1")
public ConnectionFactory connectionFactory1() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("123456");
connectionFactory.setConnectionNameStrategy(connectionFactory1 -> "MY_CONNECTION1");
//设置缓存模式为连接
connectionFactory.setCacheMode(CachingConnectionFactory.CacheMode.CONNECTION);
return connectionFactory;
}
@Bean("connectionFactory2")
public ConnectionFactory connectionFactory2() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
connectionFactory.setUsername("newadmin");
connectionFactory.setPassword("123456");
connectionFactory.setConnectionNameStrategy(connectionFactory1 -> "MY_CONNECTION2");
connectionFactory.setVirtualHost("test");
//设置缓存模式为连接
connectionFactory.setCacheMode(CachingConnectionFactory.CacheMode.CONNECTION);
return connectionFactory;
}
//路由连接工厂配置
@Bean("simpleConnectionFactory")
@Primary //优先注入该连接工厂
public ConnectionFactory simpleConnectionFactory(@Qualifier("connectionFactory1")ConnectionFactory one,@Qualifier("connectionFactory2")ConnectionFactory two) {
SimpleRoutingConnectionFactory simpleRoutingConnectionFactory = new SimpleRoutingConnectionFactory();
Map<Object, ConnectionFactory> map = new HashMap<>();
map.put("one",one);
map.put("two",two);
simpleRoutingConnectionFactory.setTargetConnectionFactories(map);
return simpleRoutingConnectionFactory;
}
发送消息
ApplicationContext context = new AnnotationConfigApplicationContext(HelloWorldConfiguration.class);
ExecutorService executorService = Executors.newFixedThreadPool(10);
RabbitTemplate amqpTemplate = context.getBean(RabbitTemplate.class);
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
//使用connectionFactory2
SimpleResourceHolder.bind(amqpTemplate.getConnectionFactory(),"two");
amqpTemplate.convertAndSend("Hello World");
System.out.println("Sent: Hello World");
//解绑使用connectionFactory2
SimpleResourceHolder.unbind(amqpTemplate.getConnectionFactory());
countDownLatch.countDown();
}
});
}
countDownLatch.await();
executorService.shutdownNow();
Connection and Channel Listeners
简单列举Connection和Channel
的创建事件。关闭、停止事件未列出。
connectionFactory.addConnectionListener(new ConnectionListener() {
@Override
public void onCreate(Connection connection) {
connection.addBlockedListener(new BlockedListener() {
@Override
public void handleBlocked(String s) throws IOException {
}
@Override
public void handleUnblocked() throws IOException {
}
});
}
});
connectionFactory.addChannelListener(new ChannelListener() {
@Override
public void onCreate(Channel channel, boolean transactional) {
}
});
定义客户端属性
在Rabbit Admin UI
界面中能够看到。
connectionFactory.getRabbitConnectionFactory().getClientProperties().put("Name","WuHan");