RabbitMQ 学习
官网:RabbitMQ Tutorials | RabbitMQ
一、RabbitMQ 是什么?
RabbitMQ是一个开源的遵循AMQP协议实现的基于Erlang语言编写,支持多种客户端(语言)。用于在分布式系统中存储消息,转发消息,具有高可用,高可扩性,易用性等特征。
二、RabbitMQ 的作用
- 解耦:不同的应用程序之间不需要直接相互依赖,通过 RabbitMQ 传递消息进行通信。
- 异步处理:可以将耗时的操作放入消息队列,让系统在后台异步处理,提高系统的响应速度。
- 流量削峰:在高并发场景下,通过队列缓冲请求,避免系统瞬间压力过大。
三、RabbitMQ的运行流程
四、核心概念
Message:消息,消息是不具体的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成, 这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可 能需要持久性存储)等。
Publisher:消息的生产者,也是一个向交换器发布消息的客户端应用程序。
Connection:网络连接,比如一个TCP连接。
Channel:信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接,AMQP命令都是通过信道 发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁TCP都 是非常昂贵的开销,所以引入了信道的概念,以复用一条TCP连接。
Broker:表示消息队列服务器实体
Virtual Host:虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加 密环境的独立服务器域。每个 vhost 本质上就是一个mini版的RabbitMQ 服务器,拥 有自己的队列、交换器、绑定和权限机制。vhost是AMQP概念的基础,必须在连接时 指定,RabbitMQ 默认的vhost是/。
Exchange:交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
Routing key:是一个路由规则,虚拟机可以用它来确定如何路由一个特定消息。
Bindings:Exchange和Queue之间的虚拟连接,binding中可以绑定多个routing key.
Queue:消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直 在队列里面,等待消费者连接到这个队列将其取走。
Consumer:消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。
五、Docker 安装 RabbitMQ
安装参照
1:https://www.rabbitmq.com/download.html
2:https://registry.hub.docker.com/_/rabbitmq/
docker run -d --name my-rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:4.0-management
使用 http://你的IP地址:15672 访问rabbit控制台
用户名密码:guest
RabbitMQ界面简单介绍
主页面:
创建虚拟机
在RabbitMQ中,虚拟机(Virtual Host)是一种逻辑分区机制,用于将RabbitMQ资源进行逻辑隔离和管理。每个虚拟机都是一个独立的消息代理环境,拥有自己的交换机、队列、绑定和权限。
虚拟机提供了一种将应用程序或用户分组和隔离的方式。不同的应用程序或用户可以在各自的虚拟机中操作和管理自己的消息队列,而不会影响其他虚拟机中的队列。
创建好虚拟机之后,会默认给创建了交换机,不用的虚拟机可以指定不同的用户。
建立交换机
在RabbitMQ中,交换机(Exchange)是消息路由的中心组件之一。它接收从生产者发送的消息,并根据一定的规则将消息路由到一个或多个队列,以便消费者可以接收并处理这些消息。
交换机的主要作用是根据 消息的路由键(Routing Key)将消息发送到合适的队列。当生产者发布消息时,会指定一个交换机和一个路由键。交换机根据规则来确定如何将消息路由到队列,这些规则定义了交换机的类型。
Name(名称):交换机的唯一标识符,用于在RabbitMQ中识别交换机。名称是必需的,并且需要是唯一的。
Type(类型):指定交换机的类型,决定了交换机的路由策略。常见的交换机类型有Direct、Fanout、Topic和Headers。不同类型的交换机对应不同的路由规则。
Durable(持久化):指定交换机是否持久化到磁盘。如果将该参数设置为true,交换机将在RabbitMQ服务器重启后仍然存在。默认情况下,交换机是非持久化的。
Auto-delete(自动删除):指定交换机在不被使用时是否自动删除。如果将该参数设置为true,当没有与之绑定的队列或连接时,交换机将被自动删除。默认情况下,交换机是不会自动删除的。
Internal(内部交换机):指定交换机是否为内部交换机。内部交换机只能被直接连接到的交换机使用,而无法通过路由键绑定到队列。该参数为可选参数,用于特定的高级使用场景。
Arguments:参数允许在创建交换机时指定一些额外的自定义参数。这些参数可以根据特定的需求来定义交换机的行为和特性。Arguments参数是一个键值对的字典,其中键和值的类型可以是字符串、数字、布尔值等。不同的交换机类型支持不同的参数。
一些常见的Arguments参数包括:
Alternate Exchange(备用交换机):指定一个备用交换机,当消息无法被路由到任何绑定的队列时,将消息转发到备用交换机。这可以提供消息的备份或延迟处理等功能。
Message TTL(消息过期时间):指定消息的生存时间(Time To Live),即消息在交换机中存储的有效期限。超过该时间的消息将被丢弃或进入死信队列。
Dead-Letter Exchange(死信交换机):指定一个死信交换机,用于处理无法被消费者成功处理的消息。当消息被拒绝、超过最大重试次数或过期时,将消息转发到死信交换机。
Queue Mode(队列模式):用于定义队列的模式,例如懒惰队列(lazy queues)模式,可以在需要时才创建队列,以减少资源消耗。
x-match:用于Topic交换机,指定如何匹配路由键和绑定键的模式,可以是任意(any)或全部(all)。
创建队列
点击对应的队列可以进去队列管理页面
发送消息
点击Publish message,在Payload输入消息,点击Publish message发送消息。
发送后在上面预览就可以看到队列中的消息统计信息
消费消息
如果应答了,消息就不会在列队里了。
六、RabbitMQ消息模式
RabbitMQ 常见的消息模式主要有以下几种:
1. 简单模式(Simple Mode):一个生产者发送消息到一个队列,一个消费者从该队列获取消息。
2. 工作队列模式(Work Queue Mode):多个消费者共同处理一个队列中的消息,实现任务的负载均衡。
3. 发布 / 订阅模式(Publish/Subscribe Mode):一个消息被发送到多个队列,多个消费者可以获取到相同的消息。
4. 路由模式(Routing Mode):根据消息的路由键将消息路由到不同的队列。
5. 主题模式(Topic Mode):通过通配符匹配路由键,实现更灵活的消息路由。
新建项目,演示
1、新建一个springboot项目。
2、引用对应的mq依赖。
<!--RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
3、添加对应的配置。
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
virtual-host: /
4、启动类上添加 @EnableRabbit
5、Spring Boot 中使用 RabbitMQ 时,常用的注解 @RabbitListener
1、简单模式(Simple Mode)
一个生产者发送消息到一个队列,一个消费者从该队列获取消息。
生产者:
@Test
void sendMessage() {
// 发送简单消息
String message = "Hello World";
rabbitTemplate.convertAndSend("simpleQueue", message);
System.out.println("发送简单消息,成功--》");
}
消息者:
/**
* 简单模式(Simple Mode)
*/
@RabbitListener(queues = "simpleQueue")
public void receiveMessage(String message) {
System.out.println("Simple 收到消息: " + message);
}
2、工作队列模式(Work Queue Mode)
多个消费者共同处理一个队列中的消息,实现任务的负载均衡。
生产者:
@Test
void sendMessage2() {
// 工作队列模式
String message = "Work Queue Message";
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend("workQueue", message + " " + i);
}
System.out.println("发送工作队列消息,成功--》");
}
消息者:
/**
* 工作队列模式(Work Queue Mode)
*/
@RabbitListener(queues = "workQueue")
public void receiveMessage1(String message) {
System.out.println("Work1 收到消息: " + message);
}
@RabbitListener(queues = "workQueue")
public void receiveMessage2(String message) {
System.out.println("Work2 收到消息: " + message);
}
3、发布 / 订阅模式(Publish/Subscribe Mode)
一个消息被发送到多个队列,多个消费者可以获取到相同的消息,不用指定 routingKey 。
生产者:
/**
* 交换机
* @return
*/
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("fanoutExchange");
}
/**
* 交换机绑定队列。
* @param fanoutExchange
* @return
*/
@Bean
public Binding binding1(FanoutExchange fanoutExchange) {
return BindingBuilder.bind(new Queue("queue1")).to(fanoutExchange);
}
@Bean
public Binding binding2(FanoutExchange fanoutExchange) {
return BindingBuilder.bind(new Queue("queue2")).to(fanoutExchange);
}
@Test
void sendMessage3() {
// 发送发布/订阅模式
String message = "Publish/Subscribe Queue Message!";
rabbitTemplate.convertAndSend("fanoutExchange", "", message);
System.out.println("发送发布/订阅模式 消息,成功--》");
}
消息者:
/**
* 发布/订阅模式(Publish/Subscribe Mode)
*/
@RabbitListener(queues = "queue1")
public void receiveMessage3(String message) {
System.out.println("发布/订阅模式 queue1 收到消息: " + message);
}
@RabbitListener(queues = "queue2")
public void receiveMessage4(String message) {
System.out.println("发布/订阅模式 queue2 收到消息: " + message);
}
4、路由模式(Routing Mode)
生产者:
/**
* 路由模式
*/
@Bean
public DirectExchange directExchange() {
return new DirectExchange("directExchange");
}
@Bean
public Binding binding3(DirectExchange directExchange) {
return BindingBuilder.bind(new Queue("queue3")).to(directExchange).with("routingKey1");
}
@Bean
public Binding binding4(DirectExchange directExchange) {
return BindingBuilder.bind(new Queue("queue4")).to(directExchange).with("routingKey2");
}
@Test
void sendMessage4() {
// 路由模式
String message = "Routing Message routingKey1 ";
rabbitTemplate.convertAndSend("directExchange", "routingKey1", message);
System.out.println("发送 路由模式 消息,成功--》");
}
消息者:
/**
* 路由模式(Routing Mode)
*/
@RabbitListener(queues = "queue3")
public void receiveMessage5(String message) {
System.out.println("路由模式 routingKey1 收到消息: " + message);
}
@RabbitListener(queues = "queue4")
public void receiveMessage6(String message) {
System.out.println("路由模式 routingKey2 收到消息: " + message);
}
5、主题模式(Topic Mode)
通过通配符匹配路由键,实现更灵活的消息路由。
生产者:
/**
* 主题模式
*/
@Bean
public TopicExchange topicExchange() {
return new TopicExchange("topicExchange");
}
@Bean
public Binding binding5(TopicExchange topicExchange) {
return BindingBuilder.bind(new Queue("queue5")).to(topicExchange).with("*.orange.*");
}
@Bean
public Binding binding6(TopicExchange topicExchange) {
return BindingBuilder.bind(new Queue("queue6")).to(topicExchange).with("*.*.rabbit");
}
@Test
void sendMessage5() {
// 主题模式
String message = "Topic Message quick.orange.rabbit ";
rabbitTemplate.convertAndSend("topicExchange", "quick.orange.rabbit", message);
System.out.println("发送 主题模式 消息,成功--》");
}
消息者:
/**
* 主题模式(Topic Mode)
*/
@RabbitListener(queues = "queue5")
public void receiveMessage7(String message) {
System.out.println("主题模式 queue5 收到消息: " + message);
}
@RabbitListener(queues = "queue6")
public void receiveMessage8(String message) {
System.out.println("主题模式 queue6 收到消息: " + message);
}
// 通过注释去命名
@RabbitListener(bindings =@QueueBinding(
// autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除
value = @Queue(value = "queue7",autoDelete = "false"),
// @Exchange(value 交换机的名字 必须和生产者保持一致
exchange = @Exchange(value = "topicExchange",type = ExchangeTypes.TOPIC),
// 路由key
key = "*.*.rabbit"
))
public void receiveMessage9(String message) {
System.out.println("主题模式 queue7 收到消息: " + message);
}
七、RabbitMQ消息确认机制
保证消息不丢失,可靠抵达。
可能会因为服务器抖动、宕机,MQ 的宕机、资源耗尽,导致消息的丢失。导致不一致情况。
模拟场景:
当用户成功下单后,需要通知仓库进行配货,并在配货完成后将货物出仓。我们需要确保每个消息都能被正确处理,如果处理失败则需要重试。
添加配置:
spring.rabbitmq.listener.simple.acknowledge-mode=manual
生产者:
/**
* 消息确认
*/
@Test
void sendMessage6() {
// 设置订单创建时间
Order order = new Order();
order.setOrderId("1");
order.setCreateTime(new Date());
order.setStatus("created");
// 将PickingRequest对象转换为JSON字符串
String request = JSONObject.toJSONString(order);
// 将JSON字符串转换为字节数组
byte[] pickingRequestJsonBytes = request.getBytes(StandardCharsets.UTF_8);
// 创建消息属性对象
MessageProperties messageProperties = new MessageProperties();
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT); // 设置消息持久化
messageProperties.setCorrelationId(String.valueOf(order.getOrderId())); // 设置相关ID
// 创建消息对象
Message message = new Message(pickingRequestJsonBytes, messageProperties);
// 发送消息到交换机
rabbitTemplate.send(RabbitMQConfig.ORDER_EXCHANGE_NAME, RabbitMQConfig.WAREHOUSE_QUEUE_NAME, message);
System.out.println("发送确认消息===: " + message.getMessageProperties().getCorrelationId());
}
消息者:
@Component
public class WarehouseConsumer implements ChannelAwareMessageListener {
@RabbitListener(queues = RabbitMQConfig.WAREHOUSE_QUEUE_NAME)
@Override
public void onMessage(Message message, Channel channel) throws Exception {
try {
// 将字节数组转换回JSON字符串
String json = new String(message.getBody(), StandardCharsets.UTF_8);
// 将JSON字符串转换为PickingRequest对象
Order request = JSONObject.parseObject(json, Order.class);
System.out.println("收入 确认 消息 id:===》 " + message.getMessageProperties().getCorrelationId());
// 进行配货逻辑...
// 假设配货已完成
request.setStatus("completed");
// 手动确认消息处理完成
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
System.out.println("消息确认完成 ,订单 Id: 《===" + request.getOrderId());
} catch (Exception e) {
System.out.println("处理配货请求时发生错误"+ e.getMessage());
// 拒绝消息处理
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
}
}
}
八、RabbitMQ延时队列
延时队列是消息进入队列后不会立即被消费者获取,而是需要经过一定的延迟时间后才可供消费。
延时队列,它是基于消息的 TTL(存活时间)以及死信 Exchange (路由)结合的。
模拟业务:模拟订单创建成功后 5 分钟未支付就关闭订单。
生产者:
public static final String ORDER_QUEUE_NAME = "orderQueue";
public static final String DLQ_QUEUE_NAME = "dlqQueue";
public static final String DELAY_EXCHANGE_NAME = "delayExchange";
public static final String DLQ_EXCHANGE_NAME = "dlqExchange";
@Bean
public Queue createDelayQueue() {
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl", 300000); // 设置队列的TTL为5分钟
args.put("x-dead-letter-exchange", DLQ_EXCHANGE_NAME); // 指定死信交换机
args.put("x-dead-letter-routing-key", DLQ_QUEUE_NAME); // 指定死信队列的路由键
return QueueBuilder.durable(ORDER_QUEUE_NAME)
.withArguments(args)
.build();
}
@Bean
public Queue dlqQueue() {
return QueueBuilder.durable(DLQ_QUEUE_NAME).build();
}
@Bean
FanoutExchange delayExchange() {
return new FanoutExchange(DELAY_EXCHANGE_NAME);
}
@Bean
FanoutExchange dlqExchange() {
return new FanoutExchange(DLQ_EXCHANGE_NAME);
}
@Bean
public Binding delayQueueBinding(Queue createDelayQueue, FanoutExchange delayExchange) {
return BindingBuilder.bind(createDelayQueue).to(delayExchange);
}
@Bean
public Binding dlqQueueBinding(Queue dlqQueue, FanoutExchange dlqExchange) {
return BindingBuilder.bind(dlqQueue).to(dlqExchange);
}
@Test
void sendMessag7() {
Order order = new Order();
// 设置订单创建时间
order.setOrderId("1");
order.setCreateTime(new Date());
order.setStatus("created");
// 将订单对象转换为JSON字符串
String orderJson = JSONObject.toJSONString(order);
// 将JSON字符串转换为字节数组
byte[] orderJsonBytes = orderJson.getBytes(StandardCharsets.UTF_8);
// 创建消息属性对象
MessageProperties messageProperties = new MessageProperties();
// 设置消息的TTL,这里设置为5分钟(300000毫秒)
messageProperties.setExpiration("300000");
// 创建消息对象
Message message = new Message(orderJsonBytes, messageProperties);
// 发送消息到交换机
rabbitTemplate.send(DELAY_EXCHANGE_NAME, "", message);
System.out.println("Sending message with TTL: " + messageProperties.getExpiration());
System.out.println(DateUtils.format(new Date())+": 发送订单消息==》: ");
}
消息者:
@RabbitListener(queues = DLQ_QUEUE_NAME) // 监听死信队列
public void processOrder(byte[] body) {
try {
// 将字节数组转换回JSON字符串
String json = new String(body, StandardCharsets.UTF_8);
// 将JSON字符串转换为Order对象
Order order = JSONObject.parseObject(json, Order.class);
System.out.println(DateUtils.format(new Date())+"===> 收到订单到延时队列==》");
if ("created".equals(order.getStatus())) {
// 订单未支付,关闭订单
order.setStatus("closed");
System.out.println("订单: " + order.getOrderId() + " 。未支付,已关闭");
}
} catch (Exception e) {
e.printStackTrace();
}
}
九、死信队列
DLX,全称为Dead-Letter-Exchange , 可以称之为死信交换机,也有人称之为死信邮箱。当消息在一个队列中变成死信(dead message)之后,它能被重新发送到另一个交换机中,这个交换机就是DLX ,绑定DLX的队列就称之为死信队列。
消息变成死信,可能是由于以下的原因:
- 消息被拒绝
- 消息过期
- 队列达到最大长度
DLX也是一个正常的交换机,和一般的交换机没有区别,它能在任何的队列上被指定,实际上就是设置某一个队列的属性。当这个队列中存在死信时,Rabbitmq就会自动地将这个消息重新发布到设置的DLX上去,进而被路由到另一个队列,即死信队列。
要想使用死信队列,只需要在定义队列的时候设置队列参数 x-dead-letter-exchange 指定交换机即可。
本文作者:黎华扬
本文链接:https://www.cnblogs.com/galenblog/p/18460181
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?