Java rabbitMQ如何发送、消费消息、全套可靠方案

  • 原生 RabbitMQ Client(最基础)
  • Spring Boot + RabbitMQ(企业实际开发 99% 用这个)

一、前提

安装并启动 RabbitMQ,开启:5672 端口。

二、Spring Boot 方式(推荐,企业真实用法)

1. pom 依赖

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

2. application.yml

XML 复制代码
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /

3. 配置类(交换机、队列、绑定)

java 复制代码
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

    public static final String QUEUE = "test_queue";
    public static final String EXCHANGE = "test_exchange";
    public static final String ROUTING_KEY = "test.rk";

    // 队列
    @Bean
    public Queue queue() {
        return new Queue(QUEUE, true); // 持久化
    }

    // 交换机
    @Bean
    public DirectExchange exchange() {
        return new DirectExchange(EXCHANGE);
    }

    // 绑定
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(queue())
                .to(exchange())
                .with(ROUTING_KEY);
    }
}

4. 生产者(发送消息)

java 复制代码
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service
public class RabbitProducer {

    @Resource
    private RabbitTemplate rabbitTemplate;

    public void send(String msg) {
        rabbitTemplate.convertAndSend(
                RabbitConfig.EXCHANGE,
                RabbitConfig.ROUTING_KEY,
                msg
        );
        System.out.println("发送成功:" + msg);
    }
}
复制代码
}

5. 消费者(监听消息)

java 复制代码
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class RabbitConsumer {

    @RabbitListener(queues = RabbitConfig.QUEUE)
    public void receive(String msg) {
        System.out.println("收到消息:" + msg);
        // 业务处理
    }
}

使用:

java 复制代码
@Autowired
RabbitProducer producer;

producer.send("Hello RabbitMQ!");

三、原生 Java Client(不依赖 Spring,底层原理)

1. 依赖

java 复制代码
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.16.0</version>
</dependency>

2. 生产者

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

public class Producer {
    private final static String QUEUE = "test_queue";

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

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

            channel.queueDeclare(QUEUE, true, false, false, null);
            String message = "原生客户端消息";
            channel.basicPublish("", QUEUE, null, message.getBytes());
        }
    }
}

3. 消费者

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

public class Consumer {
    private final static String QUEUE = "test_queue";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");

        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE, true, false, false, null);

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody());
            System.out.println("消费:" + message);
        };
        channel.basicConsume(QUEUE, true, deliverCallback, consumerTag -> {});
    }
}

四、关键要点

  1. 消息确认机制
    • 生产者:publisher-confirm-type: correlated
    • 消费者:手动 ACK(acknowledge-mode: manual)防止丢消息
  2. 消息持久化:队列、消息都要持久化
  3. 预取计数prefetch: 1 防止消费者被打满
  4. 重试、死信队列(DLX) 处理失败消息

六、企业生产级、可直接上线的 RabbitMQ 全套可靠方案

消息不丢失 + 不重复消费 + 手动 ACK + 重试 + 死信队列 + 延迟队列 + 幂等 + 高可用配置 ,全部是Spring Boot完整可运行代码。

一、完整 pom.xml

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

二、application.yml(生产级配置)

java 复制代码
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    # 生产者确认
    publisher-confirm-type: correlated  # 开启confirm
    publisher-returns: true             # 开启return
    template:
      mandatory: true                   # 消息无法路由返回生产者
    # 消费者配置(核心)
    listener:
      simple:
        acknowledge-mode: manual        # 手动ACK(关键!防丢消息)
        prefetch: 1                      # 限流,一次只推1条,处理完再推下一条
        retry:
          enabled: true                  # 开启本地重试
          max-attempts: 3                # 最多重试3次
          initial-interval: 1000         # 间隔1s

三、核心队列结构(业务队列 + 死信队列 + 延迟队列)

java 复制代码
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {

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

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

    // ==================== 延迟队列(可选) ====================
    public static final String DELAY_QUEUE = "delay.queue";
    public static final String DELAY_EXCHANGE = "delay.exchange";
    public static final String DELAY_ROUTING_KEY = "delay.rk";

    // -------------- 1. 业务队列 --------------
    @Bean
    public Queue businessQueue() {
        return QueueBuilder.durable(BUSINESS_QUEUE)
                // 绑定死信交换机
                .deadLetterExchange(DLX_EXCHANGE)
                .deadLetterRoutingKey(DLX_ROUTING_KEY)
                // 消息10分钟过期(可选)
                //.ttl(600000)
                .build();
    }

    @Bean
    public DirectExchange businessExchange() {
        return new DirectExchange(BUSINESS_EXCHANGE, true, false);
    }

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

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

    @Bean
    public DirectExchange dlxExchange() {
        return new DirectExchange(DLX_EXCHANGE, true, false);
    }

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

    // -------------- 3. 延迟队列(TTL + DLX 实现) --------------
    @Bean
    public Queue delayQueue() {
        return QueueBuilder.durable(DELAY_QUEUE)
                .deadLetterExchange(BUSINESS_EXCHANGE)
                .deadLetterRoutingKey(BUSINESS_ROUTING_KEY)
                .build();
    }

    @Bean
    public DirectExchange delayExchange() {
        return new DirectExchange(DELAY_EXCHANGE, true, false);
    }

    @Bean
    public Binding delayBinding() {
        return BindingBuilder.bind(delayQueue())
                .to(delayExchange())
                .with(DELAY_ROUTING_KEY);
    }
}

