RabbitMQ
一、MQ 概念
1. MQ
MQ(Message Queue)消息队列,是在消息的传输过程中保存消息的容器。多用于系统之间的异步通信。
同步通信相当于两个人当面对话,你一言我一语。必须及时回复。
异步通信相当于通过第三方转述对话,可能有消息的延迟,但不需要二人时刻保持联系。
2. 优势
消息队列允许不同组件或系统之间通过发送消息进行通信,而不需要直接耦合在一起。这使得系统的各个部分能够独立地进行工作,提高了系统的可伸缩性和灵活性。
消息队列的基本原理是生产者(Producer)产生消息并将其发送到队列,然后消费者(Consumer)从队列中获取消息并进行处理。这种异步通信的方式有多个优点:
-
解耦: 生产者和消费者之间通过消息队列进行通信,它们不需要直接了解彼此的存在。这种解耦性提高了系统的灵活性和可维护性。
-
异步处理: 生产者将消息发送到队列后,可以立即继续执行其他操作,而不必等待消费者的响应。消费者则在就绪时异步地从队列中获取消息并进行处理。
-
削峰填谷: 消息队列可以平滑处理系统中的峰值流量。即使生产者和消费者之间的处理速度不一致,消息队列能够调节消息的流量,防止系统因瞬时流量过大而崩溃。
-
可靠性: 消息队列通常具有持久性特性,确保消息在传递过程中不会丢失。这有助于确保系统的可靠性和数据完整性。
-
顺序性: 消息队列可以保证消息按照一定的顺序被消费,这对于某些应用场景(如订单处理)非常重要。
3. 劣势
-
复杂性: 引入消息队列会增加系统的复杂性。配置、管理和监控消息队列可能需要额外的工作,特别是在大规模系统中。
-
一致性问题: 在某些情况下,由于网络故障、节点故障或其他原因,消息队列可能导致消息重复、丢失或无序。确保消息队列系统具有高可用性和一致性是一个挑战。
-
延迟: 使用消息队列引入了一定的传输和处理延迟。在某些对实时性要求非常高的应用中,这种延迟可能是不可接受的。
-
维护和运营成本: 部署、维护和运营消息队列系统可能需要专业知识,增加了系统的总体成本。此外,确保消息队列的高可用性和性能可能需要更多的资源和投资。
-
消息序列化和反序列化开销: 将消息序列化和反序列化为可传输的格式可能会引入一些开销,尤其是对于大量的消息和复杂的消息结构。
-
消息顺序保障: 一些消息队列系统可能在维护消息的顺序性方面面临挑战。虽然某些系统可以提供有序性保障,但这可能在性能方面有一些牺牲。
-
依赖性: 使用消息队列使得系统组件之间存在依赖关系。如果消息队列出现问题,可能影响整个系统的可用性。
-
规模性: 随着系统规模的增加,一些消息队列系统可能遇到性能瓶颈,需要额外的调优和优化。
4. MQ应用场景
1. AMQP 协议
即Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,专门为消息中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受不同中间件产品,不同开发语言等条件的限制。2006年AMQP规范发布,类比HTTP。
- 协议定义: AMQP是一种二进制协议,定义了一组规范和协议层,用于在消息代理(Message Broker)和客户端之间进行通信。它提供了一种统一的方式来描述、传递、存储和路由消息。
消息模型: AMQP基于消息模型,其中消息由生产者(Producer)创建并发送到消息代理,然后由消费者(Consumer)从消息代理中获取和处理。消息可以包含结构化数据,例如JSON或XML。
- 交换机(Exchanges): 在AMQP中,交换机负责接收生产者发送的消息,并将它们路由到一个或多个队列。AMQP定义了不同类型的交换机,例如直连交换机、主题交换机和扇出交换机,以支持不同的消息路由模式。
- 队列: 队列是消息的存储位置,它们由消息代理维护。消费者可以订阅队列以接收其中的消息。AMQP中的队列具有一些特性,如持久性、排他性和自动删除。
- 绑定(Bindings): 绑定定义了将交换机和队列连接起来的规则。它决定了消息从交换机流向哪个队列,以及在什么条件下发生。
- 通道(Channels): 在AMQP中,连接由一个或多个通道组成。通道是在连接内的独立通信路径,使得多个通信流可以并行运行。通道提供了轻量级的、低开销的通信机制。
安全性: AMQP支持身份验证和授权,确保只有经过身份验证的实体才能访问和发送消息。它还提供了加密通信的选项,以确保消息的机密性。
消息可靠性: AMQP通过确认机制和事务来提供消息的可靠性。生产者可以请求消息代理确认消息的接收,而消费者可以确认已成功处理的消息。
RabbitMQ 工作原理
-
消息的生产者。也是一个向交换机发布消息的客户端应用程序。生成消息并将其发送到 RabbitMQ 代理。消息可以包含任何类型的信息,例如任务、事件等。
-
Connection
连接。生产者/消费者和RabbitMQ服务器之间建立的TCP连接。
-
Channel
信道。是TCP里面的虚拟连接。例如:Connection相当于电缆, Channel相当于独立光纤束,一条TCP连接中可以创建多条信道,增加连接效率。无论是发布消息、接收消息、订阅队列都是 通过信道完成的。
-
Broker
消息队列服务器实体。即RabbitMQ服务器
-
Virtual host
虚拟主机。出于多租户和安全因素设计的,把AMQP的基本组件划分到一个虚拟的分组中。每个vhost本质上就是一个mini版的RabbitMQ服务器,拥有自己的队列、交换机、绑定和权限机制。当多个不同的用户使用同一个RabbitMQ服务器时,可以划分出多个虚拟主机。RabbitMQ默认的虚拟主机路径是 /
-
Exchange
交换机。是消息的中转站。生产者发送的消息首先被发送到交换机。交换机根据预定义的规则(绑定)将消息路由到一个或多个队列中。
-
Queue
消息队列。用来保存消息直到发送给消费者。它是消息的容器, 也是消息的终点。消息一直在队列里面,等待消费者链接到这个队列将其取走。
-
Binding
消息队列和交换机之间的虚拟连接,绑定中包含路由规则,绑定信息保存到交换机的路由表中,作为消息的分发依据。
-
Consumer
- 消息确认(可选)
消费者处理消息后,可以选择向 RabbitMQ 发送确认消息。这是可选的步骤,但通常用于确保消息被成功处理,避免消息在处理期间丢失。
- 消息持久性(可选)
在生产者发送消息时,可以选择使消息持久化。这意味着即使 RabbitMQ 代理重启,消息也不会丢失。这对于关键性的任务和数据是很重要的。
三、RabbitMQ 工作模式
-
简单队列模式(Simple Queue):
- 单一的生产者将消息发送到一个队列,而单一的消费者从队列中接收并处理消息。
- 这是最基本的模式,适用于简单的应用场景,但对于处理大量消息或需要水平扩展的系统可能不够。
-
工作队列模式(Work Queues):
- 多个生产者将消息发送到一个共享的队列,多个消费者同时监听该队列。
- 每条消息只能被一个消费者处理,确保消息的顺序性和不重复处理。适用于处理较大的工作负载。
-
发布/订阅模式(Publish/Subscribe):
- 生产者将消息发送到交换机(fanout交换机),交换机将消息广播到与之绑定的多个队列。
- 多个消费者分别监听不同的队列,实现消息的发布和订阅。适用于广播消息给多个消费者的场景。
-
路由模式(Routing):
- 生产者将消息发送到交换机,并指定一个路由键(Routing Key)。
- 消费者通过绑定到交换机的队列指定路由键,只有指定路由键的消息会被相应队列接收。适用于有选择性地接收消息的场景。
-
主题模式(Topics):
- 类似于路由模式,但路由键可以使用通配符,以实现更灵活的消息路由。
- 消费者通过使用通配符的路由键来订阅消息,可以根据模式匹配接收感兴趣的消息。适用于更复杂的消息过滤和分发。
-
通配符规则:
-
消息设置RoutingKey时,RoutingKey由多个单词构成,中间以 . 分割。
-
队列设置RoutingKey时, # 可以匹配任意多个单词, * 可以匹配任意一个单词。
-
代码实现
在SpringBoot工程环境下引入maven依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
1. 简单队列模式
生产者:

public class Producer { @SneakyThrows public static void main(String[] args) { //1. 创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.77.129"); connectionFactory.setPort(5672); connectionFactory.setUsername("young"); connectionFactory.setPassword("young"); connectionFactory.setVirtualHost("/young"); //2. 创建连接 Connection connection = connectionFactory.newConnection(); //3. 创建信道 Channel channel = connection.createChannel(); //4. 创建队列 /** * 参数1:队列名 * 参数2:是否持久化,true表示MQ重启后队列还在 * 参数3:是否私有化,false表示所有消费者都可以访问,true表示只有第一次拥有它的消费者才能访问 * 参数4:是否自动删除,true表示不在使用队列时自动删除队列 * 参数5:其他额外参数 */ channel.queueDeclare("simple_queue",false,false,false,null); //5. 发送消息 /** * 参数1:交换机名 ""表示默认交换机 * 参数2:路由键,简单模式就是队列名 * 参数3:其他额外参数 * 参数4:要传递的消息字节数组 */ String message = "hello simple message"; channel.basicPublish("","simple_queue",null,message.getBytes()); //6. 关闭信道和连接 channel.close(); connection.close(); System.out.println("发送成功"); } }
消费者:

public class Consumer { @SneakyThrows public static void main(String[] args) { //1. 创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.77.129"); connectionFactory.setPort(5672); connectionFactory.setUsername("young"); connectionFactory.setPassword("young"); connectionFactory.setVirtualHost("/young"); //2. 创建连接 Connection connection = connectionFactory.newConnection(); //3. 创建信道 Channel channel = connection.createChannel(); //4. 监听队列 /** * 参数1:监听的队列名 * 参数2:是否自动签收,如果设置为false,则需要手动确认消息已接收,否则MQ会一直发送消息 * 参数3:Consumer的实现类,重写该类方法表示接受到消息后如何消费 */ channel.basicConsume("simple_queue",true,new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String msg = new String(body, "utf-8"); System.out.println("接收的消息为:"+msg); } }); } }
2. 工作队列模式
生产者:

public class Producer { @SneakyThrows public static void main(String[] args) { //1. 创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.77.129"); connectionFactory.setPort(5672); connectionFactory.setUsername("young"); connectionFactory.setPassword("young"); connectionFactory.setVirtualHost("/young"); //2. 创建连接 Connection connection = connectionFactory.newConnection(); //3. 创建信道 Channel channel = connection.createChannel(); //4. 创建队列 /** * 参数1:队列名 * 参数2:是否持久化,true表示MQ重启后队列还在 * 参数3:是否私有化,false表示所有消费者都可以访问,true表示只有第一次拥有它的消费者才能访问 * 参数4:是否自动删除,true表示不在使用队列时自动删除队列 * 参数5:其他额外参数 */ channel.queueDeclare("work_queue",true,false,false,null); //5. 发送消息 /** * 参数1:交换机名 ""表示默认交换机 * 参数2:路由键,简单模式就是队列名 * 参数3:其他额外参数 ,这里表示消息为持久化消息,既除了保存内存还会保存到磁盘中 * 参数4:要传递的消息字节数组 */ for (int i = 0; i < 10; i++) { channel.basicPublish("","work_queue", MessageProperties.PERSISTENT_TEXT_PLAIN,("第"+i+"条消息").getBytes()); } channel.close(); connection.close(); System.out.println("发送成功"); } }
消费者1:

public class Consumer1 { @SneakyThrows public static void main(String[] args) { //1. 创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.77.129"); connectionFactory.setPort(5672); connectionFactory.setUsername("young"); connectionFactory.setPassword("young"); connectionFactory.setVirtualHost("/young"); //2. 创建连接 Connection connection = connectionFactory.newConnection(); //3. 创建信道 Channel channel = connection.createChannel(); //4. 监听队列 /** * 参数1:监听的队列名 * 参数2:是否自动签收,如果设置为false,则需要手动确认消息已接收,否则MQ会一直发送消息 * 参数3:Consumer的实现类,重写该类方法表示接受到消息后如何消费 */ channel.basicConsume("work_queue",true,new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("消费者1接收到消息:" + new String(body, StandardCharsets.UTF_8)); } }); } }
消费者2:

public class Consumer2 { @SneakyThrows public static void main(String[] args) { //1. 创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.77.129"); connectionFactory.setPort(5672); connectionFactory.setUsername("young"); connectionFactory.setPassword("young"); connectionFactory.setVirtualHost("/young"); //2. 创建连接 Connection connection = connectionFactory.newConnection(); //3. 创建信道 Channel channel = connection.createChannel(); //4. 监听队列 /** * 参数1:监听的队列名 * 参数2:是否自动签收,如果设置为false,则需要手动确认消息已接收,否则MQ会一直发送消息 * 参数3:Consumer的实现类,重写该类方法表示接受到消息后如何消费 */ channel.basicConsume("work_queue",true,new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("消费者2接收到消息:" + new String(body, StandardCharsets.UTF_8)); } }); } }
消费者3:

public class Consumer3 { @SneakyThrows public static void main(String[] args) { //1. 创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.77.129"); connectionFactory.setPort(5672); connectionFactory.setUsername("young"); connectionFactory.setPassword("young"); connectionFactory.setVirtualHost("/young"); //2. 创建连接 Connection connection = connectionFactory.newConnection(); //3. 创建信道 Channel channel = connection.createChannel(); //4. 监听队列 /** * 参数1:监听的队列名 * 参数2:是否自动签收,如果设置为false,则需要手动确认消息已接收,否则MQ会一直发送消息 * 参数3:Consumer的实现类,重写该类方法表示接受到消息后如何消费 */ channel.basicConsume("work_queue",true,new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println("消费者3接收到消息:" + new String(body, StandardCharsets.UTF_8)); } }); } }
3. 发布订阅模式
生产者:

public class Producer { @SneakyThrows public static void main(String[] args) { //1. 创建连接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.77.129"); connectionFactory.setPort(5672); connectionFactory.setUsername("young"); connectionFactory.setPassword("young"); connectionFactory.setVirtualHost("/young"); //2. 创建连接 Connection connection = connectionFactory.newConnection(); //3. 创建信道 Channel channel = connection.createChannel(); //4. 创建交换机 /** * 参数1:交换机名 * 参数2:交换机类型 * 参数3:交换机持久化 */ channel.exchangeDeclare("exchange_fanout", BuiltinExchangeType.FANOUT,true); //5. 创建队列,如果队列存在,则直接使用 /** * 参数1:队列名 * 参数2:是否持久化,true表示MQ重启后队列还在 * 参数3:是否私有化,false表示所有消费者都可以访问,true表示只有第一次拥有它的消费者才能访问 * 参数4:是否自动删除,true表示不在使用队列时自动删除队列 * 参数5:其他额外参数 */ channel.queueDeclare("send_email",true,false,false,null); channel.queueDeclare("send_message",true,false,false,null); channel.queueDeclare("send_station",true,false,false,null); //6. 交换机绑定队列 /** * 参数1:队列名 * 参数2:交换机名 * 参数3:路由关键字 ,发布订阅模式写""即可 */ channel.queueBind("send_email","exchange_fanout",""); channel.queueBind("send_message","exchange_fanout",""); channel.queueBind("send_station","exchange_fanout",""); //7.发送消息 /** * 参数1:交换机名 ""表示默认交换机 * 参数2:路由键,简单模式就是队列名 * 参数3:其他额外参数 ,这里表示消息为持久化消息,既除了保存内存还会保存到磁盘中 * 参数4:要传递的消息字节数组 */ for (int i = 1; i <=10 ; i++) { channel.basicPublish("exchange_fanout", "", null, ("你好,尊敬的用户,秒杀商品开抢了"+i).getBytes()); } //8.关闭信道和连接 channel.close(); connection.close(); System.out.println("发送成功"); } }
邮件消费者:

public class SendMail { @SneakyThrows public static void main(String[] args) { //1.创建链接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.77.129"); connectionFactory.setPort(5672); connectionFactory.setUsername("young"); connectionFactory.setPassword("young"); connectionFactory.setVirtualHost("/young"); //2.创建链接 Connection connection = connectionFactory.newConnection(); //3.创建信道 Channel channel = connection.createChannel(); //4.监听队列 /** * 参数1:监听的队列名 * 参数2:是否自动签收,如果设置为false,则需要手动确认消息已接收,否则MQ会一直发送消息 * 参数3:Consumer的实现类,重写该类方法表示接受到消息后如何消费 */ channel.basicConsume("send_email", true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String message = new String(body,"utf-8"); System.out.println("接收邮件,消息为:"+message); } }); } }
短信消费者:

public class SendMessage { @SneakyThrows public static void main(String[] args) { //1.创建链接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.77.129"); connectionFactory.setPort(5672); connectionFactory.setUsername("young"); connectionFactory.setPassword("young"); connectionFactory.setVirtualHost("/young"); //2.创建链接 Connection connection = connectionFactory.newConnection(); //3.创建信道 Channel channel = connection.createChannel(); //4.监听队列 /** * 参数1:监听的队列名 * 参数2:是否自动签收,如果设置为false,则需要手动确认消息已接收,否则MQ会一直发送消息 * 参数3:Consumer的实现类,重写该类方法表示接受到消息后如何消费 */ channel.basicConsume("send_message", true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String message = new String(body,"utf-8"); System.out.println("接收短信,消息为:"+message); } }); } }
公共消费者1、2:

public class Consumer1 { @SneakyThrows public static void main(String[] args) { //1.创建链接工厂 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.77.129"); connectionFactory.setPort(5672); connectionFactory.setUsername("young"); connectionFactory.setPassword("young"); connectionFactory.setVirtualHost("/young"); //2.创建链接 Connection connection = connectionFactory.newConnection(); //3.创建信道 Channel channel = connection.createChannel(); //4.监听队列 /** * 参数1:监听的队列名 * 参数2:是否自动签收,如果设置为false,则需要手动确认消息已接收,否则MQ会一直发送消息 * 参数3:Consumer的实现类,重写该类方法表示接受到消息后如何消费 */ channel.basicConsume("send_station", true, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String message = new String(body,"utf-8"); System.out.println("公共接收,消息为:"+message); } }); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)