RabbitMQ入门教程

RabbitMQ 入门实战教程

一、什么是 RabbitMQ?

RabbitMQ 是一个开源的消息代理(Message Broker),采用 Erlang 语言编写,基于 AMQP(Advanced Message Queuing Protocol)协议实现。它负责接收、存储和转发消息,是微服务架构中异步解耦的核心组件。

1.1 为什么需要消息队列?

场景:用户下单后需要发送短信和邮件通知

  • 同步调用:下单接口等待短信 + 邮件发送完毕才返回,响应慢
  • 引入 MQ:下单后立即返回,MQ 异步通知短信和邮件服务

核心优势:

优势 说明
异步处理 请求无需等待所有下游完成,提升响应速度
流量削峰 秒杀场景下请求先入队列,后端按能力消费
系统解耦 生产者和消费者互不感知,可独立扩缩容
可靠性 消息持久化 + 确认机制,保证不丢失

1.2 核心概念

复制代码
                    +-----------+
                    |   Producer  |  ----- 消息发布者
                    +-----+-----+
                          |
                          v
              +-----------+-----------+
              |       Exchange        |  ----- 交换机(路由消息)
              +-----------+-----------+
                          |
          +---------------+----------------+
          |               |                |
          v               v                v
    +-----------+   +-----------+    +-----------+
    |   Queue   |   |   Queue   |    |   Queue   |  ----- 队列(存储消息)
    +-----------+   +-----------+    +-----------+
          |               |                |
          v               v                v
    +-----------+   +-----------+    +-----------+
    | Consumer  |   | Consumer  |    | Consumer  |  ----- 消息消费者
    +-----------+   +-----------+    +-----------+
概念 说明
Producer 消息的生产者/发布者
Consumer 消息的消费者/订阅者
Queue 队列,存储消息的缓冲区
Exchange 交换机,接收消息并按路由规则分发到队列
Binding 绑定,定义 Exchange 与 Queue 之间的路由关系
Routing Key 路由键,Exchange 根据它决定将消息投递到哪个队列
Connection TCP 连接
Channel 在 Connection 上建立的虚拟连接,复用 TCP

二、安装 RabbitMQ

2.1 Docker 安装(推荐)

bash 复制代码
docker run -d \
  --name rabbitmq \
  -p 5672:5672 \
  -p 15672:15672 \
  -p 25672:25672 \
  -e RABBITMQ_DEFAULT_USER=admin \
  -e RABBITMQ_DEFAULT_PASS=admin123 \
  rabbitmq:3.13-management

端口说明:

端口 用途
5672 AMQP 协议端口(客户端连接)
15672 Web 管理控制台
25672 Erlang 节点间通信

2.2 直接安装

  1. 安装 Erlang(RabbitMQ 依赖)
  2. 下载 RabbitMQ Server
  3. 启动后访问 http://localhost:15672,账号密码 guest/guest

2.3 启用管理插件

bash 复制代码
rabbitmq-plugins enable rabbitmq_management

三、六种工作模式

RabbitMQ 提供了多种消息模型,下面逐一讲解。

3.1 Hello World(简单模式)

一个生产者 → 一个队列 → 一个消费者。

复制代码
[P] ----> [Queue] ----> [C]

生产者:

java 复制代码
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {

    private static final String QUEUE_NAME = "hello";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        factory.setUsername("admin");
        factory.setPassword("admin123");

        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {

            channel.queueDeclare(QUEUE_NAME, false, false, false, null);

            String message = "Hello RabbitMQ!";
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            System.out.println(" [x] 发送消息:" + message);
        }
    }
}

消费者:

java 复制代码
import com.rabbitmq.client.*;

public class Consumer {

    private static final String QUEUE_NAME = "hello";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        factory.setUsername("admin");
        factory.setPassword("admin123");

        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [x] 收到消息:" + message);
        };

        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {});
    }
}

3.2 Work Queues(工作队列)

一个生产者 → 一个队列 → 多个消费者(竞争消费)。

复制代码
[P] ----> [Queue] ----> [C1]
                   \---> [C2]

适用于任务分发场景,默认采用轮询分发,每个消费者平均获取消息。

消息确认 & 公平分发:

java 复制代码
// 每次只处理一条消息,处理完再接受下一条
channel.basicQos(1);

// 手动 ACK
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, deliverCallback, consumerTag -> {});

// 在 deliverCallback 中确认
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);

