RabbitMQ(三)保证消息的可靠性传输【重要】

一、消息投递过程可能存在的问题

准确来说,我们需要保障MQ消息的可靠性,需要从三个层面/维度解决:生产者100%投递、MQ持久化、消费者100%消费,这里的100%消费指的是消息不少消费,也不多消费。 

二、生产者 消息重复发送解决方案

由于生产者发送消息给MQ,在MQ确认的时候出现了网络波动,生产者没有收到确认,实际上MQ已经接收到了消息。这时候生产者就会重新发送一遍这条消息。

三、生产者 消息发送失败解决方案【生产者弄丢了数据(事务、confirm)

产生原因:生产者将数据向RabbitMQ发送时候,可能数据就在半路给搞丢了,因为网络抖动问题或其它都有可能

期待目的:保证生产者的消息能准确到达MQ

解决方案:

方案一:消息确认机制(事务)

设置为transaction事务模式

生产者发送数据之前,将channel设置为事务模式【channel.txSelect】然后发送消息

  • 如果RabbitMQ收到了消息,那么可以提交事务【channel.txCommit】
  • 如果消息没有成功被RabbitMQ接收到,那么生产者会收到异常报错,此时就可以回滚事务【channel.txRollback】,然后可以根据业务重试发送消息或者自己处理特殊逻辑

不过使用事务性能不好,RabbitMQ事务机制是同步操作,一条消息发送之后会使发送端阻塞,以等待RabbitMQ Service的回应,之后才能继续发送下一条消息,生产者生产消息的吞吐量和性能都会大大降低

public class TxSend {
	private static final String QUEUE_NANE="test_queue_Transaction";
	public static void main(String[] args) throws IOException, TimeoutException {
		Connection connection=ConnectionUtils.getConnection();
		Channel channel=connection.createChannel();
		channel.queueDeclare(QUEUE_NANE, false, false, false, null);
		String msg="hello transaction message";
		try {
			//设置为transaction模式
			channel.txSelect();
			channel.basicPublish("", QUEUE_NANE, null, msg.getBytes());
			//提交事务
			channel.txCommit();
			System.out.println("commit");
		} catch (Exception e) {
			//回滚事务
			channel.txRollback();
			System.out.println("rollback");
		}
		channel.close();
		connection.close();
	}
}

方案二:消息确认机制(confirm)【推荐】

设置为confirm模式

生产者发送数据之前,将channel设置为confirm模式【channel.confirmSelect】然后发送消息,结果会通过【channel.waitForConfirms()】监听

  • 如果RabbitMQ收到了消息,那么可以提交事务【channel.txCommit】
  • 如果消息没有成功被RabbitMQ接收到,那么生产者会收到异常报错,此时就可以回滚事务【channel.txRollback】,然后可以根据业务重试发送消息或者自己处理特殊逻辑

在生产者那里设置开启confirm模式之后,你每次写的消息都会分配一个唯一的id,然后如果写入了 RabbitMQ 中,RabbitMQ 会给你回传一个 ack消息,告诉你说这个消息ok了。如果 RabbitMQ 没能处理这个消息,会回调你的一个 nack接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息id的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。

一旦信道进入 confirm 模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,RabbitMQ 就会发送一个确认(Basic.Ack)给生产者(包含消息的唯一 deliveryTag 和 multiple 参数),这就使得生产者知晓消息已经正确到达了目的地了

confirm确认机制有三种类型
1、同步确认
2、批量确认
3、异步确认
同步模式的效率很低,因为每一条消息度都需要等待确认好之后,才能处理下一条;
批量确认模式相比同步模式效率是很高,不过有个致命的缺陷,一旦回复确认失败,当前确认批次的消息会全部重新发送,导致消息重复发送;
异步模式就是个很好的选择了,不会有同步模式的阻塞问题,同时效率也很高,是个不错的选择。

springboot消息发送确认代码实现

1、设置rabbitmq的配置类 

package com.marvin.demo.common.rabbitmq.config;

import com.marvin.demo.common.rabbitmq.MessageCallBack;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

/**
 * @Configuration: 说明是一个配置类
 * @EnableRabbit: 开启基于注解的RabbitMQ模式
 */
@Configuration
@EnableRabbit
public class RabbitMqConfig {

