SpringAMQP Connecion Factory

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是将ExchangeQueue绑定起来的组件,如下所示:

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 实现,分别发出 connectionblockeddeventconnectionunblockeddevent

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");
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值