3.3 Publish / Subscribe(发布订阅模式)

一个生产者 → 交换机 → 多个队列 → 多个消费者。

复制代码
[P] ----> [Fanout Exchange] ----> [Queue1] ----> [C1]
                             \---> [Queue2] ----> [C2]

交换机类型:Fanout(广播),将消息发送给所有绑定的队列。

java 复制代码
// 生产者 - 声明交换机
channel.exchangeDeclare("logs", BuiltinExchangeType.FANOUT);

String message = "广播消息";
channel.basicPublish("logs", "", null, message.getBytes());

// 消费者 - 创建临时队列并绑定
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, "logs", "");

3.4 Routing(路由模式)

复制代码
[P] ----> [Direct Exchange] ----> [Queue1] routingKey=error ----> [C1]
                            \---> [Queue2] routingKey=info   \
                                     routingKey=warning       ----> [C2]
                                     routingKey=error

交换机类型:Direct,根据 Routing Key 精确匹配。

java 复制代码
// 生产者
channel.exchangeDeclare("direct_logs", BuiltinExchangeType.DIRECT);
channel.basicPublish("direct_logs", "error", null, message.getBytes());

// 消费者 - 绑定特定 routing key
channel.queueBind(queueName, "direct_logs", "error");
channel.queueBind(queueName, "direct_logs", "warning");

3.5 Topics(主题模式)

复制代码
[P] ----> [Topic Exchange] ----> [Queue1] *.orange.*  ----> [C1]
                           \---> [Queue2] *.*.rabbit   ----> [C2]
                           \---> [Queue3] lazy.#       ----> [C3]

交换机类型:Topic,根据 Routing Key 模糊匹配。

通配符规则:

通配符 含义
* 匹配一个单词
# 匹配零个或多个单词

示例:

java 复制代码
// Routing Key 格式:<速度>.<颜色>.<动物>

// 生产者
channel.exchangeDeclare("topic_logs", BuiltinExchangeType.TOPIC);
channel.basicPublish("topic_logs", "quick.orange.rabbit", null, message.getBytes());

// 消费者1 - 匹配所有橙色动物
channel.queueBind(queueName, "topic_logs", "*.orange.*");

// 消费者2 - 匹配所有兔子(任意颜色速度)
channel.queueBind(queueName, "topic_logs", "*.*.rabbit");

// 消费者3 - 匹配 lazy 开头的所有
channel.queueBind(queueName, "topic_logs", "lazy.#");

3.6 Headers(头部模式)

不依赖 Routing Key,而是根据消息的 Headers 属性匹配。性能较低,实际使用较少。

java 复制代码
Map<String, Object> headers = new HashMap<>();
headers.put("x-match", "all"); // all=全部匹配, any=匹配任一
headers.put("format", "pdf");
headers.put("type", "report");

channel.queueBind(queueName, "headers_exchange", "", headers);

四、Spring Boot 整合 RabbitMQ

4.1 引入依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

4.2 配置

yaml 复制代码
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: admin
    password: admin123
    virtual-host: /
    listener:
      simple:
        acknowledge-mode: manual    # 手动确认
        prefetch: 1                 # 每次拉取一条
        retry:
          enabled: true             # 消费失败重试
          max-attempts: 3
          initial-interval: 2000
    template:
      retry:
        enabled: true               # 发送重试
        max-attempts: 3

4.3 配置类

