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 确认,服务器才会删除消息,避免丢失。

相关推荐
zopple4 小时前
常见的 Spring 项目目录结构
java·后端·spring
cjy0001116 小时前
springboot的 nacos 配置获取不到导致启动失败及日志不输出问题
java·spring boot·后端
小江的记录本7 小时前
【事务】Spring Framework核心——事务管理:ACID特性、隔离级别、传播行为、@Transactional底层原理、失效场景
java·数据库·分布式·后端·sql·spring·面试
sheji34167 小时前
【开题答辩全过程】以 基于springboot的校园失物招领系统为例,包含答辩的问题和答案
java·spring boot·后端
程序员cxuan7 小时前
人麻了,谁把我 ssh 干没了
人工智能·后端·程序员
wuyikeer8 小时前
Spring Framework 中文官方文档
java·后端·spring
Victor3569 小时前
MongoDB(61)如何避免大文档带来的性能问题?
后端
Victor3569 小时前
MongoDB(62)如何避免锁定问题?
后端
wuyikeer9 小时前
Spring BOOT 启动参数
java·spring boot·后端
子木HAPPY阳VIP10 小时前
Ubuntu 22.04 VMware 设置固定IP配置
人工智能·后端·目标检测·机器学习·目标跟踪