Rabbit实战之(四)消息确认消费机制

消息高可用和确认消费

RabbitMQ在实际使用过程中,如果配置或使用不当,则会出现令人头疼的问题,下面列举三个

  1. 不知道发送的消息是否成功,生产者将消息发送出去,但是如果消息模型不存在,队列不存在,就是发送失败的,这一点作为生产者要知道
    解决方案
    RabbitMQ会要求生产者在发送完信息后,进行发送确认,当成功就代表消息成功的发送出去,在配置文件中
  @Bean
    public RabbitTemplate rabbitTemplate(){
        // 设置“发送消息后返回确认信息”
        connectionFactory.setPublisherReturns(true);
        // 构造发送消息组件实例对象
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        // 这里是为了消息确认和手动ACK
        rabbitTemplate.setMandatory(true);
        // 设置消息在传输中的格式,在这里采用JSON的格式进行传输
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
        // 发送消息后,如果发送成功,则输出“消息发送成功”的反馈信息
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                log.info("消息发送成功:correlationData({}),ack({}), cause({})", correlationData,ack,cause);
            }
        });
        // 发送消息后,如果发送失败,则输出“消息丢失”的反馈信息
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                log.info("消息丢失:exchange({}),route({}),replyCode ({}),replyText({}),message:{}",exchange,routingKey,replyCode,replyText,
                message);
            }
        });
        // 最终返回RabbitMQ的操作组件实例RabbitTemplate
        return rabbitTemplate;
    }

上述代码就实现了发送成功的回调

  1. 如果出现特殊原因,RabbitMQ的服务宕机了,需要重启,此时队列中有大量未消耗的消息,就会造成重启过程中,消息丢失
    解决方案
    RabbitMQ是建议将交换机、队列持久化设置durable为true,并且也要将消息的持久化模式也为true

    @RequestMapping("/sendReply")
    public void send(){
        CorrelationData messageId = new CorrelationData(UUID.randomUUID().toString());
        // 1指定exchange
        Message message = MessageBuilder
                .withBody("asddas".getBytes())
                .setContentType(MessageProperties.CONTENT_TYPE_BYTES)
                .setContentEncoding("ute-8")
                .setMessageId(UUID.randomUUID().toString())
                .setDeliveryMode(MessageDeliveryMode.PERSISTENT)
                .build();
        rabbitTemplate.convertAndSend("boot-topic-exchange","fast.red.dog",message);
    }
    // 创建队列
    @Bean(name = "basicQueue")
    public Queue basicQueue() {
    /*
        // durable :是否持久化(宕机以后重新发布)
        // exclusive : 是否排外,当前队列只能被一个消费者消费
        // autoDelete 如果这个队列没有消费者,队列是否被删除
        // arguments 指定当前队列的其他信息
        */
        return new Queue("basicQueue",true,false,false,null);
    }
  1. 消费幂等性的问题,消费者再监听消息的时候,如果监听失败或者生产者宕机,导致队列找不到消费者而不断的重新入队列,最终出现消息被重复消费的现象。
    解决方案
    RabbitMQ提供了ACK模式,即消息确认模式,有三种,NONE\AUTO\MANUAL,不同的确认机制,底层的执行逻辑和应用场景不一样的。在实际的环境中,都是通过消息确认后才删除消息,

常见消息确认模式介绍

NONE:无须确认模式,消费者消费消息后,不需要反馈任何消息给生产者
在这里插入图片描述
AUTO:自动确认模式,生产者监听到一个消息时,注意是监听,不是完成消费,发送一个响应信号ACK给RabbitMQ服务器,之后将消息从RabbitMQ移除,这是RabbitMQ的默认行为
在这里插入图片描述
MANUAL:手动确认消费模式,这是一种以代码的形式发送一个ACK反馈信息给RabbitMQ服务器,之后该消息在RabbitMQ的队列中移除。
在这里插入图片描述
由于NONE模式不严谨,实际使用比较少见,所以建议尽量少用。

基于自动模式消费确认

配置

#全局设置队列的确认消费模式;如果队列对应的消费者没有指定消费确认模式,则将默认 指定全局 #配置的auto模式
spring.rabbitmq.listener.simple.acknowledge-mode=auto