java 复制代码
package com.example.rabbitmq.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

    // ========== Direct 模式 ==========
    public static final String DIRECT_EXCHANGE = "direct.exchange";
    public static final String DIRECT_QUEUE = "direct.queue";
    public static final String DIRECT_ROUTING_KEY = "direct.key";

    @Bean
    public Queue directQueue() {
        return QueueBuilder.durable(DIRECT_QUEUE).build();
    }

    @Bean
    public DirectExchange directExchange() {
        return ExchangeBuilder.directExchange(DIRECT_EXCHANGE).durable(true).build();
    }

    @Bean
    public Binding directBinding(Queue directQueue, DirectExchange directExchange) {
        return BindingBuilder.bind(directQueue)
                .to(directExchange)
                .with(DIRECT_ROUTING_KEY);
    }

    // ========== Topic 模式 ==========
    public static final String TOPIC_EXCHANGE = "topic.exchange";
    public static final String TOPIC_QUEUE = "topic.queue";
    public static final String TOPIC_ROUTING_KEY = "topic.#";

    @Bean
    public Queue topicQueue() {
        return QueueBuilder.durable(TOPIC_QUEUE).build();
    }

    @Bean
    public TopicExchange topicExchange() {
        return ExchangeBuilder.topicExchange(TOPIC_EXCHANGE).durable(true).build();
    }

    @Bean
    public Binding topicBinding(Queue topicQueue, TopicExchange topicExchange) {
        return BindingBuilder.bind(topicQueue)
                .to(topicExchange)
                .with(TOPIC_ROUTING_KEY);
    }

    // ========== Fanout 模式 ==========
    public static final String FANOUT_EXCHANGE = "fanout.exchange";
    public static final String FANOUT_QUEUE_A = "fanout.queue.a";
    public static final String FANOUT_QUEUE_B = "fanout.queue.b";

    @Bean
    public Queue fanoutQueueA() {
        return QueueBuilder.durable(FANOUT_QUEUE_A).build();
    }

    @Bean
    public Queue fanoutQueueB() {
        return QueueBuilder.durable(FANOUT_QUEUE_B).build();
    }

    @Bean
    public FanoutExchange fanoutExchange() {
        return ExchangeBuilder.fanoutExchange(FANOUT_EXCHANGE).durable(true).build();
    }

    @Bean
    public Binding fanoutBindingA(Queue fanoutQueueA, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(fanoutQueueA).to(fanoutExchange);
    }

    @Bean
    public Binding fanoutBindingB(Queue fanoutQueueB, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(fanoutQueueB).to(fanoutExchange);
    }
}

4.4 消息发送

java 复制代码
package com.example.rabbitmq.service;

import com.example.rabbitmq.config.RabbitConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class MessageSender {

    private final RabbitTemplate rabbitTemplate;

    public void sendDirect(String message) {
        rabbitTemplate.convertAndSend(
                RabbitConfig.DIRECT_EXCHANGE,
                RabbitConfig.DIRECT_ROUTING_KEY,
                message
        );
        log.info("Direct 消息发送成功:{}", message);
    }

    public void sendTopic(String routingKey, String message) {
        rabbitTemplate.convertAndSend(
                RabbitConfig.TOPIC_EXCHANGE,
                routingKey,
                message
        );
        log.info("Topic 消息发送成功:[{}] {}", routingKey, message);
    }

    public void sendFanout(String message) {
        rabbitTemplate.convertAndSend(
                RabbitConfig.FANOUT_EXCHANGE,
                "",
                message
        );
        log.info("Fanout 广播消息发送成功:{}", message);
    }
}

4.5 消息接收

java 复制代码
package com.example.rabbitmq.listener;

import com.example.rabbitmq.config.RabbitConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class MessageListener {

    @RabbitListener(queues = RabbitConfig.DIRECT_QUEUE)
    public void handleDirect(String message, Channel channel, Message msg) {
        try {
            log.info("Direct 消费者收到消息:{}", message);
            // 手动确认
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            log.error("消息处理失败", e);
            try {
                // 重回队列或丢弃
                channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
            } catch (Exception ignored) {}
        }
    }

    @RabbitListener(queues = RabbitConfig.TOPIC_QUEUE)
    public void handleTopic(String message) {
        log.info("Topic 消费者收到消息:{}", message);
    }

    @RabbitListener(queues = RabbitConfig.FANOUT_QUEUE_A)
    public void handleFanoutA(String message) {
        log.info("Fanout 队列A收到消息:{}", message);
    }

    @RabbitListener(queues = RabbitConfig.FANOUT_QUEUE_B)
    public void handleFanoutB(String message) {
        log.info("Fanout 队列B收到消息:{}", message);
    }
}

4.6 Controller 测试

java 复制代码
package com.example.rabbitmq.controller;

import com.example.rabbitmq.service.MessageSender;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/mq")
@RequiredArgsConstructor
public class MqController {

    private final MessageSender messageSender;

    @GetMapping("/direct")
    public String direct(@RequestParam(defaultValue = "Hello Direct") String msg) {
        messageSender.sendDirect(msg);
        return "Direct 消息已发送";
    }

    @GetMapping("/topic")
    public String topic(
            @RequestParam(defaultValue = "topic.test.hello") String routingKey,
            @RequestParam(defaultValue = "Hello Topic") String msg) {
        messageSender.sendTopic(routingKey, msg);
        return "Topic 消息已发送";
    }