四、生产者(带 Confirm + Return 可靠发送)

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.UUID;

@Slf4j
@Service
public class ReliableProducer implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init() {
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);
    }

    /**
     * 发送普通业务消息
     */
    public void sendBusinessMsg(String msg) {
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        rabbitTemplate.convertAndSend(
                RabbitMQConfig.BUSINESS_EXCHANGE,
                RabbitMQConfig.BUSINESS_ROUTING_KEY,
                msg,
                correlationData
        );
        log.info("发送业务消息: {}", msg);
    }

    /**
     * 发送延迟消息(例如 10s 后进入业务队列)
     */
    public void sendDelayMsg(String msg, long delayMs) {
        CorrelationData data = new CorrelationData(UUID.randomUUID().toString());
        rabbitTemplate.convertAndSend(
                RabbitMQConfig.DELAY_EXCHANGE,
                RabbitMQConfig.DELAY_ROUTING_KEY,
                msg,
                message -> {
                    message.getMessageProperties().setExpiration(String.valueOf(delayMs));
                    return message;
                },
                data
        );
        log.info("发送延迟消息: {} 延迟: {}ms", msg, delayMs);
    }

    // ============ 生产者确认 Confirm ============
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData != null ? correlationData.getId() : "";
        if (ack) {
            log.info("消息已到达交换机: id={}", id);
        } else {
            log.error("消息未到达交换机: id={}, cause={}", id, cause);
            // 可存入DB,定时任务重发
        }
    }

    // ============ 消息无法路由 Return ============
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText,
                                String exchange, String routingKey) {
        log.error("消息无法路由: exchange={}, rk={}", exchange, routingKey);
        // 处理路由失败:记录日志/补偿
    }
}