或者修改配置工厂

    /**
     * 下面为单一消费者实例的配置
     * @return
     */
    @Bean(name = "singleListenerContainer")
    public SimpleRabbitListenerContainerFactory listenerContainer(){
        // 定义消息监听器所在的容器工厂
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        // 设置容器工厂所用的实例
        factory.setConnectionFactory(connectionFactory);
        // 设置消息在传输中的格式,在这里采用JSON的格式进行传输
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        // 设置并发消费者实例的初始数量,在这里为1个
        factory.setConcurrentConsumers(1);
        // 设置并发消费者实例的最大数量,在这里为1个
        factory.setMaxConcurrentConsumers(1);
        // 设置并发消费者实例中每个实例拉取的消息数量,在这里为1个
        factory.setPrefetchCount(1);
        //设置确认消费模式为自动确认消费(这里可以在配置文件配置
        // spring.rabbitmq.listener.simple.acknowledge-mode=auto)
        factory.setAcknowledgeMode(AcknowledgeMode.AUTO);
        return factory;
    }

    /**创建自动消息确认-----------------------------------------------**/

    //创建队列
    @Bean(name = "autoQueue")
    public Queue autoQueue(){
        //创建并返回队列实例
        return new Queue("autoQueue",true);
    }
    //创建交换机
    @Bean
    public DirectExchange autoExchange(){
        //创建并返回交换机实例
        return new DirectExchange("autoExchange",true,false);
    }
    //创建绑定
    @Bean
    public Binding autoBinding(){
        //创建并返回队列交换机和路由的绑定实例
        return BindingBuilder.bind(autoQueue()).to(autoExchange()).with("auto-key");
    }
    @RequestMapping("/testAutoConfirm")
    public ResultVo testAutoConfirm(@RequestBody KnowledgeInfo model) {
        rabbitTemplate.convertAndSend("autoExchange", "auto-key", model, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                // 获取消息的属性
                MessageProperties messageProperties = message.getMessageProperties();
                // 设置消息的持久化模式
                messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                // 设置消息的类型(在这里指定消息类型为User类型)
                messageProperties.setHeader(AbstractJavaTypeMapper.DEFAULT_CONTENT_CLASSID_FIELD_NAME,KnowledgeInfo.class);
                //返回消息实例
                return message;
            }
        });
        log.info("auto机制生产者发送对象消息{}",model);
        return ResultVo.success("auto机制生产者发送对象消息成功");

    }
    /** AUTO机制确认模式
     * @param model
     */
    @RabbitListener(queues = "autoQueue",containerFactory = "singleListenerContainer")
    public void receiveAutoQueue(@Payload KnowledgeInfo model){
        try {
            //输出日志
            log.info("基于AUTO的确认消费模式-消费者监听消费消息-内容为: {} ",model);
        }catch (Exception e){
            log.error("基于AUTO的确认消费模式-消费者监听消费消息-发生异常:",e.fillInStackTrace());        }
    }

在这里插入图片描述

基于手动模式确认

这回使用配置文件配置手动确认

#全局设置队列的确认消费模式;如果队列对应的消费者没有指定消费确认模式,则将默认 指定全局 #配置的auto模式
spring.rabbitmq.listener.simple.acknowledge-mode=manual
    /**创建手动消息确认-----------------------------------------------**/

    //创建队列
    @Bean(name = "manualQueue")
    public Queue manualQueue(){
        //创建并返回队列实例
        return new Queue("manualQueue",true);
    }
    //创建交换机
    @Bean
    public DirectExchange manualExchange(){
        //创建并返回交换机实例
        return new DirectExchange("manualExchange",true,false);
    }
    //创建绑定
    @Bean
    public Binding manualBinding(){
        //创建并返回队列交换机和路由的绑定实例
        return BindingBuilder.bind(manualQueue()).to(manualExchange()).with("manual-key");
    }
   /** 手动机制确认模式
     * @param model
     */
    @RabbitListener(queues = "manualQueue")
    public void receiveAutoQueue(@Payload KnowledgeInfo model, Channel channel, Message message) throws IOException {
        // 获取消息分发时的全局唯一标识
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            //输出日志
            log.info("基于手动的确认消费模式-消费者监听消费消息-内容为: {} ",model);
            // 获取消息分发时的全局唯一标识
            deliveryTag = message.getMessageProperties().getDeliveryTag();
            // 测试异常
            int i = 1/0;
            // 执行完业务逻辑后,手动进行确认消费,其中第1个参数:消息的分发标识( 全局唯一);第2个参数:是否允许批量确认消费(在这里设置为true)
            channel.basicAck(deliveryTag,true);
        }catch (Exception e){
            //如果在处理消息的过程中发生了异常,则照样需要人为手动确认消费该消息,
            //该消息将一直留在队列中,从而将导致消息的重复消费,如果第二个参数为false则抛弃
            channel.basicReject(deliveryTag,true);
            //否则该消息将一直留在队列中,从而将导致消息的重复消费
            log.error("基于手动的确认消费模式-消费者监听消费消息-发生异常:",e);
        }
    }
    @RequestMapping("/testManualConfirm")
    public ResultVo testManualConfirm(@RequestBody KnowledgeInfo model) {
        rabbitTemplate.convertAndSend("manualExchange", "manual-key", model, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                // 获取消息的属性
                MessageProperties messageProperties = message.getMessageProperties();
                // 设置消息的持久化模式
                messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                // 设置消息的类型(在这里指定消息类型为User类型)
                messageProperties.setHeader(AbstractJavaTypeMapper.DEFAULT_CONTENT_CLASSID_FIELD_NAME,KnowledgeInfo.class);
                //返回消息实例
                return message;
            }
        });
        log.info("手动确认机制生产者发送对象消息{}",model);
        return ResultVo.success("手动确认机制生产者发送对象消息成功");

    }

在这里插入图片描述
如果我们重启项目,会再次进入监听,直到异常去掉,消息被手动确认
在这里插入图片描述在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值