RabbitMQ的使用

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里解释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();
        }
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值