五、消费者(手动 ACK + 重试 + 死信 + 幂等

核心逻辑:

  • 手动 ACK
  • 业务异常:NACK 并且不重新入队 → 进入死信
  • 消费成功:basicAck
  • 消费失败:basicNack
  • 幂等:用 Redis/DB 唯一 ID 防重复
java 复制代码
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;

import java.nio.charset.StandardCharsets;

@Slf4j
@Component
public class ReliableConsumer {

    // ==================== 业务队列消费 ====================
    @RabbitListener(queues = RabbitMQConfig.BUSINESS_QUEUE)
    public void businessConsumer(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        String msg = new String(message.getBody(), StandardCharsets.UTF_8);
        String msgId = message.getMessageProperties().getMessageId();

        try {
            // 1. 幂等判断(Redis 分布式锁)
            // Boolean lock = redisTemplate.opsForValue().setIfAbsent("mq:msg:" + msgId, "1", 10, TimeUnit.MINUTES);
            // if (lock == null || !lock) {
            //     channel.basicAck(deliveryTag, false);
            //     return;
            // }

            // 2. 业务处理
            log.info("开始消费业务消息: {}", msg);

            // 3. 成功:确认消息
            channel.basicAck(deliveryTag, false);
            log.info("消费成功 ACK");

        } catch (Exception e) {
            log.error("消费异常: {}", e.getMessage());
            // 异常:拒绝消息,不重新入队 → 进入死信
            channel.basicNack(deliveryTag, false, false);
            // 如果想重试:channel.basicNack(deliveryTag, false, true);
        }
    }

    // ==================== 死信队列消费(人工/自动修复) ====================
    /**
     * 死信队列消费(失败3次以上的消息都来这里)
     * 用于:告警、人工处理、定时重刷
     */
    @RabbitListener(queues = RabbitMQConfig.DLX_QUEUE)
    public void dlxConsumer(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        String msg = new String(message.getBody(), StandardCharsets.UTF_8);
        log.error("死信队列收到失败消息: {}", msg);

        // 处理策略:
        // 1. 记录DB
        // 2. 告警
        // 3. 人工修复后重发
        channel.basicAck(deliveryTag, false);
    }
}

六、整套方案保证了什么?

1. 消息绝对不丢失(全链路可靠)

  • 生产者:Confirm + Return 确保消息到交换机
  • 队列:持久化 durable
  • 消息:默认持久化
  • 消费者:手动 ACK,处理完才确认

2. 消息不重复消费(幂等)

  • 消息唯一 msgId
  • Redis 分布式锁 / DB 唯一索引
  • 消费前判断是否已处理

3. 失败消息不阻塞、不雪崩

  • 异常直接进入死信队列
  • 不会无限重试打崩服务
  • 可监控死信,人工 / 自动重试

4. 延迟消息(订单超时、支付超时)

  • TTL + DLX 实现标准延迟队列
  • 不需要插件

5. 流量控制

  • prefetch: 1 消费者限流
  • 防止消息瞬间打满内存

七、直接复制就能用的测试接口

java 复制代码
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class TestController {

    @Resource
    private ReliableProducer producer;

    @GetMapping("/send")
    public String send() {
        producer.sendBusinessMsg("这是一条可靠消息");
        return "发送成功";
    }

    @GetMapping("/delay")
    public String delay() {
        producer.sendDelayMsg("这是10秒后执行的延迟消息", 10000);
        return "延迟发送成功";
    }
}

八、MQ 死信消息自动重试策略

Spring Boot RabbitMQ 标准方案:自动重试 3 次 → 失败进入死信队列无无限循环、无阻塞、可直接复制上线。

核心机制:

  • Spring 本地重试(Retry) :只在消费者内存重试,不回 MQ
  • 重试失败 → 抛出异常 → 消息进入死信队列(DLX)
  • 不会重新入队、不会无限循环、不会爆 CPU

一、application.yml 固定配置

java 复制代码
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /

    # 生产者确认(可选但推荐)
    publisher-confirm-type: correlated
    publisher-returns: true
    template:
      mandatory: true

    listener:
      simple:
        acknowledge-mode: auto      # 重试策略必须用 auto!!
        prefetch: 1                 # 限流1条,保证稳定
        retry:
          enabled: true             # 开启消费者本地重试
          max-attempts: 3          # 最大重试 3 次(1正常+2重试)
          initial-interval: 1000   # 第一次重试间隔 1s
          multiplier: 2            # 间隔倍数 1s → 2s →4s
          stateless: true          # 无状态,必须true

二、队列配置(业务队列绑定死信)直接复制

java 复制代码
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitRetryAndDlxConfig {

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

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

    // ============== 业务队列(绑定死信)==============
    @Bean
    public Queue businessQueue() {
        return QueueBuilder.durable(BUSINESS_QUEUE)
                .deadLetterExchange(DLX_EXCHANGE)
                .deadLetterRoutingKey(DLX_ROUTING_KEY)
                .build();
    }

    @Bean
    public DirectExchange businessExchange() {
        return new DirectExchange(BUSINESS_EXCHANGE);
    }

    @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 new DirectExchange(DLX_EXCHANGE);
    }

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

三、消费者(自动重试 3 次,失败进死信

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class RetryConsumer {

    /**
     * 业务队列消费
     * 规则:
     * 1. 抛异常 → Spring自动重试3次
     * 2. 3次都失败 → 自动进入死信队列
     */
    @RabbitListener(queues = RabbitRetryAndDlxConfig.BUSINESS_QUEUE)
    public void businessConsume(String msg) {
        log.info("开始消费消息:{}", msg);

        try {
            // 你的业务逻辑
            // int i = 1 / 0; // 模拟异常

        } catch (Exception e) {
            log.error("消费失败,准备重试,异常:{}", e.getMessage());
            // 只需要抛出异常,Spring 会自动重试,重试完自动进死信
            throw e;
        }
    }

    /**
     * 死信队列消费(失败3次以上的消息都来这里)
     * 用于:告警、人工处理、定时重刷
     */
    @RabbitListener(queues = RabbitRetryAndDlxConfig.DLX_QUEUE)
    public void dlxConsume(String msg) {
        log.error("【死信队列】消费失败3次以上,请处理:{}", msg);
        // 1. 存入DB
        // 2. 发送告警(钉钉/企业微信)
        // 3. 人工修复
    }
}
  • 第一次消费失败 → 抛异常
  • 间隔 1s 重试第 2 次
  • 间隔 2s 重试第 3 次
  • 总共 3 次都失败
  • Spring 拒绝该消息 → 自动进入死信队列
  • 不会回队列、不会循环、不占 CPU、不阻塞
相关推荐
蜡台2 小时前
JetBrains IDEA 安装 卸载相关总结
java·ide·intellij-idea·注册码
WJLSH1232 小时前
TomCat
java·tomcat
戮戮2 小时前
Spring Cloud Gateway 零拷贝参数校验:一种高性能网关架构实践
java·网络·架构·gateway
alengan3 小时前
cocos自动编译-Android自动出apk包
java·eclipse
漫霂3 小时前
二叉树的统一迭代遍历
java·算法
文静小土豆3 小时前
K8s 滚动更新在 Java 应用中的实践与优化
java·容器·kubernetes
HSunR3 小时前
java springboot3 后端 基础框架
java·开发语言
七夜zippoe3 小时前
Java技术未来展望:GraalVM、Quarkus、Helidon等新趋势探讨
java·开发语言·python·quarkus·graaivm·helidon
枫叶落雨2223 小时前
ClassPathXmlApplicationContext
java·开发语言