    @GetMapping("/fanout")
    public String fanout(@RequestParam(defaultValue = "Hello Fanout") String msg) {
        messageSender.sendFanout(msg);
        return "Fanout 广播消息已发送";
    }
}

五、死信队列(DLQ)

死信队列用于处理消费失败的消息,是保证消息可靠性的重要机制。

5.1 消息变为死信的三种情况

  1. 消息被消费者拒绝(basicNack / basicReject)且 requeue=false
  2. 消息 TTL 过期
  3. 队列达到最大长度

5.2 死信队列配置

java 复制代码
@Configuration
public class DlxConfig {

    // 业务队列
    public static final String BUSINESS_QUEUE = "business.queue";
    public static final String BUSINESS_EXCHANGE = "business.exchange";
    public static final String BUSINESS_ROUTING_KEY = "business.key";

    // 死信队列
    public static final String DLX_EXCHANGE = "dlx.exchange";
    public static final String DLX_QUEUE = "dlx.queue";
    public static final String DLX_ROUTING_KEY = "dlx.key";

    @Bean
    public Queue businessQueue() {
        return QueueBuilder.durable(BUSINESS_QUEUE)
                .deadLetterExchange(DLX_EXCHANGE)       // 指定死信交换机
                .deadLetterRoutingKey(DLX_ROUTING_KEY)  // 指定死信路由键
                .ttl(60000)                             // 消息过期时间 60s
                .maxLength(10000)                       // 最大消息数
                .build();
    }

    @Bean
    public DirectExchange businessExchange() {
        return ExchangeBuilder.directExchange(BUSINESS_EXCHANGE).durable(true).build();
    }

    @Bean
    public Binding businessBinding() {
        return BindingBuilder.bind(businessQueue())
                .to(businessExchange())
                .with(BUSINESS_ROUTING_KEY);
    }

    // 死信队列声明
    @Bean
    public Queue dlxQueue() {
        return QueueBuilder.durable(DLX_QUEUE).build();
    }

    @Bean
    public DirectExchange dlxExchange() {
        return ExchangeBuilder.directExchange(DLX_EXCHANGE).durable(true).build();
    }

    @Bean
    public Binding dlxBinding() {
        return BindingBuilder.bind(dlxQueue())
                .to(dlxExchange())
                .with(DLX_ROUTING_KEY);
    }
}

六、延迟队列

RabbitMQ 本身没有延迟队列功能,通过死信队列官方延迟插件实现。

6.1 使用死信队列实现延迟

生产者发送消息时设置 TTL,消息过期后自动进入死信队列,实现延迟消费。

6.2 使用延迟队列插件

bash 复制代码
# 下载插件
docker exec -it rabbitmq rabbitmq-plugins enable rabbitmq_delayed_message_exchange
java 复制代码
@Bean
public CustomExchange delayExchange() {
    Map<String, Object> args = new HashMap<>();
    args.put("x-delayed-type", "direct");
    return new CustomExchange("delay.exchange", "x-delayed-message", true, false, args);
}

// 发送延迟消息
public void sendDelay(String message, long delayMillis) {
    MessageProperties props = new MessageProperties();
    props.setDelay(Math.toIntExact(delayMillis));
    Message msg = MessageBuilder.withBody(message.getBytes()).andProperties(props).build();
    rabbitTemplate.send("delay.exchange", "delay.key", msg);
}

七、消息可靠性保障

7.1 发送端确认

yaml 复制代码
spring:
  rabbitmq:
    publisher-confirm-type: correlated  # 确认模式(消息到达交换机)
    publisher-returns: true             # 退回模式(消息未到达队列)
java 复制代码
@PostConstruct
public void init() {
    rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
        if (ack) {
            log.info("消息到达交换机:{}", correlationData.getId());
        } else {
            log.error("消息未到达交换机,原因:{}", cause);
        }
    });

    rabbitTemplate.setReturnsCallback(returned -> {
        log.error("消息未到达队列:{},replyCode:{},replyText:{}",
                returned.getMessage(), returned.getReplyCode(), returned.getReplyText());
    });
}

7.2 消息持久化

java 复制代码
// 队列持久化
@Bean
public Queue durableQueue() {
    return QueueBuilder.durable("queue.name").build(); // durable=true
}

// 消息持久化(MessageProperties 默认持久化)
MessageProperties props = new MessageProperties();
props.setDeliveryMode(MessageDeliveryMode.PERSISTENT);

7.3 手动确认

