【RabbitMq 篇六】-消息确认(发送确认与接收确认)

前言

消息确认是保证消息传递可靠性的重要步骤,上一节我们说到持久化,持久化只能保证消息不丢失,但是如果消息如果投递失败我们怎么进行补偿操作呢?解决办法就是实现回调函数进行操作,在消息的发送和消息的消费都可以进行补偿操作,下面我们就要讲解消息确认。

 

 

正文

目录

前言

正文

消息确认种类

 

消息发送确认

ConfirmCallback

ReturnCallback

消息消费确认

 


消息确认种类

消息的确认做有很多法,其中包括事务机制、批量确认、异步确认等。

事务机制:我们在channel对象中可以看到 txSelect(),txCommit(),txrollback() 这些方法,分别对应着开启事务,提交事务,回滚。由于使用事务会造成生产者与Broker交互次数增加,造成性能资源的浪费,而且事务机制是阻塞的,在发送一条消息后需要等待RabbitMq回应,之后才能发送下一条,因此事务机制不提倡,大家在网上也很少看到RabbitMq使用事务进行消息确认的。

批量确认:批量其实是一个节约资源的操作,但是在RabbitMq中我们使用批量操作会造成消息重复消费,原因是批量操作是使客户端程序定期或者消息达到一定量,来调用方法等待Broker返回,这样其实是一个提高效率的做法,但是如果出现消息重发的情况,当前这批次的消息都需要重发,这就造成了重复消费,因此批量确认的操作性能没有提高反而下降。

异步确认:异步确认虽然编程逻辑比上两个要复杂,但是性价比最高,无论是可靠性还是效率都没得说,他是利用回调函数来达到消息可靠性传递的,笔者接触过RocketMq,这个中间件也是通过函数回调来保证是否投递成功,下面就让我们来详细讲解异步确认是怎么实现的。

 

请看一下RabbitMq工作原理图

每一个颜色块之间都存在着消息的确认机制,我们大概分为两大类,发送方确认和接收方确认,其中发送方确认又分为生产者到交换器到确认和交换器到队列的确认。

 

消息发送确认

ConfirmCallback

ConfirmCallback是一个回调接口,消息发送到 Broker 后触发回调,确认消息是否到达 Broker 服务器,也就是只确认是否正确到达 Exchange 中。

我们需要在生产者的配置中添加下面配置,表示开启发布者确认

 

spring.rabbitmq.publisher-confirms=true

 

然后在生产者的Java配置类实现该接口

 

@Component
public class RabbitTemplateConfig implements RabbitTemplate.ConfirmCallback{
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void initRabbitTemplate() {
        // 设置生产者消息确认
        rabbitTemplate.setConfirmCallback(this);
        
    }

    /**
     * 消息发送到 Broker 后触发回调,确认消息是否到达 Broker 服务器,也就是只确认是否正确到达 Exchange 中
     *
     * @param correlationData
     * @param b
     * @param s
     */
    @Override
    public void confirm(@Nullable CorrelationData correlationData, boolean b, @Nullable String s) {
        System.out.println("ack:[{}]" + b);
        if (b) {
            System.out.println("消息到达rabbitmq服务器");
        } else {
            System.out.println("消息可能未到达rabbitmq服务器");
        }
    }

 

 

ReturnCallback

通过实现 ReturnCallback 接口,启动消息失败返回,此接口是在交换器路由不到队列时触发回调,该方法可以不使用,因为交换器和队列是在代码里绑定的,如果消息成功投递到Broker后几乎不存在绑定队列失败,除非你代码写错了。

使用此接口需要在生产者配置中加入一下配置,表示发布者返回

spring.rabbitmq.publisher-returns=true

 

然后基于刚才的生产者Java配置里实现接口ReturnCallback

@Component
public class RabbitTemplateConfig implements  RabbitTemplate.ReturnCallback {
    @Autowired
    private RabbitTemplate rabbitTemplate;

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

    /**
     * 启动消息失败返回,比如路由不到队列时触发回调
     *
     * @param message
     * @param i
     * @param s
     * @param s1
     * @param s2
     */
    @Override
    public void returnedMessage(Message message, int i, String s, String s1, String s2) {
        System.out.println("消息主体 message : " + message);
        System.out.println("消息主体 replyCode : " + i);
        System.out.println("描述 replyText:" + s);
        System.out.println("消息使用的交换器 exchange : " + s1);
        System.out.println("消息使用的路由键 routing : " + s2);
    }
}

 

以上两段Java配置可以写在一个类里。

到此,我们完成了生产者的异步确认,我们可以在回调函数中对当前失败的消息进行补偿,这样保证了我们没有发送成功的数据也被观察到了,比如某某条数据需要发送到消费者消费,但是没有发送成功,这就需要你在此做一些其他操作喽,根据你具体业务来。

 

 

 

消息消费确认

 

消费者确认发生在监听队列的消费者处理业务失败,如,发生了异常,不符合要求的数据……,这些场景我们就需要手动处理,比如重新发送或者丢弃。

 

 

我们知道ACK是默认是自动的,自动确认会在消息发送给消费者后立即确认,但存在丢失消息的可能,如果消费端消费逻辑抛出异常,加入你用回滚了也只是保证了数据的一致性,但是消息还是丢了,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息。

消息确认模式有:

