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

RabbitMQ 如何保证消息的可靠传输 针对上述问题,如果还要确保 RabbitMQ 生产者的消息正确投递,可以开启 confirm 模式,在生产者端设置开启 confirm 模式之后,你每次写的消息都会分配一个唯一的 id,然后如果写入了 RabbitMQ 中,RabbitMQ 会给你回传一个 ACK 消息,告诉你说这个消息 ok 了。注意,哪怕是你给 RabbitMQ 开启了持久化机制,也有一种可能,就是这个消息写到了 RabbitMQ 中,但是还没来得及持久化到磁盘上,结果不巧,此时 RabbitMQ 挂了,就会导致内存中的数据丢失。 阅读详情

目录

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

二、生产者 消息重复发送解决方案【生产者多次发送同一数据】

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

方案一:消息确认机制(事务)【不推荐】

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

SpringBoot消息发送确认实现代码

总结区别

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

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

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

1、有无ack消费模式对比

代码示例

2、局部消息确认【manual】

4、全局处理消息【manual】


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

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

二、生产者 消息重复发送解决方案【生产者多次发送同一数据】

产生场景:

  1. 由于多次触发相同数据、多次点击,导致相同消息发送了多次
  2. 由于生产者发送消息给MQ,在MQ确认的时候出现了网络波动,生产者没有收到确认,实际上MQ已经接收到了消息。这时候生产者就会重新发送一遍这条消息。

解决方案 :

1、给发送的消息去重处理(解决场景1):

  • 在发送端去重:在发送消息之前,可以在消息的唯一标识字段上进行去重操作。使用数据库或缓存来记录已经发送的消息的标识,每次发送消息之前先查询是否已存在相同标识的消息,如果存在则不发送。这样可以避免消息的重复发送。
  • 使用去重插件:RabbitMQ提供了一些去重插件,如rabbitmq-deduplication插件。这些插件可以在消息发送时自动进行去重操作,根据消息的内容生成唯一的消息ID,并在发送之前检查是否已存在相同ID的消息,从而避免重复发送。

2、交给消息消费端进行数据效验处理(解决场景2):当消费者接收到重复的多条消息,进行消费前先判断该数据是否已经进行过消费处理,或正在处理(最好加上分布式锁)。如果已经处理过则主动过滤抛弃掉

三、生产者 消息发送失败解决方案【生产者弄丢了数据(事务、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.ack、basic.nack、basic.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.AUTO,AcknowledgeMode.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)去设置
 

【rabbitmq】RabbitMQ 全面详解:从核心概念到高级应用 本文介绍了RabbitMQ消息队列的核心概念与应用实践。主要内容包括:RabbitMQ基本组件(生产者、交换机、队列、消费者)和四种交换机类型;保障消息幂等性的两种方案(全局唯一ID和业务逻辑判断);解决消息顺序性问题的三种模式(单队列单消费者、分区消费和业务序列号);处理消息积压的四类方法(提高消费能力、生产者限流、监控自动扩展、死信队列)。最后提供了生产环境最佳实践建议,包括配置参数、监控告警等。全文通过代码示例详细演示了各种场景下的解决方案实现。 阅读详情

相关推荐

RabbitMQ高级特性 - 事务消息

RabbitMQ 的 AMQP 协议实现了事务机制,允许开发者保证消息的发送和接收时原子性的,也就是说,要么消息全都发送成功,要么全都发送失败(只与发送方有关).

CYK_byte的博客 1546

消息队列----RabbitMQ

一、为什么要用MQ? 消息队列(message queue),见字知意,想到数据结构中的队列,特性先进先出,主要用途就是不同进程或者线程之间进行通信。 进程间通信方式有很多种,为什么用MQ呢?最经典的说法就是三点概括:解耦、异步、削峰 解耦: 从上图可以看到,传统模式的系统耦合性太强,外接系统要改代码,违背了我们软件设计高内聚低耦合的这么一个原则;而中间件模式,采用发布订阅的这么一个思...

生命不息,奋斗不止 2576

Aurora8b10b光口通信实战:复位时序与IP核配置详解

本文深入解析了Aurora 8B10B协议在FPGA高速光口通信中的核心实践,重点剖析了确保链路稳定启动的关键复位时序与IP核配置细节。通过详解全双工与单工模式下的复位流程、Vivado中IP核的物理层与链路层参数设置,并结合用户逻辑设计实例,为工程师实现可靠的高速数据传输提供了清晰的实战指南。

weixin_29280219的博客 178

RabbitMQ怎么处理消息事务

