Springboot 集成 RabbitMQ —— 消息确认机制

上一篇文章介绍了 Springboot 如何集成 RabbitMQ,同时也形成了最基本的实现,不过这个实现好像有一点点问题,记得还在菊厂培训的时候,有一篇培训 PPT 讲过,极其健壮的服务需要达到一年下来,平均只有一到两个小时不能提供服务,反过来就是说,程序可能会在这一两个小时中宕机,宕机意味着数据丢失。就例如我们讲到的 RabbitMQ,假如我们在处理某一条数据的过程中宕机了,程序没有完成处理过程不会正常返回,同时 RabbitMQ 上这条消息因为已经发送给消费者,所以不会保留,那么这条未处理完的数据是不是就丢失了。RabbitMQ 给了我们一个保护机制,手动回复 ack,上一篇文章使用了默认的自动 ack 就无法避免这个问题。

注:后面代码都是对上一篇文章的程序小小改动后测试的

修改手动回复 ack 配置文件

增加开启手动回复的配置
在这里插入图片描述
修改的配置信息如下

#rabbitmq相关配置
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
# 开启发送失败退回
spring.rabbitmq.publisher-returns=true
# 开启 ACK,必须开启,否则回调没有通道信息
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.direct.acknowledge-mode=manual

也可以手动设置

生产者

生产者调整为以 Message 的形式发送送至队列

@Component
public class HoshMQSender implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback{

    private RabbitTemplate rabbitTemplate;

    @Autowired
    public HoshMQSender(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
        this.rabbitTemplate.setConfirmCallback(this);
        this.rabbitTemplate.setReturnCallback(this);
    }

    public void send(String str) {
        sendMessageWithAck(str);
    }

    private void sendMessageWithAck(String str) {
        // 消息内容
        byte[] toSendBytes = str.getBytes();
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setMessageId(String.valueOf(System.currentTimeMillis()));
        Message msg = new Message(toSendBytes, messageProperties);
        CorrelationData correlationData = new CorrelationData();
        correlationData.setId(UUID.randomUUID().toString());
        rabbitTemplate.convertAndSend(Constant.HOSH_TOPIC_EXC, Constant.HOSH_TOPIC, msg, correlationData);
    }

    // 队列内容发送到 MQ 的确认
    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
//        if (b) {
//            System.out.println("recv HoshMQSender confirm id=" + correlationData.getId());
//        } else {
//            System.out.println("not recv " + s);
//        }
    }

    /**
     * exchange 到达 queue, 则 returnedMessage 不回调
     * exchange 到达 queue 失败, 则 returnedMessage 回调
     * 需要设置spring.rabbitmq.publisher-returns=true
     * @param message
     * @param replyCode
     * @param replyText
     * @param exchange
     * @param routingKey
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        System.out.println("sender return success" + message.toString()
                +"\n replyCode "+replyCode+"\n replyText "+replyText
                +"\n exchange "+exchange + "\n routingKey "+routingKey);
    }
}

消费者

为了验证手动 ack 的效果,消费者会有回复 ack 和不回复 ack 的对比

1、不回复 ack 的消费者代码

@Component
public class HoshMqReceiver2 {

    @RabbitHandler
    @RabbitListener(queues = Constant.HOSH_TOPIC)
    public void onReceiver(String info, Channel channel, Message msg) {
//        try {
//            // 开启手动应答 ack 以后,只有当程序明确回复,数据已经被处理,
//            // 对应数据才会被 RabbitMQ server 清除,否则保留在 RabbitMQ server 上
//            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
//            System.out.println("HoshMqReceiver msg " + JSON.toJSONString(msg));
            System.out.println("HoshMqReceiver info " + info);
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
    }
}

测试一下向队列发送消息
在这里插入图片描述
可以看到我们还是受到了消息

2、回复 ack 的消费者代码块

@Component
public class HoshMqReceiver2 {

    @RabbitHandler
    @RabbitListener(queues = Constant.HOSH_TOPIC)
    public void onReceiver(String info, Channel channel, Message msg) {
        try {
            // 开启手动应答 ack 以后,只有当程序明确回复,数据已经被处理,
            // 对应数据才会被 RabbitMQ server 清除,否则保留在 RabbitMQ server 上
            channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
            System.out.println("HoshMqReceiver msg " + JSON.toJSONString(msg));
            System.out.println("HoshMqReceiver info " + info);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

再看一下测试结果,这次,我们只是直接运行了程序,还并没有向队列发送消息
在这里插入图片描述
大家应该发现了,如果消费者没有为对应的消息回复 ack,那么这条消息依旧会保存在队列里,直到收到了消费者对该条消息的 ack 后,才会从队列移除

发布了23 篇原创文章 · 获赞 22 · 访问量 3880

猜你喜欢

转载自blog.csdn.net/qq_19154605/article/details/103703885