    /**
     * RabbitMQ的使用入口
     *
     * @param connectionFactory : 连接信息
     * @param messageCallBack :自定义回调类
     * @return
     */
    @Bean
    //定义生命周期为prototype
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory, MessageCallBack messageCallBack) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        //指定消息转换器
        rabbitTemplate.setMessageConverter(jsonMessageConverter());

        //confirm确认:当消息发送到broker服务器交换机【exchange】时,该方法被调用.
        rabbitTemplate.setConfirmCallback(messageCallBack);

        //return确认:触发setReturnCallback回调必须设置mandatory=true, 否则Exchange没有找到Queue就会丢弃掉消息, 而不会触发回调
        rabbitTemplate.setMandatory(true);
        //return确认:当消息从交换机(Exchange)路由到队列(Queue)失败时,该方法被调用【若成功,则不调用】
        rabbitTemplate.setReturnCallback(messageCallBack);
        return rabbitTemplate;
    }

    @Bean
    public MessageConverter jsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }


}

2、消息回调实现类 - 实现rabbitMQ 消息回调(自定义处理逻辑)

package com.marvin.demo.common.rabbitmq;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * RabbitMQ 消息回调
 */
@Slf4j
@Component
public class MessageCallBack implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

//    final private MQErrorLogRepository mqErrorLogRepository;
//
//    MessageCallBack(MQErrorLogRepository mqErrorLogRepository) {
//        this.mqErrorLogRepository = mqErrorLogRepository;
//    }
//

    /**
     * 回调函数: confirm确认:当消息发送到broker服务器交换机【exchange】时,该方法被调用.
     * 1.如果消息没有到exchange,则 ack=false
     * 2.如果消息到达exchange,则 ack=true
     *
     * @param correlationData:消息的唯一标识
     * @param ack:确认结果【true:发送成功|false:发送失败】
     * @param cause:失败原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (ack) {
            //投递成功
            log.info("【成功】【confirmCallback】Client消息发送到Exchange ==================== correlationData【{}】, cause: 【{}】", correlationData, cause);
        } else {
            //投递失败
            log.error("【失败】【confirmCallback】Client消息发送到Exchange ==================== correlationData【{}】, cause: 【{}】", correlationData, cause);
            //失败记录写入DB
//            MQErrorLog mqErrorLog = new MQErrorLog();
//            mqErrorLog.setErrorType(MQConstants.ERROR_TYPE_CALL_BACK_CONFIRM);
//            mqErrorLog.setExtraData(correlationData.getId());
//            mqErrorLog.setCreateTime(new Date());
//            mqErrorLogRepository.save(mqErrorLog);
            //(nack)失败则进行具体的后续操作:重试 或者补偿等手段
        }
    }

    /**
     * 回调函数: return确认:当消息从交换机(Exchange)路由到队列(Queue)失败时,该方法被调用【若成功,则不调用】
     * 需要注意的是:该方法调用后,MsgSendConfirmCallBack中的confirm方法也会被调用,且ack = 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) {
        Integer messageLength = message.toString().length();
        if (messageLength < 500) {
            log.error("【失败】【returnCallback】Exchange消息发送到Queue ==================== messge:【{}】replyCode:【{}】replyText:【{}】exchange:【{}】routingKey:【{}】",
                    message, replyCode, replyText, exchange, routingKey);
        }else{
            log.error("【失败】【returnCallback】Exchange消息发送到Queue ==================== replyCode:【{}】replyText:【{}】exchange:【{}】routingKey:【{}】",
                     replyCode, replyText, exchange, routingKey);
        }
        //临时使用交换机名称进行过滤(排除不需要写DB日志的消息)
        if (!exchange.toUpperCase().contains("NOT_WRITE_DB_LOG")) {
            Map<String, Object> extraData = new HashMap<>();
            extraData.put("replyCode", replyCode);
            extraData.put("replyText", replyText);
            extraData.put("exchange", exchange);
            extraData.put("routingKey", routingKey);
            //失败记录写入DB
//            MQErrorLog mqErrorLog = new MQErrorLog();
//            mqErrorLog.setErrorType(MQConstants.ERROR_TYPE_CALL_BACK_RETURN);
//            mqErrorLog.setMessage(message.toString().substring(0, messageLength >= 2000 ? 2000 : messageLength));
//            mqErrorLog.setExtraData(JSON.toJSONString(extraData));
//            mqErrorLog.setCreateTime(new Date());
//            mqErrorLogRepository.save(mqErrorLog);
            //失败则进行具体的后续操作:重试 或者补偿等手段。。。
        }


    }
}

3、springboot启动类引入rabbitmq的配置类 

@SpringBootApplication
//引入配置
@Import({RabbitMqConfig.class})
public class ApplicationAnnotationFull {
    public static void main(String[] args) {
        SpringApplication.run(ApplicationAnnotationFull.class);
    }
}

总结区别:

事务机制和confirm机制最大的不同在于:

  • 事务机制同步的,你提交一个事务之后会阻塞在那儿
  • confirm机制异步的,你发送个消息之后就可以发送下一个消息,然后那个消息RabbitMQ接收了之后会异步回调你的一个接口通知你这个消息接收到了。

所以一般在生产者这块避免数据丢失,都是用confirm机制的。

四、RabbitMQ 出现问题及解决方案

服务中断\停止【RabbitMQ 弄丢了数据(持久化)

就是 RabbitMQ 自己弄丢了数据,这个你必须开启 RabbitMQ 的持久化,就是消息写入之后会持久化到磁盘,哪怕是 RabbitMQ 自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。除非极其罕见的是,RabbitMQ 还没持久化,自己就挂了,可能导致少量数据丢失,但是这个概率较小。

设置持久化有两个步骤:

(1)创建 queue 的时候将其设置为持久化

这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。

(2)第二个是发送消息的时候将消息的 deliveryMode 设置为 2

就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。

必须要同时设置这两个持久化才行,RabbitMQ 哪怕是挂了,再次重启,也会从磁盘上重启恢复 queue,恢复这个 queue 里的数据。

注意,哪怕是你给 RabbitMQ 开启了持久化机制,也有一种可能,就是这个消息写到了 RabbitMQ 中,但是还没来得及持久化到磁盘上,结果不巧,此时 RabbitMQ 挂了,就会导致内存里的一点点数据丢失。

所以,持久化可以跟生产者那边的 confirm 机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者 ack 了,所以哪怕是在持久化到磁盘之前,RabbitMQ 挂了,数据丢了,生产者收不到 ack,你也是可以自己重发的。

五、消费者 消息重复消费解决方案

产生场景:

消费者消费成功后,再给MQ确认的时候出现了网络波动,MQ没有接收到确认,为了保证消息被消费,MQ就会继续给消费者投递之前的消息。这时候消费者就接收到了两条一样的消息。 

解决方案:

让每个消息携带一个全局的唯一ID,即可保证消息的幂等性,具体消费过程为:

消费者获取到消息后先根据id去查询redis/db是否存在该消息
如果不存在,则正常消费,消费完毕后写入redis/db
如果存在,则证明消息被消费过,直接丢弃

六、消费者 消息消费异常解决方案【消费端弄丢了数据(ack机制)

产生场景:

RabbitMQ投递丢失了数据,主要是因为你消费的时候,刚消费到,还没处理,结果进程挂了,比如重启了,那么就尴尬了,RabbitMQ已经将消息投递出去了,认为你都消费了,这数据就丢了。

解决方案:

这个时候得用RabbitMQ提供的 ack机制,简单来说,就是你必须关闭RabbitMQ的自动 ack,可以通过一个api来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 ack一把。这样的话,如果你还没处理完,不就没有ack了?那RabbitMQ就认为你还没处理完,这个时候 RabbitMQ 会把这个消费分配给别的 consumer 去处理,消息是不会丢的。

确认模式有三种:

1、AcknowledgeMode.NONE:不确认

默认情况下消息消费者是NONE模式,默认所有消息消费成功,会不断的向消费者推送消息。
因为rabbitMq认为所有消息都被消费成功,不管消费者端是否成功处理本次投递,所以队列中不在存有消息,消息存在丢失的危险

2、AcknowledgeMode.AUTO:自动确认

在自动确认模式下,依据消息消费者的 处理业务逻辑是否抛出异常 自动发送【ack(无异常)、 nack(异常)】到 server 端

  • 可配合消息重试机制,来进行再次发送该消息;
  • 进行规定重试次数后仍然失败,则将消息进行丢弃;
  • 后期可搭配消息补偿逻辑进行失败消息的再次发起;

优点:这种模式下吞吐量非常高。

缺点:

  1. 有可能出现投递丢失的情况,不同于手动确认模式,如果消费者的TCP连接或通道在消息成功交互之前关闭,则此消息会丢失
  2. 消费者端过载的问题。在手动确认模式中,可以设置一次最多同时处理多少消息,而自动模式不能设置此值。因此,消费者有可能因为消息无法及时处理,堆积中内存中,内存耗尽而奔溃
  3. 此种模式只推荐在消费者可以快速且稳定处理投递的消息的场景中使用

3、AcknowledgeMode.MANUAL:手动确认

消费者收到消息后,手动调用basic.ackbasic.nackbasic.reject后,RabbitMQ收到这些消息后,才认为本次投递成功

手动确认模式可以使用 prefetch,限制通道上未完成的(“正在进行中的”)发送的数量

spring boot 配置方法

  • spring.rabbitmq.listener.simple.acknowledge-mode = auto

1、有无ack消费模式对比

  • ack 模式:效率高,存在丢失大量消息的风险
  • ack 模式:效率低,不会丢失消息,但需要考虑避免死性队列问题。

无ack模式(AcknowledgeMode.NONE

server端行为

  • rabbitmq server 默认推送的所有消息都已经消费成功,会不断的向消费端推送消息。
  • 因为rabbitmq server 认为推送的消息已经被成功消费,所以推送出去的消息不会暂存在 server端。

有ack模式(AcknowledgeMode.AUTOAcknowledgeMode.MANUAL

AcknowledgeMode.AUTO 模式:spring-rabbit 依据消息处理逻辑是否抛出异常自动发送ack(无异常)nack(异常)到server端。

AcknowledgeMode.MANUAL 模式:需要人为的获取到 channel 之后调用方法向 server 发送 ack (或消息消费失败时的 nack)信息。

server端行为

  • rabbitmq server 推送给每个 channel 的消息数量有限,会保证每个 channel 没有收到的 ack 的消息数量不会超过 prefetchCount
  • server 端会暂存没有收到 ack 的消息,等消费端 ack 后才会丢掉;如果收到消费端的 nack(消费失败的标识)或 connection 断开没有收到反馈,会将消息放回到原队列头部。

这种模式不会丢失消息,但效率较低,因为 server 端需要等收到消费端的答复之后才会继续推送消息,当然,推送消息和等待答复是异步的,可适当增大 prefetchCount 提高效率。

注意:ack 的模式下,需要考虑 setDefaultRequeueRejected(false) ,否则当消费消息抛出异常没有 catch 住时,这条消息会被rabbitmq server 放回到 queue 头部,再被推送过来,然后再抛异常再放回头部。。。死循环了。设置 false 的作用是在抛异常时不放回,而是直接丢弃,所以可能需要对这条消息做处理,以避免丢失。更详细的配置参考。

代码示例

1、AcknowledgeMode.AUTO 模式 ack 代码示例

配置文件:

spring.rabbitmq.listener.simple.acknowledge-mode = auto

消费者:

其实不需要明文ack代码,只是由消费者的方法业务是否抛出异常进行消息确认的,rabbitmq server 根据异常情况控制,ack(无异常)nack(异常)

推荐:搭配消息重试机制配置 和 消息补偿业务逻辑

2、AcknowledgeMode.MANUAL 模式 手动ack 代码示例

配置文件:

spring.rabbitmq.listener.simple.acknowledge-mode = manual

消费者:

    @RabbitListener(queues = "activity-eleven-first-notify", containerFactory = "activityElevenCainerFactory")
    public void processNormalOrder(Message message, Channel channel) {
        try {
            dealInfo(message);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

2、局部消息确认【manual】

开启手动ack确认【acknowledge-mode: manual】

spring:
  application:
    name: rabbitMQ_annotation_full
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: springcloud
    password: 123456
    virtual-host: /spring_cloud
    #确保生产者消息能到达MQ服务【若使用 confirm-callback 或 return-callback,必须要配置 publisher-confirms 或 publisher-returns 为 true】
    #是否启用【发布确认】
    publisher-confirms: true
    #是否启用【发布返回】
    publisher-returns: true
    #消费者监听配置
    listener:
      simple:
        #ack模式(none:不确认【默认】;auto:自动确认模式;manual:手动确认模式)
        acknowledge-mode: manual
        #消息重试机制
        retry:
          #监听重试是否可用
          enabled: true
          #最大重试次数
          max-attempts: 5
          #传递消息的时间间隔 默认1s
          initial-interval: 2s
        #消息被拒绝(未消费)重新放入队列
        default-requeue-rejected: true

根据前面章节例子,启动程序

启动两次,发现有两条消息未ack确认

消费者 channel.basicAck(tag,false); ack确认消息

/**
     * 主题模式【routingKey = TopicQueue1.#】
     *
     * @param userRequestModel:通过 @Payload 可以解析为POJO【我们需要的数据】
     * @param channel:获取 channel 目的是为了手动ack【此参数rabbitMQ会自动传入】
     * @param deliveryTag:通过 @Header 获取 deliveryTag 目的是为了手动ack【此参数rabbitMQ会自动传入】
     * @throws IOException
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "${learn_annotation_TopicQueue1}"),
            exchange = @Exchange(value = "${learn_annotation_TopicExchange}", type = "topic"),
            key = "TopicQueue1.#"
    ))
    public void processTopic1(@Payload UserRequestModel userRequestModel, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) throws IOException {
        log.info("Enter ConsumerTopic --> processTopic1()~~~~~~~~~~~~~~~~~~~");
        System.out.println("ConsumerTopic queue1 msg:" + userRequestModel);
        System.out.println("ConsumerTopic Object1 UserRequestModel:" + userRequestModel.toString());
        //获取真正的数据对象
        UserBean userBean = userRequestModel.getContent();
        System.out.println("ConsumerTopic Object1 UserBean:" + (ObjectUtils.isEmpty(userBean) ? null : userBean.toString()));
        //手动ack
        channel.basicAck(deliveryTag, false);
    }

 发现rabbitmq服务器上队列带确认消息没有了。

 1、channel.basicAck(deliveryTag, false);

deliveryTag:deliveryTag(唯一标识 ID):当一个消费者向 RabbitMQ 注册后,会建立起一个 Channel ,RabbitMQ 会用 basic.deliver 方法向消费者推送消息,这个方法携带了一个 delivery tag, 它代表了 RabbitMQ 向该 Channel 投递的这条消息的唯一标识 ID,是一个单调递增的正整数,delivery tag 的范围仅限于 Channel

multiple:是否批量.true:将一次性ack所有小于deliveryTag的消息

2、channel.basicNack(deliveryTag, false, true);

deliveryTag:该消息的index
multiple:是否批量.true:将一次性拒绝所有小于deliveryTag的消息。
requeue:被拒绝的是否重新入队列

    /**
     * 主题模式【routingKey = TopicQueue1.#】
     *
     * @param userRequestModel:通过 @Payload 可以解析为POJO【我们需要的数据】
     * @param channel:获取          channel 目的是为了手动ack【此参数rabbitMQ会自动传入】
     * @param deliveryTag:通过      @Header 获取 deliveryTag 目的是为了手动ack【此参数rabbitMQ会自动传入】
     * @throws IOException
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = "${learn_annotation_TopicQueue1}"),
            exchange = @Exchange(value = "${learn_annotation_TopicExchange}", type = "topic"),
            key = "TopicQueue1.#"
    ))
    public void processTopic1(@Payload UserRequestModel userRequestModel, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) throws IOException {
        log.info("Enter ConsumerTopic --> processTopic1()~~~~~~~~~~~~~~~~~~~");
        System.out.println("ConsumerTopic queue1 msg:" + userRequestModel);
        System.out.println("ConsumerTopic Object1 UserRequestModel:" + userRequestModel.toString());
        //获取真正的数据对象
        UserBean userBean = userRequestModel.getContent();
        System.out.println("ConsumerTopic Object1 UserBean:" + (ObjectUtils.isEmpty(userBean) ? null : userBean.toString()));
        //手动ack
//        channel.basicAck(deliveryTag, false);
        //手动否认
        channel.basicNack(deliveryTag, false, true);
    }

执行程序发现:消息被拒绝后,重回队列,然后不断重新发送给消费者。导致死循环

 3、channel.basicReject(deliveryTag:, false);

deliveryTag:该消息的index
requeue:被拒绝的是否重新入队列
channel.basicNack 与 channel.basicReject 的区别在于basicNack可以批量拒绝多条消息,而basicReject一次只能拒绝一条消息。

4、全局处理消息【manual】

 自动确认涉及到一个问题就是如果在处理消息的时候抛出异常,消息处理失败,但是因为自动确认而导致 Rabbit 将该消息删除了,造成消息丢失

手动确认消息

@Bean
public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    container.setQueueNames("demo3_queue");              // 监听的队列
    container.setAcknowledgeMode(AcknowledgeMode.MANUAL);        // 手动确认
    container.setMessageListener((ChannelAwareMessageListener) (message, channel) -> {      //消息处理
        System.out.println("====接收到消息=====");
        System.out.println(new String(message.getBody()));
        if(message.getMessageProperties().getHeaders().get("error") == null){
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
            System.out.println("消息已经确认");
        }else {
            //channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
            channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
            System.out.println("消息拒绝");
        }

    });
    return container;
}

AcknowledgeMode 除了 NONE 和 MANUAL 之外还有 AUTO ,它会根据方法的执行情况来决定是否确认还是拒绝(是否重新入queue)

如果消息成功被消费(成功的意思是在消费的过程中没有抛出异常),则自动确认
当抛出 AmqpRejectAndDontRequeueException 异常的时候,则消息会被拒绝,且 requeue = false(不重新入队列)
当抛出 ImmediateAcknowledgeAmqpException 异常,则消费者会被确认
其他的异常,则消息会被拒绝,且 requeue = true(如果此时只有一个消费者监听该队列,则有发生死循环的风险,多消费端也会造成资源的极大浪费,这个在开发过程中一定要避免的)。可以通过 setDefaultRequeueRejected(默认是true)去设置
 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值