MQ作为消息队列中间件,经常会被我们用到各种环境中,例如:异步处理、削峰、解耦等。RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件),轻量、高性能、健壮、可伸缩,是经常被使用的MQ之一。
在spring中使用rabbitmq,我们可以借助Spring AMQP模块。Spring AMQP包含两部分内容:spring-amqp和spring-rabbit。spring-amqp不依赖任何特定AMQP代理实现,只为提供基于AMQP的通用抽象。目前,spring针对spring-amqp的通用抽象,只基于rabbitmq做了实现,也就是这里的spring-rabbit。在spring中使用rabbitmq是非常简单的,利用提供的AmqpTemplete或者RabbitTemplete就能够很好的完成任务,至于springboot中就更加的简单了,相关的配置已经自动完成,你可以直接使用它提供的相关的工具类完成工作。具体内容就不多说了,下面我们先来一个项目了解一下。
在springboot中使用rabbitmq,我们需要引入spring-boot-starter-amqp,如下:
pom.xml
org.springframework.boot
spring-boot-starter-amqp
application.properties:
#rabbitmq配置信息
spring.rabbitmq.host=localhost
spring.rabbitmq.username=php
spring.rabbitmq.password=960202
RabbitmqConfig ,@Configuration表明该文件是一个配置文件,可以被spring加载到,该文件用于配置交换机、队列、绑定等,@EnableRabbit用于开启rabbitmq。内容如下:
@Configuration
@EnableRabbit
public class RabbitmqConfig {
@Bean("test_queue")
public Queue queue() {
return new Queue("test_queue");
}
}
Producer 生产者,RabbitTemplate是spring amqp模块提供给我们用于消息的发送与接受。使用如下:
@Component
public class Producer {
private final RabbitTemplate rabbitTemplate;
@Autowired
public Producer(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
public void produce(String message) {
this.rabbitTemplate.convertAndSend("test_queue", message);
}
}
Consumer 消费者,@RabbitListener用作异步消息监听,是极其优雅的一种消息消费方式:
@Component
public class Consumer {
@RabbitListener(queues = "test_queue")
public void consume(String message) throws IOException {
System.out.println("接受到消息:" + message);
}
}
测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
@Resource
private Producer producer;
@Test
public void contextLoads(){
this.producer.produce("hello world");
}
}
大家可以发现,在springboot中使用rabbitmq是如此的简单,几个注解加上rabbitmq配置就可以解决,至于内部是如何实现的,接下来是我们需要了解的重点了。另外在这里我没有介绍rabbitmq一些常见的知识点,例如:exchange(交换机),binding(绑定),routing key(路由键)等,这些东西都是比较基础的,大家应该都比较清楚,若有需要,后续我再写一篇文章,详细介绍这些内容,本文不再描述。
初步使用都是比较简单的,看看demo就可以写出来,但是只有了解其内部,我们才能掌握的更加熟练,遇到问题时,才能够迎刃而解,最主要的是避免项目使用过程中留坑。首先了解一下rabbitmq的属性配置内容:
@ConfigurationProperties(prefix = "spring.rabbitmq")
public class RabbitProperties {
//ip地址
private String host = "localhost";
//端口
private int port = 5672;
//用户名
private String username = "guest";
//密码
private String password = "guest";
//ssl认证信息
private final Ssl ssl = new Ssl();
//虚拟主机
private String virtualHost;
//多个rabbitmq地址,以逗号分隔
private String addresses;
//心跳检测 s(默认)
@DurationUnit(ChronoUnit.SECONDS)
private Duration requestedHeartbeat;
//发布确认 防止消息在到达rabbitmq服务器时丢失(true,假如消息和队列是持久化的,则会在消息存入磁盘后返回确认消息)
private boolean publisherConfirms;
//发布返回 若消息不能正确路由到任何队列,false就会直接丢弃消息,true则会把消息返回给生产者,可以通过returnListener监听return事件
private boolean publisherReturns;
//连接超时时间
private Duration connectionTimeout;
//缓存配置 具有两种缓存方式:channel、connection 默认使用channel
private final Cache cache = new Cache();
//监听配置 默认两种监听容器:SIMPLE、DIRECT 默认使用SIMPLE
private final Listener listener = new Listener();
//templete配置 内含retry重试机制配置
private final Template template = new Template();
}
当我们知道rabbitmq的配置属性之后,心里大致就知道了具体有哪些东西,然后我们就可以分别了解这些内容,当这些东西都了解之后,也差不多对springboot中rabbitmq的运用了解清楚了。
配置信息我们已经知道了,现在我们就来看看这些配置信息是在什么时候,什么地方使用的。进入rabbitmq的自动配置类RabbitAutoConfiguration,其中存在如下片段:
/**
*仅当上下文中不存在ConnectionFactory Bean时才初始化
*该类创建CachingConnectionFactory,用于缓存connection以及channel
*默认采用channel缓存级别,缓存connection 1个,channel 25个
*/
@Configuration
@ConditionalOnMissingBean(ConnectionFactory.class)
protected static class RabbitConnectionFactoryCreator {
@Bean
public CachingConnectionFactory rabbitConnectionFactory(
RabbitProperties properties,
ObjectProvider connectionNameStrategy)
throws Exception {
PropertyMapper map = PropertyMapper.get();
//根据RabbitProperties信息封装CachingConnectionFactory对象
CachingConnectionFactory factory = new CachingConnectionFactory(
getRabbitConnectionFactoryBean(properties).getObject());
map.from(properties::determineAddresses).to(factory::setAddresses);
map.from(properties::isPublisherConfirms).to(factory::setPublisherConfirms);
map.from(properties::isPublisherReturns).to(factory::setPublisherReturns);
RabbitProperties.Cache.Channel channel = properties.getCache().getChannel();
map.from(channel::getSize).whenNonNull().to(factory::setChannelCacheSize);
map.from(channel::getCheckoutTimeout).whenNonNull().as(Duration::toMillis)
.to(factory::setChannelCheckoutTimeout);
//设置缓存模式(connection/channel)
RabbitProperties.Cache.Connection connection = properties.getCache()
.getConnection();
map.from(connection::getMode).whenNonNull().to(factory::setCacheMode);
//缓存大小(仅当缓存模式为connection时有效)
map.from(connection::getSize).whenNonNull()
.to(factory::setConnectionCacheSize);
map.from(connectionNameStrategy::getIfUnique).whenNonNull()
.to(factory::setConnectionNameStrategy);
return factory;
}
}
注意:上述我们所说的缓存,并不是channel默认只能使用25个,而是说只会缓存25个,假如使用的时候超过25个,超过的部分在使用结束后将会直接关闭。当然,若想要channel数量受到限制,可以设置channelCheckoutTimeout和channelCacheSize属性,channelCheckoutTimeout>0时,获取channel超过时间,将会抛出AmqpTimeoutException异常。
rabbitTemplete提供多种消息发送和接受API,并且提供MessageConverter。
@Bean
@ConditionalOnSingleCandidate(ConnectionFactory.class)
@ConditionalOnMissingBean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
PropertyMapper map = PropertyMapper.get();
RabbitTemplate template = new RabbitTemplate(connectionFactory);
//设置消息转换器
MessageConverter messageConverter = this.messageConverter.getIfUnique();
if (messageConverter != null) {
template.setMessageConverter(messageConverter);
}
//开启mandatory spring.rabbitmq.template.mandatory为null则取spring.rabbitmq.publisher-returns的值
template.setMandatory(determineMandatoryFlag());
RabbitProperties.Template properties = this.properties.getTemplate();
//若开启重试机制,则将重试机制添加到RabbitTemplate
if (properties.getRetry().isEnabled()) {
template.setRetryTemplate(createRetryTemplate(properties.getRetry()));
}
//receive()操作超时时间
map.from(properties::getReceiveTimeout).whenNonNull().as(Duration::toMillis)
.to(template::setReceiveTimeout);
//sendAndReceive()操作超时时间
map.from(properties::getReplyTimeout).whenNonNull().as(Duration::toMillis)
.to(template::setReplyTimeout);
//设置默认交换机 缺省""
map.from(properties::getExchange).to(template::setExchange);
//设置默认路由键 缺省""
map.from(properties::getRoutingKey).to(template::setRoutingKey);
return template;
}
在消费消息的时候,可能因为网络波动或其它原因导致消息手动ack,或者处理超时等,导致消息消费失败,这时我们不想直接抛出异常,而是重新获取消息进行消费,这时就可以引入重试机制(注意数据一致性),如下内容:
private RetryTemplate createRetryTemplate(RabbitProperties.Retry properties) {
PropertyMapper map = PropertyMapper.get();
RetryTemplate template = new RetryTemplate();
//默认最大重试3次
SimpleRetryPolicy policy = new SimpleRetryPolicy();
map.from(properties::getMaxAttempts).to(policy::setMaxAttempts);
template.setRetryPolicy(policy);
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
//重试初始间隔时间 (默认0.1s)
map.from(properties::getInitialInterval).whenNonNull().as(Duration::toMillis)
.to(backOffPolicy::setInitialInterval);
//间隔时间倍数 (默认2) 下一次间隔时间=上一次间隔时间*multiplier
map.from(properties::getMultiplier).to(backOffPolicy::setMultiplier);
//重试最大间隔时间 (默认30s)
map.from(properties::getMaxInterval).whenNonNull().as(Duration::toMillis)
.to(backOffPolicy::setMaxInterval);
template.setBackOffPolicy(backOffPolicy);
return template;
}
提供RabbitAdmin, 用于对交换机、队列、绑定做管理。
@Bean
@ConditionalOnSingleCandidate(ConnectionFactory.class)
@ConditionalOnProperty(prefix = "spring.rabbitmq", name = "dynamic", matchIfMissing = true)
@ConditionalOnMissingBean
public AmqpAdmin amqpAdmin(ConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
spring amqp模块中提供了两种容器供大家使用:SimpleMessageListenerContainer和DirectMessageListenerContainer,容器提供对监听的启动和停止。SimpleMessageListenerContainer中每个consumer采用专用线程,可以配置多个队列,消费者使用同一个线程处理多个队列的消息。SimpleMessageListenerContainer的并发由concurrentConsumers和maxConcurrentConsumers动态控制,concurrentConsumers代表初始固定的消费者数量,maxConcurrentConsumers代表最大的消费者数量,假设消息监听采用的是@RabbitListener,则可以通过设置concurrency=2-10达到控制消费者数量的效果。DirectMessageListenerContainer与SimpleMessageListenerContainer最大的不同,在于为每个消费者提供一个单独的channel并且消费者的逻辑不是在自己的线程池中处理的,而是在rabbit 客户端线程池处理,可以有效减少线程上下文切换所带来的资源消耗。另外DirectMessageListenerContainer在运行时添加队列或者是移除队列的性能最好,因为SimpleMessageListenerContainer会先移除所有的消费者再重新创建,而DirectMessageListenerContainer只会处理受到影响的消费者。
消息异步监听,我们可以直接在方法上通过使用该注解进行监听某个或者多个队列,例如:
@RabbitListener(queues = "test_queue")
public void consume(String message) throws IOException {
System.out.println("接受到消息:" + message);
}
@RabbitListener支持多个属性,例如:containerFactory设置容器工厂,queues设置监听队列,concurrency设置消费者并发数量等,还有一些其它的属性,大家可以自己慢慢探索。假如我们想让某个监听手动ack,可以配置此参数containerFactory,为该监听配置独立的容器工厂,虽然我们可以通过spring.rabbitmq.listener.simple.acknowledge-mode或者spring.rabbitmq.listener.direct.acknowledge-mode配置手动ack,但它是全局共享的。
springboot2与rabbitmq的初步介绍,就到此为此了,目前没有给大家说有哪些具体的使用,只是简单的介绍了有哪些内容,限于篇幅,后面我们专门来描述在现实项目中,我们应该如何来使用这些内容。若上述内容有不对的地方,望大家能够指正,共同学习,共同进步。