Spring Boot学习 RabbitMQ 消息队列(3) 延时队列的介绍和使用 保证消息可靠性的方法 解决 消息丢失 消息重复 消息积压

Spring Boot学习 RabbitMQ 消息队列 基本概念 web端操作 SpringBoot整合

4.延时队列 实现定时任务

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

设置队列TTL的实现方式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9976FUMk-1600669854433)(D:\学习资料\笔记\project-gulimail\image-20200918112119325.png)]

此次实现方式:使用同一个交换机的不同路由键实现

在这里插入图片描述

代码实现:

4.1 创建组件

package com.rwp.gulimail.order.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

@Configuration
public class MyMQConfig {
    
    
    /**
     * 添加@Bean后,springboot启动是如果mq中没有相关的队列,则会先创建队列
     * @return
     */
    @Bean
    public Queue orderDelayQueue(){
    
    
        Map<String,Object> argument = new HashMap<>();
        //String name, boolean durable, boolean exclusive, boolean autoDelete, @Nullable Map<String, Object> arguments
        //创建延时队列--死信队列
        argument.put("x-dead-letter-exchange","order-event-exchange");
        argument.put("x-dead-letter-routing-key","order.release.order");
        argument.put("x-message-ttl",60000);
        Queue queue = new Queue("order.delay.order.queue", true, false, false, argument);
        return queue;
    }

    @Bean
    public Queue orderReleaseOrderQueue(){
    
    
        Queue queue = new Queue("order.release.order.queue", true, false, false);
        return queue;
    }
    @Bean
    public Exchange orderEventExchange(){
    
    
        //String name, boolean durable, boolean autoDelete, Map<String, Object> arguments
        Exchange exchange = new TopicExchange("order-event-exchange",true,false);
        return exchange;
    }
    @Bean
    public Binding orderCreateOrderBinding(){
    
    
        //String destination, Binding.DestinationType destinationType, String exchange, String routingKey, @Nullable Map<String, Object> arguments
        Binding binding = new Binding("order.delay.order.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.create.order",
                null);
        return binding;
    }
    @Bean
    public Binding orderReleaseOrderBinding(){
    
    
        //String destination, Binding.DestinationType destinationType, String exchange, String routingKey, @Nullable Map<String, Object> arguments
        Binding binding = new Binding("order.release.order.queue",
                Binding.DestinationType.QUEUE,
                "order-event-exchange",
                "order.release.order",
                null);
        return binding;
    }

}

4.2测试

发送消息:

@Resource
    private RabbitTemplate rabbitTemplate;
    @GetMapping("/create/order")
    @ResponseBody
    public OrderEntity createOrderTest(){
    
    
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setOrderSn(new Date().toString());
        rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",orderEntity);
        return orderEntity;
    }

监听消息:

@RabbitListener(queues = "order.release.order.queue")
    public void listener(OrderEntity entity, Message message, Channel channel) throws IOException {
    
    
        System.out.println("收到过期的订单信息,准备关闭订单"+entity);
        System.out.println("当前时间:"+new Date());
        //手动ack
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }

结果:

  1. 发送请求后,一分钟内队列 order.delay.order.queue中会存在一个消息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PzhN61jh-1600669854442)(D:\学习资料\笔记\project-gulimail\image-20200918134450327.png)]

  1. 一分钟后消息到达队列order.release.order.queue并被消费,实现延时队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kLVy64kl-1600669854446)(D:\学习资料\笔记\project-gulimail\image-20200918134417437.png)]

5.保证消息可靠性

5.1 消息丢失产生的原因和解决方案

原因一:消息发出去,由于网络问题没有抵达服务器

  • 做好容错方法(try-catch),发送消息可能会网络失败,失败后要有重试机制,可以记录到数据库,采用定期扫描重发的方式

  • 做好日志记录,每个消息状态是否都被服务器收到都应该记录

  • 做好定期重发

  • CREATE TABLE mq_message(
        message_id varhcar(32) not null,
        content text,  #json序列化后的消息体
        to_exchange varchar(255) default null, #发送消息时的三个参数
        routing_key varchar(255) default null,
        class_type varchar(255) default null,
        message_status int(1) default '0' comment '0-新建  1-已发送  2-错误抵达 3-已抵达',
        create_time datetime,
        update_time datetime,
        PRIMARY KEY (message_id)
    )ENGINE=INNODB DEFAULT CHARSET=utf8mb4
    

原因二:消息抵达Broker,Broker要将消息写入磁盘(持久化)才算成功.此时Broker尚未完成持久化,发生了宕机

  • publisher也必须加入确认回调机制,确认成功的消息,修改数据库消息状态
  • 在returnedMessage回调方法中(没有到达Broker时会调用),修改数据库中保存信息的状态

原因三:自动ack的状态下.消费者收到消息,但没来得及消费后宕机

  • consumer一定开启手动ACK,消费成功才移除,失败或没来得及处理就noACK并重新进队

总结:

  • publish(ReturnCallback)和consumer(手动ACK)同时做好消息确认机制
  • 数据库进行所有消息的持久化和定期检查重新发送

5.2 消息重复产生的原因和解决方案

原因:消息消费成功,事务已经提交,手动ack时,机器宕机.导致没有ack成功,Broker的消息从unack变为ready,并发送给其他消费者

解决方案:

  • 消费者的业务消费接口应该设计为幂等的.例如添加修改的标识位
  • 使用防重表,发送消息每一个都有业务的唯一标识,处理过就不用处理
  • rabbitMQ的每一个消息都有redelivered字段,可以获取是否是被重新投递过来的消息,而不是第一次投递

5.3 消息积压产生的原因和解决方案

原因:

  • 消费者宕机积压
  • 消费者消费能力不足积压
  • 发送者发送流量太大

解决方案:

  • 增加更多的消费者,进行正常消费
    息,修改数据库消息状态
  • 在returnedMessage回调方法中(没有到达Broker时会调用),修改数据库中保存信息的状态

原因三:自动ack的状态下.消费者收到消息,但没来得及消费后宕机

  • consumer一定开启手动ACK,消费成功才移除,失败或没来得及处理就noACK并重新进队

总结:

  • publish(ReturnCallback)和consumer(手动ACK)同时做好消息确认机制
  • 数据库进行所有消息的持久化和定期检查重新发送

5.2 消息重复产生的原因和解决方案

原因:消息消费成功,事务已经提交,手动ack时,机器宕机.导致没有ack成功,Broker的消息从unack变为ready,并发送给其他消费者

解决方案:

  • 消费者的业务消费接口应该设计为幂等的.例如添加修改的标识位
  • 使用防重表,发送消息每一个都有业务的唯一标识,处理过就不用处理
  • rabbitMQ的每一个消息都有redelivered字段,可以获取是否是被重新投递过来的消息,而不是第一次投递

5.3 消息积压产生的原因和解决方案

原因:

  • 消费者宕机积压
  • 消费者消费能力不足积压
  • 发送者发送流量太大

解决方案:

  • 增加更多的消费者,进行正常消费
  • 上线专门的队列消息服务,将消息先批量取出来,记录数据库,离线时慢慢处理

猜你喜欢

转载自blog.csdn.net/weixin_44634197/article/details/108709997