springboot整合RabbitMQ 消费者消息接收确认

一,消息接收确认

1.ACK机制:消息确认机制

1.作用:

  • 确认消息是否被消费者消费,消息通过ACK机制确认是否被正确接收,每个消息都要被确认。
  • 默认情况下,一个消息被消费者正确消费就会从队列中移除

2.ACK确认模式

  • AcknowledgeMode.NONE :不确认
    1. 默认所有消息消费成功,会不断的向消费者推送消息
    2. 因为rabbitMq认为所有消息都被消费成功,所以队列中不在存有消息,消息存在丢失的危险
  • AcknowledgeMode.AUTO:自动确认
    1. 由spring-rabbit依据消息处理逻辑是否抛出异常自动发送ack(无异常)或nack(异常)到server端。 存在丢失消息的可能,如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息
    如果消息已经被处理,但后续代码抛出异常,使用 Spring 进行管理的话消费端业务逻辑会进行回滚,这也同样造成了实际意义的消息丢失
    2. 使用自动确认模式时,需要考虑的另一件事是消费者过载
  • AcknowledgeMode.MANUAL:手动确认
    1. 手动确认则当消费者调用 ack、nack、reject 几种方法进行确认,手动确认可以在业务失败后进行一些操作,如果消息未被 ACK 则会发送到下一个消费者
    2. 手动确认模式可以使用 prefetch,限制通道上未完成的(“正在进行中的”)发送的数量

3. 忘记ACK确认
忘记通过basicAck返回确认信息是常见的错误。这个错误非常严重,将导致消费者客户端退出或者关闭后,消息会被退回RabbitMQ服务器,这会使RabbitMQ服务器内存爆满,而且RabbitMQ也不会主动删除这些被退回的消息。只要程序还在运行,没确认的消息就一直是 Unacked 状态,无法被 RabbitMQ 重新投递。更厉害的是,RabbitMQ 消息消费并没有超时机制,也就是说,程序不重启,消息就永远是 Unacked 状态。处理运维事件时不要忘了这些 Unacked 状态的消息。当程序关闭时(实际只要 消费者 关闭就行),消息会恢复为 Ready 状态。

2.局部消息确认

开启手动ack确认

#消息确认机制 --- 是否开启手ack动确认模式
spring.rabbitmq.listener.direct.acknowledge-mode=manual
#消息确认机制 --- 是否开启手ack动确认模式
spring.rabbitmq.listener.simple.acknowledge-mode=manual

配置消费者

