RabbitMQ
一、什么是消息队列
- 消息队列(Message Queue):是一种用于在应用程序之间传递消息的通信方式,消息队列允许应用程序异步地发送和接收消息,并且不需要直接连接到对方。
- 消息(Message):是指在应用间传送的数据。消息可以非常简单,比如只包含文本字符串,也可以更复杂,可能包含嵌入对象。
- 队列(Queue):一个数据结构,先进先出。
二、消息队列的作用和应用场景
1. 应用解耦(系统之间解耦合)
-
作用说明: 通过消息队列,发送者(生产者)只负责发送消息,不关心谁处理、如何处理,由接收者(消费者)负责消费消息,实现模块间的解耦。
-
应用场景举例:
- 用户注册后需要发送欢迎邮件和短信 → 主系统将用户信息写入消息队列,邮件和短信系统分别监听队列处理。
- 电商订单系统下单后需要通知库存系统 → 通过消息传递实现松耦合。
2. 异步提速(提升响应速度)
- 作用说明: 对于一些耗时操作(如写日志、发送邮件、生成报表),不需要立即返回结果的,可以异步处理,显著提升接口响应速度和用户体验。
- 应用场景举例:
- 用户支付成功后,页面立即跳转;后续的「发货单生成」、「积分奖励」等通过消息队列异步完成。
- 新闻系统中用户评论后,后台异步更新热门榜单。
3. 削峰填谷(流量削峰)
- 作用说明: 在高并发场景下,大量请求瞬间涌入会导致数据库或后端系统崩溃。通过消息队列“缓冲”请求,限制消费速度,从而保护后端系统。
- 应用场景举例:
- 秒杀系统:用户抢购请求写入队列,后台按顺序逐个处理,防止数据库被打爆。
- 数据采集:采集服务将数据写入消息队列,分析系统平稳消费,防止高峰期宕机。
三、消息队列的缺点
1. 降低系统可用性
-
描述: 系统引入了消息中间件(如 RabbitMQ、Kafka)作为新的外部依赖,一旦消息队列服务宕机,就会影响整个业务链路,导致系统不可用。
-
举例:
- 下单服务依赖 RabbitMQ 通知库存系统,如果 MQ 崩了,库存同步失败,导致超卖。
- 依赖消息队列异步发送短信,MQ 不可用时用户收不到短信。
-
解决建议:
- 使用集群部署 RabbitMQ,开启镜像队列、持久化机制;
- 在应用层做好降级处理,例如失败时记录日志或重试。
2. 系统复杂度提高
-
描述: 使用 MQ 后,系统间通过消息传递协作,带来一系列新的挑战,例如:
- 如何避免消息重复消费?
- 如何处理消息丢失?
- 如何保证消息顺序性?
- 如何进行消息幂等处理?
-
举例:
- 支付结果通知被多次消费,重复发货;
- 消息积压导致消费者延迟处理;
- 顺序消费失败导致数据错乱。
-
解决建议:
- 为消费者设计幂等处理机制;
- 配置消息确认机制(ACK/NACK);
- 对顺序敏感的业务使用顺序队列或事务消息;
3. 数据一致性问题
-
描述: 引入 MQ 后,业务变成了分布式事务场景,发送方可能已经提交成功,但部分消费方处理失败,导致系统间数据不一致。
-
举例:
- 用户支付成功,支付系统成功返回,但消息通知发给 B、C、D 系统;
- B、D 成功入库;
- C 写库失败,造成系统间状态不一致。
- 用户支付成功,支付系统成功返回,但消息通知发给 B、C、D 系统;
-
解决建议:
- 使用事务消息(如 RocketMQ 支持)确保发送和消费的事务一致性;
- 结合本地消息表 + 定时补偿任务机制;
- 加入消费失败告警/人工处理机制。
四、RabbitMQ
1.四大核心:
- 生产者
- 消费者
- 队列
- 交换机
2.AMQP协议
- AMQP协议是一种二进制协议,它定义了一组规则和标准,以确保消息可以在不同的应用程序和平台之间传递和解释,AMQP协议包含四个核心组件:
- 消息
- 交换机
- 队列
- 绑定
3.RabbitMQ工作原理
(1) RabbitMQ 工作原理概览
[Producer]
|
| 发送消息
v
[Exchange 交换机] ←→ 路由规则
|
| 转发消息
v
[Queue 队列]
|
| 拉取/推送消息
v
[Consumer]
(2) 核心组成部分解释
组件 | 作用 |
---|---|
Producer(生产者) | 发送消息到 RabbitMQ |
Exchange(交换机) | 接收生产者发来的消息,按照规则转发到一个或多个队列 |
Queue(队列) | 真正存储消息的地方,消费者从队列中读取消息 |
Consumer(消费者) | 从队列中消费消息,处理业务逻辑 |
Binding(绑定) | 把交换机和队列连接起来,指定路由规则 |
(3) 工作流程详解(6步走)
-
生产者发送消息
生产者将消息发送给 Exchange,并指定一个 Routing Key(路由键)。 -
交换机接收消息
Exchange 不存储消息,只负责根据路由规则将消息转发给合适的队列。 -
路由匹配规则决定消息流向
通过绑定关系(Binding),Routing Key 与队列匹配,决定消息进入哪个队列。 -
队列缓存消息
队列中存储消息,等待消费者来消费。支持持久化设置(消息掉电不丢失)。 -
消费者连接并消费消息
消费者从队列拉取或等待 RabbitMQ 推送消息,处理业务逻辑。 -
消息确认(ACK)机制
消费者处理完后发送 ACK,RabbitMQ 才会删除消息;失败可以重新投递(NACK)或丢弃。
(4) Exchange 类型(4种)
类型 | 描述 | 示例 |
---|---|---|
Direct | 精准路由匹配 Routing Key | 下单消息 → 订单队列 |
Fanout | 广播消息给所有绑定队列 | 系统通知 → 所有服务 |
Topic | 模糊匹配通配符(如 a.*.b) | 日志级别 info/error |
Headers | 根据消息头属性路由 | 高级路由控制,较少用 |
(5) 生产者代码示例:
package com.rgg.rabbitmqhello;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* @author yexiebao
* @date 2025/4/19
*/
public class Producer {
public static void main(String[] args) throws Exception {
//交换机名称
String exchangeName = "exchange_name";
//队列名称
String queueName = "queue_name";
//1.创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.RabbitMQ服务地址
factory.setHost("192.168.2000.2");
//3.账号
factory.setUsername("admin");
//4.密码
factory.setPassword("123456");
//5.端口号
factory.setPort(5672 );
//6.创建连接
Connection connection = factory.newConnection();
//7.创建信道
Channel channel = connection.createChannel();
/**
* 创建交换机
* 1.交换机名称
* 2.交换机类型:direct,topic,fanout,headers
* 3.指定交换机是否需要持久化,如果设置true,那么交换机的元数据要持久化
* 4.指定交换机在没有队列绑定时,是否需要删除,设置false表示不删除
* 5.Map<String, Object></String,>
*/
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT,true,false,null);
/**
* 生成一个队列
* 1.队列名称
* 2.队列是否需要持久化,但注意只是队列名称这些元数据持久化,不是队列中消息的持久化
* 3.表示队列是不是私有的,如果是私有的,只有创建他的应用程序才能消费消息
* 4.队列在没有消费者订阅的情况下是否自动删除
* 5.队列的一些结构化信息,比如声明死信队、磁盘队列
*/
channel.queueDeclare(queueName,true, false,false,null);
/**
* 将我们的队列和交换机绑定
* 1.队列名称
* 2.交换机名称
* 3.路由键,直连模式下可为队列名称
*/
channel.queueBind(queueName,exchangeName,queueName);
//发送消息
String message = "hello rabbitmq";
/**
* 发送消息
* 1.发送到哪个交换机
* 2.队列名称
* 3.其他参数信息
* 4.发送消息的消息体
*/
channel.basicPublish(exchangeName,queueName,null,message.getBytes());
//关闭通道
channel.close();
//关闭连接
connection.close();
}
}
(6) 消费者代码示例:
package com.rgg.rabbitmqhello;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author yexiebao
* @date 2025/4/21
*/
public class Consumer {
public static void main(String[] args) throws Exception {
//交换机名称
String exchangeName = "exchange_name";
//队列名称
String queueName = "queue_name";
//1.创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.RabbitMQ服务地址
factory.setHost("192.168.2000.2");
//3.账号
factory.setUsername("admin");
//4.密码
factory.setPassword("123456");
//5.端口号
factory.setPort(5672 );
//6.创建连接
Connection connection = factory.newConnection();
//7.创建信道
Channel channel = connection.createChannel();
/**
* // 定义 DeliverCallback
* DeliverCallback deliverCallback = new DeliverCallback() {
* @Override
* public void handle(String consumerTag, com.rabbitmq.client.Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws Exception {
* // consumerTag 是消费者的标签
* // envelope 包含消息的元数据(如路由键、交换机名称等)
* // properties 包含消息的属性(如内容类型、优先级等)
* // body 是消息的实际内容(字节数组)
* System.out.println("接收到消息: " + new String(body));
* }
* };
*/
//接收消息的回调函数
DeliverCallback deliverCallback = (consumerTage, message) ->{
System.out.println("接收到消息"+new String(message.getBody()));
};
//取消消息的回调函数
CancelCallback cancelCallback = consumerTage ->{
System.out.println("消费消息被中断");
};
/**
* 消费消息
* 1.消费哪个队列
* 2.消费成功之后是否需要自动应答,true:自动应答
* 3.接收消息的回调函数
* 4.取消消息的回调函数
*/
channel.basicConsume("queue_name",true, deliverCallback, cancelCallback);
}
}