注意:在生产者中,我们使用 channel.txCommit() 提交事务,而在消费者中,我们使用 channel.basicAck() 手动确认消息或 channel.basicReject() 手动拒绝消息并重新入队。通过将通道(Channel)设置为事务模式,所有发送到该通道的消息都将在提交事务之前被缓存,并且在提交事务后才会被投递到交换机中。在确认监听器中,可以处理确认(ack)和未确认(nack)消息的逻辑,确保消息发送的可靠性。当收到未确认消息时,表示消息发送失败,可以进行相应的处理。

yuanchengfu0910的博客 3227

.ipynb文件深度解析:从JSON结构到Jupyter Notebook高效使用指南

在Python开发与数据处理领域,交互式编程正成为探索数据与验证思路的高效方式。Jupyter Notebook作为最流行的交互式环境之一,其默认文件格式.ipynb本质是一个基于JSON的文本文件,内部通过单元格组织代码、说明文字与运行结果。理解其JSON结构,不仅有助于掌握Notebook的存储原理,更能为文件损坏修复、版本控制与自动化转换提供基础。依托对单元格类型、魔法命令及输出可视化的熟练运用,数据分析和数据可视化流程可以变得更为流畅。同时,借助VS Code、nbconvert等工具,开发者能将.

weixin_28839629的博客 350

基于禾赛32线雷达和Xsens MTi-G-710 组合导航模块,松灵Scout mini 底盘实现 Cartographer 3D 建图过程

地下车库 Cartongrapher 3D建图

yhy_yhy13053117的博客 3940

RabbitMQ如何保证消息可靠性,看完这篇文章佬会有新的理解

本篇文章将详细介绍RabbitMQ的消息可靠性机制,如消息丢失,消息重复性消费,消息积压等问题,感兴趣的大佬可以一起学习哟~

小威的博客 3万+

ZYNQ 系统的IEEE1588 实现方法

zynq IEEE588/PTP 实现方案(讨论稿)

姚家湾博客 1万+

RabbitMQ:如何保证消息的可靠性?

要想保证消息的可靠性,就要先知道什么时候消息会丢失?会有三方面分别是:消息到MQ的过程丢失、MQ自己丢失、MQ到消费过程中丢失。

abc_138的博客 2568

基于小波变换的信号降噪处理及仿真研究_信号处理方法推荐--1(转载自用,侵删)...

综述作者:aresmiki链接:https://www.zhihu.com/question/23701194/answer/167005497来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。非平稳信号处理应该是现在信号处理技术最新的也是最热的研究方向了,信号处理方法从最早的时域统计到傅里叶变换的频域分析,是人们认识信号本质的一次巨大飞跃,给了分析人员换个角度看世界的...

weixin_32144483的博客 1249

单电源运放偏置电压设计:如何避免信号失真

本文详细解析了单电源运放偏置电压设计的关键技术,以LM358为例,探讨如何通过1/2 VCC偏置避免信号失真。文章从基本原理出发,揭示了双端偏置的黄金法则,并提供了常见设计陷阱的解决方案和进阶调试技巧,帮助工程师实现高保真信号放大。

weixin_29266647的博客 293

RabbitMQ系列--消息如何保证可靠性传输以及幂等性

一、消息如何保证可靠性传输 1.1、可能出现消息丢失的情况   1、Producer在把Message发送Broker的过程中,因为网络问题等发生丢失,或者Message到了Broker,但是出了问题,没有保存下来 针对这个问题,Producer可以开启MQ的事务,如果这个过程出现异常,进行回滚,但是有个很大的问题,你提交一个事务就会阻塞在那, 非常影响性能,生产环境肯定不会开启事务,一般都是使用confirm机制 2、Broker接收到Message暂存到内存,Consumer还没来得及消费,Broker

Zachary 4788

RK3568平台(电源篇)电源域IO配置

在kernel之前需要修改电源域,主控电源域的IO电平要与对接外设芯片的IO电平匹配,还要注意软件的电压配置要跟硬件的电压一致,否则,最坏的情况可能会导致IO的损坏。其实就是管理 IO 输出的电平;假如硬件上 IO 电源域配置为 3.3V,则 IO 输出最大电平为 3.3V;同理 IO 电源域硬件配置为 1.8V,则 IO 输出最大电平是 1.8V;这样有利于 IO 电平的灵活配置;

weixin_49303682的博客 3479

面试必问:RabbitMQ如何保证消息队列的可靠性传输

场景

孔明的博客 3074

华为OD机考双机位C卷 - 相对开音节 (Java & Python & JS & GO & C++ & C)

华为OD机考双机位C卷 - 相对开音节 , 提供Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。

算法大师! 744

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

rabbitmq如何保证消息的可靠性

rabbitmq如何保证消息的可靠性

林隐的博客 1908

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

RabbitMQ如何保证消息传输可靠性

