本文参考:RabbitMQ官方文档
1. 简介
RabbitMQ是一个开源的消息代理和队列服务器,它提供了一个可靠的消息传递服务,适用于多种场景,包括但不限于异步处理、应用解耦、流量控制和提高消息系统的可靠性。RabbitMQ遵循AMQP(Advanced Message Queuing Protocol)协议,同时也支持其他消息协议,如STOMP、MQTT等。
关键特性以及概念:
-
消息(Message):
消息是传递数据的基本单位,包含有效载荷(payload)和一些属性(如路由键、优先级等)。
-
队列(Queue):
队列是消息的容器,它们存储等待处理的消息。队列是消息的缓冲区,可以存储消息,直到有消费者准备好处理它们。
-
交换器(Exchange):
交换器是RabbitMQ中的核心组件,负责接收来自生产者的消息,并将它们路由到一个或多个队列。交换器有不同类型,包括direct、topic、fanout和headers等,每种类型根据特定的规则来路由消息。
-
绑定(Binding):
绑定是交换器、队列和路由键之间的关系定义。通过绑定,你可以定义消息如何从交换器路由到队列。
-
路由键(Routing Key):
路由键是一个字符串,用于确定消息如何通过交换器路由到队列。它的使用方式取决于交换器的类型。
-
虚拟主机(Virtual Host):
虚拟主机是RabbitMQ服务器的逻辑分区,它们提供了隔离的环境,允许不同的应用程序或不同的部分应用程序使用不同的交换器、队列和绑定,而不会相互干扰。
-
持久化(Persistence):
RabbitMQ支持消息和队列的持久化,这意味着即使在服务器重启之后,消息也不会丢失。
-
集群(Clustering):
RabbitMQ支持集群部署,可以横向扩展以提供更高的吞吐量和可用性。
-
高可用性(High Availability):
通过镜像队列和故障转移机制,RabbitMQ可以在部分节点失败的情况下继续处理消息。
-
消息确认(Message Acknowledgments):
消费者可以发送确认信号,告知消息已经成功处理。这有助于确保消息不会被意外丢失。
-
死信队列(Dead Letter Exchanges):
当消息无法被正常处理时,可以被发送到死信队列,以便后续分析和处理。
-
延迟消息(Delayed Messages):
RabbitMQ支持延迟消息,允许消息在一段时间后再被消费。
2. 基本消息传输流程
交换器、队列和路由键之间的工作流程:
-
生产者(Producer) 发送消息到 交换器(Exchange) ,并指定一个 路由键(Routing Key)。
-
交换器(Exchange) 根据其类型和路由键来决定如何处理消息:
-
Direct Exchange(直连交换器):这种类型的交换器使用路由键(routing key)来决定消息应该发送到哪个队列。如果消息的路由键与队列的绑定键完全匹配,消息就会被发送到该队列。默认情况下,每个队列都会绑定到一个默认的直连交换器上,其路由键就是队列的名字。这种模式适合一对一的消息传递。
-
Fanout Exchange(广播交换器):在这种模式下,交换器会将接收到的所有消息发送到所有绑定的队列,而不考虑消息的路由键。这种类型的交换器适用于发布/订阅模型,其中消息需要被多个消费者接收。
-
Topic Exchange(主题交换器):这种类型的交换器允许使用通配符进行消息路由。消息的路由键会被分割成单词,队列在绑定到交换器时可以指定一个模式,这个模式可以包含一个或多个单词。交换器会将消息发送到所有其绑定键与消息路由键匹配的队列。这种模式适合需要基于主题或模式进行消息分发的场景。
-
Headers Exchange(头交换器):这种类型的交换器不依赖于路由键来路由消息,而是根据消息内容中的 headers 属性进行匹配。在绑定队列与交换器时,可以指定一组键值对;当消息发送到交换器时,RabbitMQ 会取出该消息的 headers 并对比其中的键值对是否完全匹配队列与交换器绑定时指定的键值对。如果完全匹配,则消息会被路由到该队列。由于性能问题,这种类型的交换器在实际中使用较少。
-
Default Exchange(默认交换器):每个 RabbitMQ 虚拟主机都会预声明一个默认的交换器,其名称为空字符串。如果生产者在发布消息时没有指定
-
-
交换器(Exchange) 根据上述规则将消息路由到一个或多个 队列(Queue)。
如果没有绑定关系,那么:
-
Direct Exchange 和 Topic Exchange 会丢弃消息,因为没有绑定的队列可以接收它。
-
Fanout Exchange 会将消息发送到所有绑定的队列,不论路由键是什么,因为扇形交换器不使用路由键进行路由。
-
Headers Exchange 也会丢弃消息,因为没有绑定的队列可以匹配消息的header属性。
3. 消息类型
3.1 普通消息
普通消息是最常见的消息类型,生产者(Publisher)将消息发送到交换器(Exchange),然后交换器根据路由键(Routing Key)和交换器类型将消息路由到一个或多个队列(Queue)。
实现方式:
-
生产者连接到RabbitMQ服务器并创建一个频道(Channel)。
-
生产者声明一个交换器,并设置所需的属性(如持久性、自动删除等)。
-
生产者将消息发布到交换器上,消息会包含路由键。
-
交换器根据路由键和绑定(Binding)将消息路由到一个或多个队列。
-
消费者(Consumer)连接到RabbitMQ服务器并从队列中拉取消息进行处理。
3.2 延迟消息
延迟消息是指消息在被发送到队列后,并不会立即被消费者消费,而是在特定的时间后才能被消费。
实现方式: RabbitMQ 原生并不直接支持消息延迟,但可以通过以下几种方式实现:
- 使用死信队列(Dead Letter Exchanges):将消息首先发送到一个设置有过期时间的队列,当消息在该队列中过期后,它会进入一个死信队列,然后从死信队列中被消费。
-
使用RabbitMQ Delayed Message插件:这个插件允许你在发送消息时设置一个延迟时间,消息会被存储在一个延迟队列中,直到指定的延迟时间过后才会被发送到目标队列。
-
应用层面实现:生产者可以在消息体中包含一个时间戳或延迟时间,然后消费者在消费消息时检查这个时间戳,如果当前时间超过了消息中指定的时间,则处理消息,否则将消息重新入队或保存到外部存储中,直到可以处理的时间到达。
3.3 事务消息
事务消息确保消息的发送和处理是原子性的,即要么消息完全被处理,要么完全不处理,这在某些需要强一致性的场景中非常有用。
实现方式:
-
使用RabbitMQ的事务机制:生产者可以在一个频道上开启事务,然后发送消息。如果消息发送成功,生产者可以提交事务,确保消息被持久化并路由到队列。如果发送失败,生产者可以回滚事务,这样消息就不会被处理。
-
使用发布确认(Publisher Confirms):RabbitMQ支持发布确认机制,生产者在发送消息后可以等待服务器的确认。只有在收到确认后,生产者才认为消息已经成功发送。这提供了一种机制来确保消息不会因为网络问题或服务器问题而丢失。
-
应用层面的补偿逻辑:在应用层面实现事务逻辑,例如,生产者发送消息后,同时在数据库中记录消息状态。如果消息发送失败,可以根据数据库中的状态进行补偿操作。
注:
-
事务消息的实现通常需要考虑消息的持久性、事务的隔离级别以及系统的可用性。在分布式系统中,完全的事务性消息队列可能需要更复杂的设计,如使用两阶段提交协议(2PC)等。
-
单独使用消息中间件来实现延迟消息和事务消息,RocketMQ更容易实现(可能是由于更熟悉)
4. 相关概念图
- 工作队列
工作队列(又称任务队列 )背后的主要思想是避免立即执行资源密集型任务并等待其完成。相反,将任务安排在稍后完成。将任务封装 为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行该作业。当运行许多工作进程时,任务将在它们之间共享。
- 路由
根据不同的routingKey将消息推送到不同的queue。
- 发布订阅
使用相同的绑定键绑定多个队列是完全合法的。在X
和之间添加绑定,Q1
绑定键为vlog。在这种情况下,交换机direct
将表现得像fanout
并将消息广播到所有匹配的队列。具有路由键的消息vlog将同时传递给 Q1
和Q2。
5. 案例
定义交换器、路由键、队列之间的关系
java
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
// 定义队列
@Bean
public Queue myQueue() {
return new Queue("queue", true); // true表示队列持久化
}
// 定义Direct交换器
@Bean
public DirectExchange myExchange() {
return new DirectExchange(" exchange");
}
// 定义队列和交换器之间的绑定
@Bean
public Binding binding(Queue myQueue, DirectExchange myExchange) {
return BindingBuilder.bind(myQueue).to(myExchange).with("routingKey");
}
}
发送消息
java
package com.xiaokai.service;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Author:yang
* Date:2024-10-15 15:02
*/
@Service
public class SendService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendMessage(String message){
rabbitTemplate.convertAndSend("exchange","routingKey",message);
}
}
消费消息
java
package com.xiaokai.listener;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* Author:yang
* Date:2024-10-15 15:11
*/
@Component
public class MessageListener {
@RabbitListener(queues = "queue")
public void processMessage(String message) {
System.out.println("Received message: " + message);
}
}