java 复制代码
@RabbitListener(queues = "queue")
public void handle(Message message, Channel channel) {
    long tag = message.getMessageProperties().getDeliveryTag();
    try {
        // 业务处理
        process(message);
        channel.basicAck(tag, false);       // 确认成功
    } catch (Exception e) {
        if (message.getMessageProperties().getRedelivered()) {
            channel.basicNack(tag, false, false); // 已重试过,丢弃
        } else {
            channel.basicNack(tag, false, true);  // 重回队列重试
        }
    }
}

八、运维与管理

8.1 管理控制台

访问 http://localhost:15672 可查看:

  • 连接(Connections)和通道(Channels)
  • 交换机(Exchanges)和队列(Queues)
  • 消息速率和堆积情况
  • 用户权限管理

8.2 常用命令

bash 复制代码
# 查看队列
rabbitmqctl list_queues

# 查看队列详细信息(包含消费者数)
rabbitmqctl list_queues name messages consumers

# 查看连接
rabbitmqctl list_connections

# 清除队列消息
rabbitmqctl purge_queue queue_name

8.3 集群部署

bash 复制代码
# 启动多个 RabbitMQ 节点后组成集群
docker run -d --name rabbitmq1 -p 5672:5672 -p 15672:15672 rabbitmq:management
docker run -d --name rabbitmq2 -p 5673:5672 -p 15673:15672 --link rabbitmq1 rabbitmq:management

# 在 rabbitmq2 中加入集群
docker exec -it rabbitmq2 rabbitmqctl stop_app
docker exec -it rabbitmq2 rabbitmqctl join_cluster rabbit@rabbitmq1
docker exec -it rabbitmq2 rabbitmqctl start_app

九、常见问题

9.1 消息堆积

  • 原因:消费者消费速度跟不上生产者
  • 解决:增加消费者实例 / 调整 prefetch / 检查业务逻辑耗时

9.2 连接超时

yaml 复制代码
spring:
  rabbitmq:
    connection-timeout: 15000      # 连接超时
    template:
      reply-timeout: 5000         # 发送回复超时

9.3 消息丢失

排查链路:

复制代码
生产者 -> 交换机 -> 队列 -> 消费者
  ①         ②        ③       ④
  • ①:开启 publisher-confirm
  • ②:Exchange 必须存在(或 auto-delete 为 false)
  • ③:队列持久化 + 消息持久化
  • ④:手动 ACK

9.4 重复消费

消费端做好幂等性处理:

  • 数据库唯一约束
  • Redis 记录消息 ID(SETNX
  • 业务表内置消息 ID 字段

十、总结

模式 交换机类型 适用场景
简单模式 无(默认直连) 点对点通信
工作队列 任务分发、负载均衡
发布订阅 Fanout 广播通知、全量推送
路由模式 Direct 日志分级、定向通知
主题模式 Topic 按规则匹配、灵活路由
延迟队列 x-delayed-message 订单超时取消、定时任务

RabbitMQ 以其稳定可靠、功能丰富、社区活跃等优势,成为业界最广泛使用的消息中间件之一。掌握 RabbitMQ 是后端开发工程师进阶的必修课。

相关推荐
代码漫谈3 小时前
RabbitMQ 单节点部署指南
分布式·消息队列·rabbitmq
aLTttY3 小时前
Spring Boot + Redis 实战分布式锁:从入门到精通
spring boot·redis·分布式
weixin_419658313 小时前
RabbitMQ 应用问题
java·分布式·中间件·rabbitmq
2301_815279524 小时前
RabbitMQ - 在微服务架构中的落地实践:消息推送 / 解耦 / 削峰填谷
微服务·架构·rabbitmq
希望永不加班4 小时前
SpringBoot 整合 RabbitMQ 入门
java·spring boot·后端·rabbitmq·java-rabbitmq
爱艺江河4 小时前
HarmonyOS智慧风控:基于分布式架构的安全与创新实践
分布式·架构·harmonyos
juniperhan4 小时前
Flink 系列第18篇:Flink 动态表、连续查询与 Changelog 机制
java·大数据·数据仓库·分布式·flink
juniperhan4 小时前
Flink 系列第19篇:深入理解 Flink SQL 的时间语义与时区处理:从原理到实战
java·大数据·数据仓库·分布式·sql·flink
珠海西格电力4 小时前
零碳园区管理系统“云-边-端”架构协同的核心价值
大数据·人工智能·分布式·微服务·架构·能源