提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
提示:这里解释AMQP协议部分内容
AMQP(高级消息队列协议)是一个开放标准的应用层协议,专为消息中间件设计,定义了消息的结构、路由方式和传递规则,使不同厂商的消息队列系统(如 RabbitMQ、Apache Qpid 等)能够相互兼容。
一、AMQP 的核心组件
AMQP 模型包含以下关键组件,它们共同构成了消息传递的基础架构:
生产者(Producer):消息的创建者,负责发送消息到消息中间件
消费者(Consumer):消息的接收者,从消息中间件获取并处理消息
交换机(Exchange):接收生产者发送的消息,并根据路由规则转发到队列
队列(Queue):存储消息的缓冲区,消费者从队列中获取消息
绑定(Binding):定义交换机和队列之间的关联关系,包含路由规则
连接(Connection):客户端与消息中间件之间的 TCP 连接
信道(Channel):在连接内部建立的虚拟连接,用于消息传输(减少 TCP 连接开销)
二、AMQP 的消息结构
AMQP 消息由两部分组成:
1、消息属性(Properties):
描述消息的元数据,包括:消息 ID(message-id)、内容类型(content-type)、优先级(priority)、过期时间(expiration)、持久化模式(delivery-mode)、回复地址(reply-to)等。
2、消息体(Body)
实际的消息内容,通常是二进制数据,可以是任何格式(文本、JSON、二进制等)。
三、AMQP 的消息流转过程
生产者通过信道连接到消息中间件,生产者声明交换机(如需要)并发送消息,消息包含属性和体,交换机根据自身类型和绑定规则,将消息路由到一个或多个队列,队列存储消息,直到被消费者接收,消费者通过信道连接到消息中间件,声明队列(如需要)并订阅,消费者接收消息并处理,消费者发送确认(Ack),告知消息中间件消息已处理。
提示:以下是本篇文章正文内容,下面案例可供参考
一、什么是RabbitMQ
RabbitMQ 是一个开源的消息代理(Message Broker)软件,它实现了高级消息队列协议(AMQP)。 RabbitMQ 就是应用程序之间的“邮局”,负责接收、存储和转发消息。
RabbitMQ 解决了什么问题?
在现代软件架构中,应用程序很少是孤立运行的。它们需要相互通信。直接的点对点通信(比如一个服务直接调用另一个服务的 API)虽然简单,但存在很多问题:
耦合性高:服务A必须知道服务B的地址和接口,如果B宕机或接口变更,A就会失败。
峰值压力:如果突然有大量请求涌向服务B,B可能无法处理,导致系统崩溃。
同步阻塞:服务A调用服务B后,必须等待B返回结果才能继续,这段时间A是被阻塞的。
RabbitMQ 通过引入一个中间人(Broker) 来解决这些问题,它的核心思想是 “异步” 和 “解耦”。
RabbitMQ 的核心组件
Producer(生产者):发送消息的程序。
Exchange(交换机): 负责接收生产者发送的消息,并根据预设的路由规则(Binding Rule)将消息转发到对应的队列(Queue)中。
Queue(队列):位于 RabbitMQ 内部的“邮箱”。它本质上是一个巨大的消息缓冲区。多个生产者可以向一个队列发送消息,多个消费者可以尝试从一个队列接收消息。
Consumer(消费者):接收消息的程序。大部分时间都在等待,准备处理新消息。
基本流程:生产者将消息发送到交换机,并根据预设的路由规则(Binding Rule)将消息转发到对应的队列中,消费者从队列中获取消息并进行处理。RabbitMQ 负责确保消息从生产者安全地到达队列,最终被消费者接收。
二、交换机解读
1、交换机的核心属性
每个交换机都包含以下关键属性,这些属性决定了它的行为特征:
Name:交换机名称,自定义字符串(如 order_exchange),唯一标识交换机
Type:交换机类型 ,决定路由规则,核心类型有 4 种(Direct、Topic、Fanout、Headers)
Durability:是否持久化,true(重启 RabbitMQ 后交换机不丢失)、false(临时交换机,重启后删除)
Auto-delete:是否自动删除,true(最后一个绑定关系解除后,交换机自动删除)、false(不自动删除)
Internal:是否内部交换机,true(仅允许交换机之间转发消息,生产者无法直接发送消息到该交换机)、false(默认,允许生产者发送消息)
2、交换机的 4 种核心类型
不同类型的交换机对应不同的路由规则,适用于不同的业务场景。以下是 4 种核心类型的对比与说明:
1)Direct 交换机(精确匹配)
路由规则:消息的 Routing Key 必须与绑定关系中的 Binding Key 完全一致,才会被转发到对应的队列。
场景:一对一通信(如 “订单创建后发送通知”,一个消息仅需被一个消费者处理)。
示例逻辑:
交换机 direct_exchange 与队列 queue_a 绑定,Binding Key = “order.create”;
生产者发送消息时指定 Routing Key = “order.create”,消息会被路由到 queue_a;
若生产者指定 Routing Key = “order.pay”,则无队列匹配,消息会被丢弃(或进入死信队列)。
2)Topic 交换机(模糊匹配)
路由规则:Routing Key 和 Binding Key 均为 “点分隔的字符串”(如 order.create.user),支持通配符匹配:
*:匹配一个单词(如 order.* 可匹配 order.create、order.pay,但不能匹配 order.create.user);
#:匹配零个或多个单词(如 order.# 可匹配 order.create、order.create.user)。
场景:一对多通信(如 “日志分类转发”,不同级别 / 模块的日志发送到不同队列)。
示例逻辑:
交换机 topic_exchange 与队列 queue_error 绑定,Binding Key = “log.error.#”;
与队列 queue_order 绑定,Binding Key = “log.#.order”;
生产者发送消息 Routing Key = “log.error.order”,消息会被同时路由到 queue_error 和 queue_order。
3)Fanout 交换机(广播)
路由规则:忽略 Routing Key 和 Binding Key,将消息转发到所有与该交换机绑定的队列(“广播模式”)。
场景:一对多通信(如 “系统通知推送”,所有在线用户都需接收通知)。
特点:路由效率最高(无需匹配逻辑),但灵活性最低(无法筛选消息)。
示例逻辑:
交换机 fanout_exchange 绑定队列 queue_1、queue_2、queue_3;
生产者发送消息时无需指定 Routing Key(或任意指定),消息会被同时转发到 3 个队列。
4)Headers 交换机(头信息匹配)
路由规则:不依赖 Routing Key,而是通过消息的 “头信息(Headers)”(键值对形式,如 {“type”: “order”, “level”: “high”})与绑定关系中的 Headers 进行匹配。
匹配模式:
x-match = all:消息的所有头信息必须与绑定的头信息完全一致;
x-match = any:消息的头信息只需有一个与绑定的头信息一致。
场景:复杂的消息筛选(如 “根据消息的多个属性路由”),但实际使用较少(Topic 交换机已能满足大部分模糊匹配场景)。
3、绑定(Binding)与路由键(Routing Key)
交换机的路由能力依赖于 “绑定” 和 “路由键” 两个概念:
绑定(Binding):是交换机与队列之间的 “关联关系”,通过 Binding Key 定义匹配规则(Headers 交换机除外)。创建绑定时需指定:交换机名 + 队列名 + Binding Key + 其他参数。
路由键(Routing Key):是生产者发送消息时附加的 “标识”,用于告诉交换机 “该消息应被路由到哪些队列”。交换机根据自身类型,将 Routing Key 与 Binding Key 进行匹配,最终决定消息流向。
三、Java 中使用 RabbitMQ 交换机的完整实践
Java 中操作 RabbitMQ 主要依赖官方提供的 amqp-client 依赖,以下通过 “生产者 - 消费者” 模型,演示 4 种交换机类型的具体使用。
1、前置准备:环境搭建
1)添加 Maven 依赖
在 pom.xml 中引入 amqp-client(RabbitMQ 官方 Java 客户端):
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.20.0</version> <!-- 最新版本可在 Maven 中央仓库查询 -->
</dependency>
2)RabbitMQ 连接工具类
创建工具类 RabbitMQUtils,封装连接创建与关闭逻辑(避免重复代码):
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class RabbitMQUtils {
// RabbitMQ 服务器地址(本地默认地址为 localhost)
private static final String HOST = "localhost";
// 端口号(默认 5672)
private static final int PORT = 5672;
// 虚拟主机(默认 /,可自定义)
private static final String VIRTUAL_HOST = "/";
// 用户名(默认 guest)
private static final String USERNAME = "guest";
// 密码(默认 guest)
private static final String PASSWORD = "guest";
// 创建连接工厂(单例,避免重复创建)
private static ConnectionFactory factory;
static {
factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setVirtualHost(VIRTUAL_HOST);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
}
/**
* 获取 RabbitMQ 连接
*/
public static Connection getConnection() throws IOException, TimeoutException {
return factory.newConnection();
}
/**
* 关闭连接和通道
*/
public static void close(Connection connection, Channel channel) {
try {
if (channel != null && channel.isOpen()) {
channel.close();
}
if (connection != null && connection.isOpen()) {
connection.close();
}
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
2、实践 1:Direct 交换机(精确匹配)
1)生产者(发送消息)
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class DirectProducer {
// 交换机名称
private static final String DIRECT_EXCHANGE_NAME = "direct_exchange";
// 路由键(需与绑定键完全匹配)
private static final String ROUTING_KEY = "order.create";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1. 获取连接
connection = RabbitMQUtils.getConnection();
// 2. 创建通道(RabbitMQ 通信的核心,大部分操作通过通道完成)
channel = connection.createChannel();
// 3. 声明交换机(参数:交换机名、类型、是否持久化、是否自动删除、是否内部、其他参数)
channel.exchangeDeclare(DIRECT_EXCHANGE_NAME, "direct", true, false, null);
// 4. 发送消息(参数:交换机名、路由键、消息属性、消息体(字节数组))
String message = "用户 1001 创建订单:订单号 ORDER_20240520001";
channel.basicPublish(DIRECT_EXCHANGE_NAME, ROUTING_KEY, null, message.getBytes());
System.out.println("Direct 生产者发送消息成功:" + message);
} catch (IOException | TimeoutException e) {
e.printStackTrace();
} finally {
// 5. 关闭连接和通道
RabbitMQUtils.close(connection, channel);
}
}
}
2)消费者(接收消息)
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class DirectConsumer {
// 交换机名称(与生产者一致)
private static final String DIRECT_EXCHANGE_NAME = "direct_exchange";
// 队列名称(自定义)
private static final String DIRECT_QUEUE_NAME = "direct_queue";
// 绑定键(与生产者的路由键完全匹配)
private static final String BINDING_KEY = "order.create";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
// 1. 获取连接和通道
connection = RabbitMQUtils.getConnection();
channel = connection.createChannel();
// 2. 声明队列(参数:队列名、是否持久化、是否排他、是否自动删除、其他参数)
channel.queueDeclare(DIRECT_QUEUE_NAME, true, false, false, null);
// 3. 绑定队列与交换机(参数:队列名、交换机名、绑定键、其他参数)
channel.queueBind(DIRECT_QUEUE_NAME, DIRECT_EXCHANGE_NAME, BINDING_KEY, null);
// 4. 消费消息(通过 DefaultConsumer 监听队列,异步接收消息)
Channel finalChannel = channel;
Consumer consumer = new DefaultConsumer(finalChannel) {
// 消息到达时触发的回调方法
@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("Direct 消费者接收消息:" + message);
// 手动确认消息(参数:消息标识、是否批量确认)
finalChannel.basicAck(envelope.getDeliveryTag(), false);
}
};
// 5. 启动消费(参数:队列名、是否自动确认消息、消费者)
// 注意:此处设置为 false,手动确认消息,避免消息丢失
channel.basicConsume(DIRECT_QUEUE_NAME, false, consumer);
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
// 消费者需持续运行,不主动关闭连接
}
}
3、实践 2:Topic 交换机(模糊匹配)
1)生产者(发送消息)
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class TopicProducer {
private static final String TOPIC_EXCHANGE_NAME = "topic_exchange";
// 路由键(支持通配符匹配)
private static final String ROUTING_KEY = "log.error.order";
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
connection = RabbitMQUtils.getConnection();
channel = connection.createChannel();
// 声明 Topic 类型交换机
channel.exchangeDeclare(TOPIC_EXCHANGE_NAME, "topic", true, false, null);
String message = "订单支付失败:订单号 ORDER_20240520001,错误原因:余额不足";
channel.basicPublish(TOPIC_EXCHANGE_NAME, ROUTING_KEY, null, message.getBytes());
System.out.println("Topic 生产者发送消息成功:" + message);
} catch (IOException | TimeoutException e) {
e.printStackTrace();
} finally {
RabbitMQUtils.close(connection, channel);
}
}
}
2)消费者 1(接收 error 级别的日志)
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class TopicConsumer1 {
private static final String TOPIC_EXCHANGE_NAME = "topic_exchange";
private static final String QUEUE_NAME = "topic_queue_error";
// 绑定键:匹配所有 error 级别的日志
private static final String BINDING_KEY = "log.error.#";
public static void main(String[] args) {
try {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
channel.queueBind(QUEUE_NAME, TOPIC_EXCHANGE_NAME, BINDING_KEY, null);
Consumer consumer = 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("Topic 消费者 1(error 日志)接收消息:" + message);
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(QUEUE_NAME, false, consumer);
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
3)消费者 2(接收与订单相关的日志)
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class TopicConsumer2 {
private static final String TOPIC_EXCHANGE_NAME = "topic_exchange";
private static final String QUEUE_NAME = "topic_queue_order";
// 绑定键:匹配所有与订单相关的日志
private static final String BINDING_KEY = "log.#.order";
public static void main(String[] args) {
try {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
channel.queueBind(QUEUE_NAME, TOPIC_EXCHANGE_NAME, BINDING_KEY, null);
Consumer consumer = 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("Topic 消费者 2(订单日志)接收消息:" + message);
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(QUEUE_NAME, false, consumer);
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
4、实践 3:Fanout 交换机(广播)
Fanout 交换机无需指定 Routing Key 和 Binding Key,绑定后即可广播消息:
1)生产者
public class FanoutProducer {
private static final String FANOUT_EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] args) {
try {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
// 声明 Fanout 类型交换机
channel.exchangeDeclare(FANOUT_EXCHANGE_NAME, "fanout", true, false, null);
String message = "系统通知:2024年5月20日 20:00 进行服务器维护,预计持续 1 小时";
// 发送消息时,Routing Key 可设为 ""(Fanout 会忽略)
channel.basicPublish(FANOUT_EXCHANGE_NAME, "", null, message.getBytes());
System.out.println("Fanout 生产者发送广播消息:" + message);
RabbitMQUtils.close(connection, channel);
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
2)消费者 1(接收广播消息)
public class FanoutConsumer1 {
private static final String FANOUT_EXCHANGE_NAME = "fanout_exchange";
private static final String QUEUE_NAME = "fanout_queue_1";
public static void main(String[] args) {
try {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 绑定队列与 Fanout 交换机,Binding Key 设为 ""(忽略)
channel.queueBind(QUEUE_NAME, FANOUT_EXCHANGE_NAME, "", null);
Consumer consumer = 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("Fanout 消费者 1 接收消息:" + message);
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(QUEUE_NAME, false, consumer);
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
3)消费者 2(接收广播消息)
public class FanoutConsumer2 {
private static final String FANOUT_EXCHANGE_NAME = "fanout_exchange";
private static final String QUEUE_NAME = "fanout_queue_2";
public static void main(String[] args) {
try {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 绑定队列与 Fanout 交换机,Binding Key 设为 ""(忽略)
channel.queueBind(QUEUE_NAME, FANOUT_EXCHANGE_NAME, "", null);
Consumer consumer = 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("Fanout 消费者 2 接收消息:" + message);
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(QUEUE_NAME, false, consumer);
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
5、实践 4:Headers 交换机(头信息匹配)
Headers 交换机通过消息头匹配,需在绑定和发送消息时指定头信息:
1)生产者
public class HeadersProducer {
private static final String HEADERS_EXCHANGE_NAME = "headers_exchange";
public static void main(String[] args) {
try {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
// 声明 Headers 类型交换机
channel.exchangeDeclare(HEADERS_EXCHANGE_NAME, "headers", true, false, null);
// 定义消息头(键值对)
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.headers(Map.of("type", "order", "level", "high")) // 消息头:类型=订单,级别=高
.build();
String message = "高优先级订单:用户 1001 提交 VIP 订单,需优先处理";
// Headers 交换机忽略 Routing Key,设为 ""
channel.basicPublish(HEADERS_EXCHANGE_NAME, "", properties, message.getBytes());
System.out.println("Headers 生产者发送消息:" + message);
RabbitMQUtils.close(connection, channel);
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
2)消费者
public class HeadersConsumer {
private static final String HEADERS_EXCHANGE_NAME = "headers_exchange";
private static final String QUEUE_NAME = "headers_queue";
public static void main(String[] args) {
try {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 定义绑定头信息和匹配模式(x-match=all:所有头信息需匹配)
Map<String, Object> bindingHeaders = new HashMap<>();
bindingHeaders.put("type", "order");
bindingHeaders.put("level", "high");
bindingHeaders.put("x-match", "all"); // 匹配模式:全部匹配
// 绑定队列与 Headers 交换机,传入绑定头信息
channel.queueBind(QUEUE_NAME, HEADERS_EXCHANGE_NAME, "", bindingHeaders);
Consumer consumer = 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("Headers 消费者接收消息:" + message);
System.out.println("消息头:" + properties.getHeaders());
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(QUEUE_NAME, false, consumer);
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
51万+

被折叠的 条评论
为什么被折叠?