@RabbitHandler
    @RabbitListener(queues = DirectKeyInterface.DIRECT_QUEUE_NAME)
    public void receiveObjectDel(Channel channel, String json, Message message,@Headers Map<String,Object> map){

        log.info("接收到的id:"+json);
        int id = Integer.parseInt(json);
        registerDao.deleteUser(id);

        //<P>代码为在消费者中开启消息接收确认的手动ack</p>
        //<H>配置完成</H>
        //<P>可以开启全局配置</p>
        if (map.get("error")!= null){
            log.info("错误的消息");
            try {
                //否认消息,拒接该消息重回队列
                channel.basicNack((Long)map.get(AmqpHeaders.DELIVERY_TAG),false,false);
                return;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //手动ACK
        //默认情况下如果一个消息被消费者所正确接收则会被从队列中移除
        //如果一个队列没被任何消费者订阅,那么这个队列中的消息会被 Cache(缓存),
        //当有消费者订阅时则会立即发送,当消息被消费者正确接收时,就会被从队列中移除
        try {
            //手动ack应答
            //告诉服务器收到这条消息 已经被我消费了 可以在队列删掉 这样以后就不会再发了
            // 否则消息服务器以为这条消息没处理掉 后续还会在发,true确认所有消费者获得的消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
            log.info("消息消费成功:id:{}",message.getMessageProperties().getDeliveryTag());
        } catch (IOException e) {
            e.printStackTrace();
            //丢弃这条消息
            try {
                //最后一个参数是:是否重回队列
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
                //拒绝消息
                //channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
                //消息被丢失
                //channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
                //消息被重新发送
                //channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
                //多条消息被重新发送
                //channel.basicNack(message.getMessageProperties().getDeliveryTag(), true, true);
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            log.info("消息消费失败:id:{}",message.getMessageProperties().getDeliveryTag());
        }
    }
3.全局消息确认
    /**
     * 消费者全局消息手动ACK确认(还没配置完成)
     * @return
     */
    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(){

        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory());
        //监听的队列(是一个String类型的可变参数,将监听的队列配置上来,可减少在消费者中代码量)
        container.setQueueNames(DirectKeyInterface.DIRECT_QUEUE_NAME);
        //手动确认
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        container.setMessageListener(channelAwareMessageListener());
    
        //消息处理
        container.setMessageListener((ChannelAwareMessageListener) (message, channel) -> {
            log.info("====接收到消息=====");
            log.info(new String(message.getBody()));
            //它会根据方法的执行情况来决定是否确认还是拒绝(是否重新入queue)
                //1.抛出NullPointerException异常则重新入队列
                    //throw new NullPointerException("消息消费失败");
                //2.当抛出的异常是AmqpRejectAndDontRequeueException异常的时候,则消息会被拒绝,且requeue=false
                    //throw new AmqpRejectAndDontRequeueException("消息消费失败");
                //3.当抛出ImmediateAcknowledgeAmqpException异常,则消费者会被确认
                    //throw new ImmediateAcknowledgeAmqpException("消息消费失败");
            //消息手动弄ACK确认
            if(message.getMessageProperties().getHeaders().get("error") == null){

                try {
                    //手动ack应答
                    //告诉服务器收到这条消息 已经被我消费了 可以在队列删掉 这样以后就不会再发了
                    // 否则消息服务器以为这条消息没处理掉 后续还会在发,true确认所有消费者获得的消息
                    channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
                    log.info("消息消费成功:id:{}",message.getMessageProperties().getDeliveryTag());
                } catch (IOException e) {
                    e.printStackTrace();
                    //丢弃这条消息
                    try {
                        //最后一个参数是:是否重回队列
                        channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,true);
                        //拒绝消息
                        //channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
                        //消息被丢失
                        //channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
                        //消息被重新发送
                        //channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
                        //多条消息被重新发送
                        //channel.basicNack(message.getMessageProperties().getDeliveryTag(), true, true);
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                    log.info("消息消费失败:id:{}",message.getMessageProperties().getDeliveryTag());
                }
            }else {
                //处理错误消息,拒觉错误消息重新入队
                channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
                channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
                log.info("消息拒绝");
            }
        });

        return container;
    }
  • 7
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Spring Boot 是一个方便快捷的轻量级框架,提供了许多便利的功能和特性,其中就包括了 RabbitMQ整合功能。 RabbitMQ 是一个广泛使用的消息队列系统,具有高可靠性和可扩展性,能够在分布式系统中实现异步通信、解耦和任务调度等功能,因此对于分布式系统而言具备非常重要的价值。 在Spring Boot中,整合RabbitMQ 可以通过以下步骤完成: 1. 添加 RabbitMQ 的相关依赖:在 pom.xml 文件中添加 spring-boot-starter-amqp 依赖。 2. 配置 RabbitMQ 相关信息:通过 application.yml 或 application.properties 配置文件配置RabbitMQ 的基本信息,例如:连接地址、用户名、密码等。 3. 创建 RabbitMQ 模板:通过 RabbitTemplate 类提供的方法向 RabbitMQ 发送消息接收响应信息。 4. 创建 Exchange 和 Queue:在 RabbitMQ 中创建 Exchange 和 Queue,Exchange 用于将消息路由到指定的 Queue 中。 5. 编写发送消息的代码:通过 RabbitTemplate 提供的方法send()发送消息,可以是简单字符串/对象/json等。 至此,我们已经实现了一个简单的 RabbitMQ 消息发送程序。但是在实际应用场景中,还需要具备更进一步完善的功能,例如:消息确认消息持久化、消费者监听等功能,这些可以通过配置listener容器、消息确认机制以及使用DurableQueue,DurableExchange等参数来实现。 总之,RabbitMQ’s and Spring Boot的集成非常方便和快捷。通过简单的配置和实现,我们就可以使用 RabbitMQ 在分布式系统中实现异步通信、解决问题、任务调度等功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值