RabbitMQ的消息安全性

情况1:消息没有发送到交换机,或没有发送到消息队列

方案: 消息没有发送到消息队列,我们可以在生产者端进行确认,也就是分别针对交换机和队列来确认。如果没有成功发送到消息队列服务器上,那就可以尝试重新发送。

  • 开启确认模式 :根据服务器返回的 ack 值判断是否重发。
  • 开启回退模式:消息路由失败时,服务器退回消息,生产者可决定是否重发。

开启确认配置

yaml 复制代码
logging:
  level:
    com.pluto.order.config.MQProducerAckConfig: info
​
# 配置数据库的连接信息
spring:
  rabbitmq:
    host:
    port:
    username: 
    password: 
    virtual-host: /
    publisher-confirm-type: CORRELATED # 交换机的确认
    publisher-returns: true # 队列的确认(队列没收到才会返回)

添加生产者配置类 MQProducerAckConfig

typescript 复制代码
/**
 * @author Pluto
 * @date 2026/2/12 11:06
 * @description: 生产者配置
 */
@Component
@Slf4j
public class MQProducerAckConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback{

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init() {
        // 注册到模板上去
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnsCallback(this);
    }

    // 确认回调 :交换机收没收到确定都会返回ack确认
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (ack) {
            log.info("消息发送到交换机成功!关联数据:" + correlationData);
        } else {
            log.info("消息发送到交换机失败!关联数据:" + correlationData + " 原因:" + cause);
        }
    }

    // 返回回调 :仅仅在消息没有到队列,返回给生产者
    @Override
    public void returnedMessage(ReturnedMessage returned) {
        log.info("消息没有到队列");
        log.info("消息主体: " + new String(returned.getMessage().getBody()));
        log.info("应答码: " + returned.getReplyCode());
        log.info("描述:" + returned.getReplyText());
        log.info("消息使用的交换器 exchange : " + returned.getExchange());
        log.info("消息使用的路由键 routing : " + returned.getRoutingKey());
    }
}

@PostConstruct注解是Java中的一个标准注解,它用于指定在对象创建之后立即执行的方法。当使用依赖注入(如Spring框架)或者其他方式创建对象时,@PostConstruct注解可以确保在对象完全初始化之后,执行相应的方法。

使用@PostConstruct注解的方法必须满足以下条件:

  1. 方法不能有任何参数。
  2. 方法必须是非静态的。
  3. 方法不能返回任何值。

当容器实例化一个带有@PostConstruct注解的Bean时,它会在调用构造函数之后,并在依赖注入完成之前调用被@PostConstruct注解标记的方法。这样,我们可以在该方法中进行一些初始化操作,比如读取配置文件、建立数据库连接等。

情况2:消息成功存入队列,但是消息队列服务器宕机,消息丢失

方案: 消息持久化到硬盘上,哪怕服务器重启也不会导致消息丢失,也就是我们需要让交换机和队列都是持久化的,这样消息就不会丢失。

这里只要在创建的时候设置持久化即可。 可以通过管理面板创建队列或者交换机的时候,设置持久化。 也可以在代码中创建交换机或者队列的时候设置持久化,以下是在修饰监听方法@RabbitListener设置的持久化。

less 复制代码
public static final String EXCHANGE_DIRECT = "exchange.direct.pluto";
public static final String ROUTING_KEY = "pluto";
public static final String QUEUE_NAME  = "queue.pluto";

// 修饰监听方法
@RabbitListener(
        // 设置绑定关系
        bindings = @QueueBinding(

                // 配置队列信息:durable 为 true 表示队列持久化;autoDelete 为 false 表示关闭自动删除
                value = @Queue(value = QUEUE_NAME, durable = "false", autoDelete = "true"),

                // 配置交换机信息:durable 为 true 表示队列持久化;autoDelete 为 false 表示关闭自动删除
                exchange = @Exchange(value = EXCHANGE_DIRECT, durable = "false", autoDelete = "true"),

                // 配置路由键信息
                key = {ROUTING_KEY}
        ))
public void processMessage(String msg, Message message, Channel channel) {
    System.out.println("messageContent = " + msg);
}

durable(持久化)

  • durable = true:队列/交换机的元数据会持久化到磁盘
  • durable = false:队列/交换机的元数据只存在内存中

autoDelete(自动删除)

  • autoDelete = true:当最后一个消费者断开连接时,队列自动删除
  • autoDelete = false:队列会一直存在,即使没有消费者

情况3:消息成功存入消息队列,但是消费端出现问题

方案: 消费端异常导致消息没有成功被消费成功。默认情况下,消费端取回消息后,默认会自动返回ACK确认消息,所以还是要修改成手动确认。

yaml 复制代码
spring:
  rabbitmq:
    host: 
    port: 
    username: 
    password: 
    virtual-host: /
    listener:
      simple:
        acknowledge-mode: manual # 把消息确认模式改为手动确认

修改监听

java 复制代码
 //监听之后 手动通过唯一标识确认收到消息
    @RabbitListener(queues = {"code_queue"})
    public void processMessageManual(String msg, Message message, Channel channel) throws IOException {
        //消息唯一标识
        long deliveryTag = message.getMessageProperties().getDeliveryTag();;
        log.info("messageContent = {}", msg);
        try {
            int i = 1 / 0;
            channel.basicAck(deliveryTag, true);
        } catch (Exception e) {
            System.out.println("发生异常,拒绝确认消息");

            // 获取信息,看当前消息是否曾经被投递过
            Boolean redelivered = message.getMessageProperties().getRedelivered();

            if (!redelivered) {
                // 如果没有被投递过,那就重新放回队列,重新投递,再试一次
                channel.basicNack(deliveryTag, false, true);
            } else {
                // 如果已经被投递过,且这一次仍然进入了 catch 块,那么返回拒绝且不再放回队列
                channel.basicReject(deliveryTag, false);
            }

        }
    }

这样处理完消息后,必须手动调用 basicAck 确认,服务器才会删除消息,避免丢失。

相关推荐
踏浪无痕1 小时前
微服务日志与调用链打通方案:一个简单但实用的思路
后端
爱叫啥叫啥2 小时前
ESP32Mini打印机:多状态的LED灯驱动
后端
人道领域2 小时前
SSM框架从入门到入土(RESTful风格)
后端·restful
野犬寒鸦2 小时前
WebSocket协同编辑:高性能Disruptor架构揭秘及项目中的实战应用
java·开发语言·数据库·redis·后端
AntBlack2 小时前
上下求索,2025年我用AI写了哪些东西
后端·ai编程·年终总结
消失的旧时光-19432 小时前
第二十一课:系统是怎么一步步拆坏的?——单体到模块化实践(完整工程版)
java·spring boot·后端·架构
wanderful_2 小时前
自定义用户体系下 Django 业务模块开发踩坑与通用解决方案(技术分享版)
后端·python·django
Coder_Boy_3 小时前
Java高级_资深_架构岗 核心面试知识点(AI整合+混合部署)
java·人工智能·spring boot·后端·面试·架构