  • AcknowledgeMode.NONE:自动确认
  • AcknowledgeMode.AUTO:根据情况确认
  • AcknowledgeMode.MANUAL:手动确认

 

长话短说……

需要在消费者的配置里加手动 ack(确认)则需要修改确认模式为 manual,手动确认的方式有很多,可以在RabbitListenerContainerFactory类进行设置。

 

spring.rabbitmq.listener.direct.acknowledge-mode=MANUAL

 

消费者类

@Service
public class AsyncConfirmConsumer {
    @RabbitListener(queues = "confirm_queue")
    @RabbitHandler
    public void asyncConfirm(Order order, Message message, Channel channel) throws IOException {

        try {
            System.out.println("消费消息:" + order.getName());
//            int a = 1 / 0;
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
            System.out.println("消费消息确认" + message.getMessageProperties().getConsumerQueue() + ",接收到了回调方法");
        } catch (Exception e) {
            //重新回到队列
//            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
//            System.out.println("尝试重发:" + message.getMessageProperties().getConsumerQueue());
            //requeue =true 重回队列,false 丢弃
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            // TODO 该消息已经导致异常,重发无意义,自己实现补偿机制


        }


    }
}

 

需要注意的 basicAck 方法需要传递两个参数

  • deliveryTag(唯一标识 ID):当一个消费者向 RabbitMQ 注册后,会建立起一个 Channel ,RabbitMQ 会用 basic.deliver 方法向消费者推送消息,这个方法携带了一个 delivery tag, 它代表了 RabbitMQ 向该 Channel 投递的这条消息的唯一标识 ID,是一个单调递增的正整数,delivery tag 的范围仅限于 Channel
  • multiple:为了减少网络流量,手动确认可以被批处理,当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息

 

basicNack方法需要传递三个参数

  • deliveryTag(唯一标识 ID):上面已经解释了。
  • multiple:上面已经解释了。
  • requeue: true :重回队列,false :丢弃,我们在nack方法中必须设置 false,否则重发没有意义。

 

basicReject方法需要传递两个参数

  • deliveryTag(唯一标识 ID):上面已经解释了。
  • requeue:上面已经解释了,在reject方法里必须设置true。

 

还要说明一下,建议大家不要重发,重发后基本还是失败,因为出现问题一般都是异常导致的,出现异常的话,我的观点是丢弃这个消息,然后在catch里做补偿操作。

 

到此,我们都已经准备好了,可以进行测试,我把剩余相关代码都写在一起了。

 

@RestController
public class AsyncConfirmController {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/async/{id}")
    public String AETest(@PathVariable Integer id) {
        Order order = new Order(id, "胖虎");
        rabbitTemplate.convertAndSend("confirm_exchange", "", order);
        return "成功";
    }

---------------------------------

@Configuration
public class AsyncConfirmListener {

    @Bean
    public Queue confirmQueue() {
        return new Queue("confirm_queue");
    }

    @Bean
    public FanoutExchange confirmExchange() {
        return new FanoutExchange("confirm_exchange");
    }

    //交换器绑定队列
    @Bean
    Binding bindingExchangeConfirm(Queue confirmQueue, FanoutExchange confirmExchange) {
        return BindingBuilder.bind(confirmQueue).to(confirmExchange);
    }
}

 

我们可以自己下载项目进行测试,这里就不贴运行结果了。

github地址:https://github.com/362460453/rabbitMQ-demo

 

 

 

 

 

 

 

  • 13
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
RabbitMQ 中,消费者接收消息后,需要发送 ACK 确认接收,以告知 RabbitMQ消息已经被正确地接收并处理。如果消费者接收消息后没有发送 ACK 确认接收,那么 RabbitMQ 将会认为该消息没有被正确地处理,会重新将消息发送给其他消费者。 在 RabbitMQ 中,发送 ACK 确认接收的方式有两种: 1. 自动确认模式 在自动确认模式下,当消费者接收消息后,RabbitMQ 会自动发送 ACK 确认接收,不需要手动发送 ACK。这种模式下,如果消息处理失败,那么消息就会被丢弃,因此,只有在消息处理相对简单、不需要进行复杂的错误处理时,才适合使用自动确认模式。 2. 手动确认模式 在手动确认模式下,当消费者接收消息后,需要手动发送 ACK 确认接收。如果消息处理失败,可以发送 NACK 拒绝接收,然后重新将消息发送给其他消费者。手动确认模式可以保证消息可靠性和一致性,但需要消费者手动发送 ACK、NACK 等命令,因此比较复杂。 在 RabbitMQ 的 Java 客户端中,可以使用 channel.basicAck() 方法手动发送 ACK 确认接收,使用 channel.basicNack() 方法发送 NACK 拒绝接收。例如,以下代码演示了如何手动发送 ACK 确认接收: ```java channel.basicConsume(queueName, false, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { // 处理消息 // ... // 手动发送 ACK 确认接收 channel.basicAck(envelope.getDeliveryTag(), false); } }); ``` 在上述代码中,第二个参数设置为 false,表示关闭自动确认模式,需要手动发送 ACK 确认接收。当消息处理完成后,调用 channel.basicAck() 方法发送 ACK 确认接收。这样可以保证消息被正确地处理,并且可以避免消息丢失的情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值