如果消息被设置为非持久化,RabbitMQ 将尽力将消息传递给消费者,但不会将其写入磁盘,这意味着如果 RabbitMQ 服务器在消息传递之前崩溃或重启,消息可能会丢失。2(持久化):如果消息被设置为持久化,RabbitMQ 会将消息写入磁盘,以确保即使在 RabbitMQ 服务器重启时,消息也不会丢失。RabbitMQ 在接收到消息后,默认并不会立即进行持久化,而是先把消息暂存在内存中,这时候如果 MQ 挂了,那么消息就会丢失。持久化消息在发送到持久化队列后,将在服务器重启后保留,以确保消息不会丢失。

u013149491的博客 1494

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

【RabbitMQ】如何保证消息的可靠性传输,数据不丢失?

目录 一、消息丢失的情况 1. 生产者——RabbitMQ: 2. RabbitMQ: 3. RabbitMQ——消费者: 二、如何保证消息的可靠性传输? 1.生产者角度 a. 事务机制 b. 发送方确认(publisher confirm)机制 2. RabbitMQ-持久化 3.消费者 一、消息丢失的情况 1. 生产者——RabbitMQ: 当消息的生产者将消息...

千千 1440

RabbitMQ 如何保证消息可靠性

作者 | 黄永灿后端开发攻城狮,关注服务端技术与性能优化。RabbitMQ 的消息发送机制在讨论 RabbitMQ 的消息可靠性之前,我们先来回顾一下消息在 RabbitMQ 中是怎么流...

杏仁技术站 1525

RabbitMQ实现保证消息可靠性

RabbitMQ实现保证消息可靠性

qq_37741426的博客 1556

rabbitmq接收不到消息_MQ保证消息的可靠性传输

前景概要: 我们在使用mq的时候肯定是希望数据不能多也不能少的,不能多即需要考虑mq的重复消费(幂等性)问题。不能少即不能丢失数据。 很明显如果如果你的mq中存放的是非常核心、重要的数据,比如订单数据、积分流水等, 这些数据是必须不能丢的,一旦丢失则会造成数据的一致性问题,可能会给公司带来极大损失。 丢失数据分析: 丢失数据一般来说分为2种情况,一种是数据已经发送到了mq,但是mq自身把...

weixin_39647412的博客 6471

RabbitMQ 保证消息可靠性传输

RabbitMQ 保证消息可靠性传输消息可靠性问题解决消息可靠性问题生产者到 Queue 阶段消息丢失Queue 到消费者阶段 我们知道,RabbltMQ 不能够保证消息不会丢失,那么在高并发场景下,如何保证消息的可靠性传输? 消息可靠性问题 Publisher 发送消息到 Exchange 阶段: Publisher 发送的消息未到达 Exchange。 消息到达 Exchange 未到达 Queue。 Exchange 发送消息到 Queue 阶段:MQ 宕机,Queue 将消息丢失。 Queue

qq_44157349的博客 1833

消息队列如何保证消息可靠性传输

在本文中,我们主要介绍了持久化存储、消息确认机制、重试机制和幂等性处理等机制,以及使用 JAVA 语言和 Apache Kafka、RabbitMQ 进行代码实践的示例。通过上述机制的支持和实践,可以保证消息队列系统的可靠性和稳定性,为各大互联网公司提供高效、稳定的消息传输服务。

青山师 3786

rabbitmq unacked消息如何处理_RabbitMQ 如何保证消息可靠性

作者 | 黄永灿后端开发攻城狮,关注服务端技术与性能优化。RabbitMQ 的消息发送机制在讨论 RabbitMQ 的消息可靠性之前,我们先来回顾一下消息在 RabbitMQ 中是怎么流转的。消息生产端将消息发送给 ExchangeExchange 根据 RoutingKey 把消息路由至具体 Queue消息消费端监听 Queue 并消费消息那么,这几个步骤有哪些地方可能会掉链子导致消息...

weixin_39774808的博客 3018

MQ如何保证消息的可靠性传输(绝对通俗易懂)

转载自: https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md 面试题 如何保证消息的可靠性传输?或者说,如何处理消息丢失的问题? 面试官心理分析 这个是肯定的,用 MQ 有个基本原则,就...

WhereIsMyChair的博客 1万+

RabbitMQ如何保证消息可靠性?

以上介绍了RabbitMQ保证消息可靠性的问题、产生原因、解决方案等。不足之处,欢迎指正。

辣香牛肉面的博客 1448
上一篇: 注解@CrossOrigin解决跨域问题
下一篇: RabbitMQ(二)Springboot整合及使用
Marvin-Fox
博客等级 码龄11年 190粉丝 100原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值