本文内容来自SpringBoot相关书籍和项目学习总结
1 RabbitMQ入门
1.1 RabbitMQ简介
什么是消息队列?
消息队列实现系统之间的双向解耦,生产者往消息队列中发送消息,消费者从队列中拿取消息并处理,生产者不用关心是谁来消费,消费者不用关心谁在生产消息,从而达到系统解耦的目的,也大大提高了系统的高可用性和高并发能力。
什么是RabbitMQ?
RabbitMQ基于开源的AMQP协议实现,服务器端用Erlang语言编写,支持多种客户端,如Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP、AJAX等。
主要应用场景:
- 异步任务
- 提速
- 接口解耦
- 削峰
什么是AMQP协议?
AMQP(Advanced Message Queuing Protocol,高级消息队列协议)是应用层协议的开放标准,是为面向消息的中间件设计。基于此协议的客户端可与消息中间件传递消息,从而不受产品、开发语言等条件限制。消息中间件主要用于组件之间的解耦,消息发送者无须知道消息使用者的存在,反之亦然。
1.2 RabbitMQ组件功能
RabbitMQ中有几个非常重要的组件:服务实体(Broker)、虚拟主机(Virtual Host)、交换机(Exchange)、队列(Queue)和绑定(Binging)等。
1.3 交换机
交换机(Exchange)的功能主要是接收消息并且转发到绑定的队列,交换机不存储消息,只是把消息分发给各自的队列。
当消息发送到交换机(Exchange)时,通过消息携带的RoutingKey与当前交换机所有绑定的BindingKey进行匹配,如果满足匹配规则,则往BindingKey所绑定的消息队列发送消息,这样就解决了向RabbitMQ发送一次消息,可以分发到不同的消息队列,实现消息路由分发的功能。
交换机有Direct、Topic、Headers和Fanout四种消息分发类型:
- Direct:其类型的行为是“先匹配,再发送”,即在绑定时设置一个BindingKey,当消息的RoutingKey匹配队列绑定的BindingKey时,才会被交换机发送到绑定的队列中。
- Topic:按规则转发消息(最灵活)。支持用
*或#的模式进行绑定。*表示匹配一个单词,#表示匹配0个或者多个单词。 - Headers:设置header attribute参数类型的交换机。根据应用程序消息的特定属性进行匹配,这些消息可能在绑定key中标记为可选或者必选。
- Fanout:转发消息到所有绑定队列(广播)。将消息广播到所有绑定到它的队列中,而不考虑队列绑定的BindingKey的值。
2 SpringBoot集成RabbitMQ
2.1 准备工作
1、添加pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2、修改配置文件
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: root
password: 123456
virtual-host: imooc-news-dev
3、定义交换机和队列
@Configuration
public class RabbitMQConfig {
//定义交换机的名字
public static final String EXCHANGE_ARTICLE = "exchange_article";
//定义队列的名字
public static final String QUEUE_DOWNLOAD_ARTICLE = "queue_download_article";
/**
* 创建交换机
*
* @return
*/
@Bean(EXCHANGE_ARTICLE)
public TopicExchange topicExchange() {
return new TopicExchange(EXCHANGE_ARTICLE);
}
/**
* 创建队列
*
* @return
*/
@Bean(QUEUE_DOWNLOAD_ARTICLE)
public Queue queue() {
return new Queue(QUEUE_DOWNLOAD_ARTICLE);
}
/**
* 队列绑定到交换机,指定路由键(RoutingKey)
*
* @return
*/
@Bean
public Binding binding() {
return BindingBuilder
.bind(queue())
.to(topicExchange())
.with("article.#.do");
}
}
2.2 创建生产者和消费者
1、创建生产者发送消息
rabbitTemplate.convertAndSend(
RabbitMQConfig.EXCHANGE_ARTICLE,
"article.hello.do",
"这是从生产者发送的消息~");
RabbitTemplate提供了convertAndSend方法发送消息。convertAndSend方法有routingKey和message两个参数:
1)routingKey为要匹配的路由键。
2)message为具体的消息内容。
2、创建消费者消费消息
@Component
public class RabbitMQConsumer {
@RabbitListener(queues = {RabbitMQConfig.QUEUE_DOWNLOAD_HTML})
@RabbitHandler
public void watchQueue(String payload, Message message) {
System.out.println(payload);
String routingKey = message.getMessageProperties().getReceivedRoutingKey();
System.out.println(routingKey);
}
}
1)@RabbitListener注解提供了@QueueBinding、@Queue、@Exchange等对象,通过这个组合注解配置交换机、绑定路由并且配置监听功能等。
2)@RabbitHandler注解为具体接收的方法。
3 实战案例:异步解耦
1、创建生产者
通过rabbitTemplate向指定的交换机(Exchange)发送消息,通过RoutingKey与交换机绑定的BindingKey进行匹配,对符合匹配规则的消息队列发送消息。
@Autowired
private RabbitTemplate rabbitTemplate;
private void doDownloadArticleHTMLByMQ(String articleId, String articleMongoId) {
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_ARTICLE,
"article.download.do",
articleId + "," + articleMongoId);
}
2、创建消费者
消费者在监听到消息后,根据RoutingKey进行判断,然后对消息进行解析,执行具体的操作。
@Component
public class RabbitMQConsumer {
@Autowired
private ArticleHTMLComponent articleHTMLComponent;
@RabbitListener(queues = {RabbitMQConfig.QUEUE_DOWNLOAD_HTML})
@RabbitHandler
public void watchQueue(String payload, Message message) {
System.out.println(payload);
String routingKey = message.getMessageProperties().getReceivedRoutingKey();
if (routingKey.equalsIgnoreCase("article.download.do")) {
String articleId = payload.split(",")[0];
String articleMongoId = payload.split(",")[1];
try {
articleHTMLComponent.download(articleId, articleMongoId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
4 实战案例:延迟队列
注:需要安装rabbitmq-delayed-message-exchange插件
1、开启延迟消息支持
@Configuration
public class RabbitMQDelayConfig {
//定义交换机的名字
public static final String EXCHANGE_DELAY = "exchange_delay";
//定义队列的名字
public static final String QUEUE_DELAY = "queue_delay";
/**
* 创建交换机
*
* @return
*/
@Bean(EXCHANGE_DELAY)
public TopicExchange topicExchange() {
TopicExchange exchange = new TopicExchange(EXCHANGE_DELAY);
exchange.setDelayed(true); //开启支持延迟消息
return exchange;
}
/**
* 创建队列
*
* @return
*/
@Bean(QUEUE_DELAY)
public Queue queue() {
return new Queue(QUEUE_DELAY);
}
/**
* 队列绑定到交换机,指定路由键(RoutingKey)
*
* @return
*/
@Bean
public Binding delayBinding() {
return BindingBuilder
.bind(queue())
.to(topicExchange())
.with("delay.#");
}
}
2、生产者发送延迟消息
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
// 设置消息的持久
message.getMessageProperties()
.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
// 设置消息延迟的时间,单位ms毫秒
message.getMessageProperties()
.setDelay(5000);
return message;
}
};
rabbitTemplate.convertAndSend(
RabbitMQDelayConfig.EXCHANGE_DELAY,
"delay.do",
"这是从生产者发送的延迟消息~",
messagePostProcessor);
3、消费者消费消息
@RabbitListener(queues = {RabbitMQDelayConfig.QUEUE_DELAY})
@RabbitHandler
public void watchQueue(String payload, Message message) {
System.out.println(payload);
String routingKey = message.getMessageProperties().getReceivedRoutingKey();
System.out.println(routingKey);
if (routingKey.equalsIgnoreCase("delay.do")) {
System.out.println("消费者接受的延迟消息:" + new Date());
}
}