RabbitMQ实用篇
RabbitMQ引入依赖
RabbitMQ依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
快速入门
1.publisher实现(出版者)
思路:
- 建立连接
- 创建Channel
- 声明队列
- 发送消息
- 关闭连接和channel
代码实现:
package cn.alidada.mq.helloword;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
@Slf4j
public class PublisherTest {
/**
* - 建立连接
* - 创建Channel
* - 声明队列
* - 发送消息
* - 关闭连接和channel
*/
@Test
public void testSendingMessages() throws Exception {
// 1.建立连接
ConnectionFactory factory = new ConnectionFactory();
// 1.1. 设置连接参数,分别是,主机名、端口、虚拟主机、用户名、密码
factory.setHost("192.168.150.101"); //主机名称
factory.setPort(5672); //主机端口
factory.setVirtualHost("/"); //虚拟主机
factory.setUsername("alidada"); //用户名
factory.setPassword("123321"); //密码
// 1.2 建立连接
Connection connection = factory.newConnection();
// 2.创建通道Channel
Channel channel = connection.createChannel();
// 3.声明队列
String queueName = "simple.queue";
channel.queueDeclare(queueName, false, false, false, null);
// 4.发送消息
for (int i = 0; i < 50; i++) {
Thread.sleep(2000);
String message = "hello reabbitmq";
channel.basicPublish("", queueName, null, message.getBytes(StandardCharsets.UTF_8));
log.info("发送的消息成功:{}", message);
}
// 5.关闭通道
channel.close();
connection.close();
}
}
2.consumer实现(消费者)
代码思路:
- 建立连接
- 创建Channel
- 声明队列
- 订阅消息
代码实现:
package cn.alidada.mq.helloworld;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConsumerTest {
public static void main(String[] args) throws IOException, TimeoutException {
// 1.建立连接
ConnectionFactory factory = new ConnectionFactory();
// 1.1.设置连接参数,分别是:主机名、端口号、虚拟主机、用户名、密码
factory.setHost("192.168.150.101");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("alidada");
factory.setPassword("123321");
// 1.2.建立连接
Connection connection = factory.newConnection();
// 2.创建通道Channel
Channel channel = connection.createChannel();
// 3.创建队列
String queueName = "simple.queue";
channel.queueDeclare(queueName, false, false, false, null);
// 4.订阅消息
channel.basicConsume(queueName, true, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
// 5.处理消息
String message = new String(body);
System.out.println("接收到消息:【" + message + "】");
}
});
System.out.println("等待接收消息。。。。");
}
}
SpringAMQP
SpringAMQP是基于RabbitMQ封装的一套模板,并且还利用SpringBoot对其实现了自动装配,使用起来非常方便。
SpringAmqp的官方地址:https://spring.io/projects/spring-amqp
AMQP: 是 Advanced Message Queuing Protocol 的缩写。其中字母代表的含义如下:
AMQP 表示高级消息队列协议,它是一种用于消息传递的协议标准。
A: Advanced(高级)
M: Message(消息)
Q: Queuing(队列)
P: Protocol(协议)
Spring AMQP : 是一个基于 Spring 框架的开源项目,它提供了在应用程序中使用 AMQP(高级消息队列协议)进行消息传递的支持。Spring AMQP 为开发人员提供了一个简单且强大的方式来与消息队列系统(如 RabbitMQ)进行交互。
消息生产者和消费者:Spring AMQP 提供了用于创建消息生产者和消费者的 API,使开发人员能够方便地发送和接收消息。
消息监听器容器:Spring AMQP 提供了消息监听器容器,可以自动监听和处理队列中的消息。开发人员只需编写一个消息监听器,然后将其注册到容器中,容器会负责从队列中获取消息并调用监听器进行处理。
消息转换器:Spring AMQP 提供了消息转换器,用于将消息从一种格式转换为另一种格式。这样,开发人员可以使用不同的消息格式(如 JSON、XML 等)进行消息传递,而不需要手动进行转换。
事务支持:Spring AMQP 支持事务,开发人员可以在发送和接收消息时使用事务,确保消息的原子性和一致性。
异常处理:Spring AMQP 提供了异常处理机制,可以处理发送和接收消息时可能出现的异常情况,例如连接失败、消息发送失败等。
SpringAMQP提供了三个功能:
- 自动声明队列、交换机及其绑定关系
- 基于注解的监听器模式,异步接收消息
- 封装了RabbitTemplate工具,用于发送消息
1.Basic Queue 基本队列模型
在父工程mq-demo中引入依赖
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
1.消息发送
首先配置MQ地址,在publisher服务的application.yml中添加配置:
spring:
rabbitmq:
host: 192.168.150.101 # 主机名
port: 5672 # 端口
virtual-host: / # 虚拟主机
username: alidada # 用户名
password: 123321 # 密码
1.1 测试类 RabbitTemplate实现消息发送:
package cn.alidada.mq.spring;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSimpleQueue() {
// 队列名称
String queueName = "simple.queue";
// 消息
String message = "hello, spring amqp!";
// 发送消息
rabbitTemplate.convertAndSend(queueName, message);
}
}
2.消息接收
首先配置MQ地址,在consumer服务的application.yml中添加配置:
spring:
rabbitmq:
host: 192.168.150.101 # 主机名
port: 5672 # 端口
virtual-host: / # 虚拟主机
username: alidada # 用户名
password: 123321 # 密码
2.1消息接受
package cn.alidada.mq.listener;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueueMessage(String msg) throws InterruptedException {
System.out.println("spring 消费者接收到消息:【" + msg + "】");
}
}
2.WorkQueue 任务队列模型
Work queues,也被称为(Task queues),任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息。
**WorkQueue(任务队列)**模型是 RabbitMQ 中的一种消息模型,用于在多个消费者之间共享和处理任务。它的工作原理如下:
- 产者将任务消息发送到一个队列中。
- 多个消费者同时监听该队列,竞争获取任务消息。
- 每个任务消息只能被一个消费者接收和处理,确保每个任务只被处理一次。
- 当有多个消费者时,任务消息会被平均分配给不同的消费者,实现负载均衡。
- 消费者根据自身的处理能力和负载情况,自动调整并控制任务的处理速度。
任务队列模型常用于分布式系统中的任务处理,可以实现任务的并行处理、负载均衡和任务的持久化等功能。它可以提高系统的可靠性和扩展性,确保任务的可靠执行。
1. 能者多劳模式(负载均衡)
在spring中有一个简单的配置,可以解决这个问题。我们修改consumer服务的application.yml文件,添加配置:
spring:
rabbitmq:
listener:
simple:
prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
3.Fanout 交换机(广播模式)
Fanout 是 RabbitMQ 中的一种消息交换机类型。消息交换机是 RabbitMQ 中用于接收消息并将其路由到一个或多个队列的组件。Fanout 交换机的工作方式如下:
-
Fanout 交换机接收到消息后,会将消息广播到与之绑定的所有队列,不考虑路由键或者绑定规则。
-
无论队列是否有消费者,Fanout 交换机都会将消息发送到所有与之绑定的队列。
-
Fanout 交换机适用于广播模式,即一个消息需要被多个消费者同时接收和处理的场景。
-
每个消费者都会独立地接收到相同的消息副本,消费者之间没有竞争关系。
-
Fanout 交换机通常用于发布/订阅模式,其中一个生产者将消息发送到 Fanout 交换机,然后多个消费者分别从不同的队列中接收消息进行处理。
1.创建交换器配置类
package cn.alidada.mq.fanout;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Fanoutconfig {
/**
* 声明交换机
* @return Fanout 类型交换机
* 接受出版者 生产的消息 传递给交换机
*/
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("alidada.fanout");
}
/**
* 第一个队列
* @return
*/
@Bean
public Queue fanoutQueue1(){
return new Queue("fanout.queue1");
}
* 绑定队列和交换机
* @param fanoutQueue1
* @param fanoutExchange
* @return
*/
@Bean
public Binding bindingQueue1(Queue fanoutQueue1,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
/**
* 第二队列
* @return
*/
@Bean
public Queue fanoutQueue2(){
return new Queue("fanout.queue2");
}
/**
* 绑定队列和交换机
*/
@Bean
public Binding bindingQueue2(Queue fanoutQueue2,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
}
2.消息发送
@Test
public void testFanoutExchange() {
// 队列名称
String exchangeName = "alidada.fanout";
// 消息
String message = "hello, everyone!";
rabbitTemplate.convertAndSend(exchangeName, "", message);
}
3.消息接受
@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) {
log.info("消费者1接收到Fanout消息:{}",msg);
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg) {
log.info("消费者2接收到Fanout消息:{}",msg);
}
4.Direct 直连交换机(direct exchange 路由模式)
在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。
在Direct模型下:
- 队列与交换机的绑定,不能是任意绑定了,而是要指定一个
RoutingKey
(路由key) - 消息的发送方在 向 Exchange发送消息时,也必须指定消息的
RoutingKey
。 - Exchange不再把消息交给每一个绑定的队列,而是根据消息的
Routing Key
进行判断,只有队列的Routingkey
与消息的Routing key
完全一致,才会接收到消息
1.添加消费者
/**
* value 添加消费者
* exchange 添加交换机
* key 条件
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"),
exchange = @Exchange(name = "alidada.direct", type = ExchangeTypes.DIRECT),
key = {"red", "blue"}
))
public void listenDirectQueue1(String msg) {
log.info("消费者接收到direct.queue1的消息:{}", msg);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "alidada.direct", type = ExchangeTypes.DIRECT),
key = {"yellow", "red"}
))
public void listenDirectQueue2(String msg) {
log.info("消费者接收到direct.queue2的消息:{}", msg);
}
2.添加发送
@Test
public void testSendDirectExchange() {
// 交换机名称
String exchangeName = "alidada.direct";
// 消息
String message = "红色警报!日本乱排核废水,导致海洋生物变异,惊现哥斯拉!";
// 发送消息
rabbitTemplate.convertAndSend(exchangeName, "red", message);
}
5.Topic 路由模式加强版
Topic
类型的Exchange
与Direct
相比,都是可以根据RoutingKey
把消息路由到不同的队列。只不过Topic
类型Exchange
可以让队列在绑定Routing key
的时候使用通配符!
Routingkey
一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
通配符规则:
#
:匹配一个或多个词
*
:匹配不多不少恰好1个词
-
举例:
-
item.#
:能够匹配item.spu.insert
或者item.spu
-
item.*
:只能匹配item.spu
提问:
描述下Direct交换机与Topic交换机的差异?
- Topic交换机接收的消息RoutingKey必须是多个单词,以
**.**
分割- Topic交换机与队列绑定时的bindingKey可以指定通配符
#
:代表0个或多个词*
:代表1个词
1.消息发送
/**
* topicExchange
*/
@Test
public void testSendTopicExchange() {
// 交换机名称
String exchangeName = "alidada.topic";
// 消息
String message = "喜报!孙悟空大战哥斯拉,胜!";
// 发送消息
rabbitTemplate.convertAndSend(exchangeName, "china.news", message);
}
2.消息接受
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue1"),
exchange = @Exchange(name = "alidada.topic", type = ExchangeTypes.TOPIC),
key = "china.#"
))
public void listenTopicQueue1(String msg){
System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue2"),
exchange = @Exchange(name = "alidada.topic", type = ExchangeTypes.TOPIC),
key = "#.news"
))
public void listenTopicQueue2(String msg){
System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】");
}
6.配置JSON转换器
显然,JDK序列化方式并不合适。我们希望消息体的体积更小、可读性更高,因此可以使用JSON方式来做序列化和反序列化。
在publisher和consumer两个服务中都引入依赖:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.10</version>
</dependency>
配置消息转换器。
在启动类中添加一个Bean即可:
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}