RabbitMQ的死信队列与延时队列,RabbitMQ的延迟插件

RabbitMQ的死信队列

死信,在官网中对应的单词为“Dead Letter”,可以看出翻译确实非常的简单粗暴。那么死信是个什么东西呢?
对RabbitMQ来说,产生死信的来源大致有如下几种:

  1. 消息被拒绝(basic.reject或basic.nack)并且requeue=false.
  2. 消息TTL过期
  3. 队列达到最大长度(队列满了,无法再添加数据到mq中)

“死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。而死信队列其实就是绑定到死信交换机上的队列

如何配置死信队列

  1. 正常配置业务队列(绑定到业务交换机上,并配置路由键)
  2. 配置死信队列(绑定到死信交换机上,并配置路由键),和业务队列是一样的,只是名字不一样
  3. 将业务队列绑定到死信交换机上,并配置路由键A

死信的处理流程
消息正常发送到业务队列,如果该业务队列拒绝消息,则该消息变成死信,这个时候就会根据路由键A找到对应的死信队列,将消息投放给死信队列,由死信队列处理

修改producer模块
使用到的一些常量

public class Constants {
    //直连型交换机
    public static final String DEFAULT_DIRECT_EXCHANGE = "defaultDirectExchange";
    public static final String DEFAULT_DIRECT_QUEUE = "defaultDirectQueue";
    public static final String DEFAULT_DIRECT_ROUTING_KEY = "defaultDirectRoutingKey";

    //主题交换机
    public static final String DEFAULT_TOPIC_EXCHANGE = "defaultTopicExchange";
    public static final String MAN_TOPIC_QUEUE = "manTopicQueue";
    public static final String TOTAL_TOPIC_QUEUE = "totalTopicQueue";
    public static final String MAN_TOPIC_ROUTING_KEY = "user.man";
    public static final String TOTAL_TOPIC_ROUTING_KEY = "user.#";

    //扇形交换机
    public static final String DEFAULT_FANOUT_EXCHANGE = "defaultFanoutExchange";
    public static final String DEFAULT_FANOUT_QUEUE_1 = "defaultFanoutQueue1";
    public static final String DEFAULT_FANOUT_QUEUE_2 = "defaultFanoutQueue2";
    public static final String DEFAULT_FANOUT_QUEUE_3 = "defaultFanoutQueue3";
}

配置业务队列并绑定到死信队列

@Configuration
public class DirectExchangeConfig {

    @Bean
    public DirectExchange defaultDirectExchange(){
        //直连型交换机,持久化消息,不自动删除
        return new DirectExchange(Constants.DEFAULT_DIRECT_EXCHANGE,true,false);
    }

    @Bean
    public Queue defaultDirectQueue(){
        //给普通队列绑定死信交换机,设置路由键
        Map<String, Object> map = new HashMap<>();
        map.put("x-dead-letter-exchange", Constants.DEAD_DIRECT_EXCHANGE);
        map.put("x-dead-letter-routing-key", Constants.DEAD_ROUTING_KEY);
        //声明队列,持久化消息,非独有,不自动删除,消息属性
        return new Queue(Constants.DEFAULT_DIRECT_QUEUE,true,false,false,map);
    }

    @Bean
    public Binding directBiding(){
        //将队列和交换机绑定,并设置路由键
        return BindingBuilder.bind(defaultDirectQueue()).to(defaultDirectExchange()).with(Constants.DEFAULT_DIRECT_ROUTING_KEY);
    }
}

配置死信队列

@Configuration
public class DeadDirectExchangeConfig {

    @Bean
    public DirectExchange deadDirectExchange(){
        return new DirectExchange(Constants.DEAD_DIRECT_EXCHANGE,true,false);
    }

    @Bean
    public Queue deadQueue(){
        return new Queue(Constants.DEAD_QUEUE,true,false,false);
    }

    @Bean
    public Binding deadQueueBiding(){
        //绑定死信队列和死信交换机、路由key
        return BindingBuilder.bind(deadQueue()).to(deadDirectExchange()).with(Constants.DEAD_ROUTING_KEY);
    }
}

发送消息

@RequestMapping("/direct/sendMsg")
    public void directSendMsg(){
        String msg = "direct-msg";
        //发送消息到交换机,并携带路由键(会映射到业务队列,业务队列消费失败时,该消息会变成死信,由死信队列消费)
        rabbitTemplate.convertAndSend(Constants.DEFAULT_DIRECT_EXCHANGE,Constants.DEFAULT_DIRECT_ROUTING_KEY,msg);
    }

修改consumer模块

//监听业务队列
@RabbitListener(queues = Constants.DEFAULT_DIRECT_QUEUE)
public void directConsumer(String msg, Channel channel,Message message) throws IOException {
    //死信队列测试使用,直接拒绝消息,将消息变为死信
    channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
}

//监听死信队列
@RabbitListener(queues = Constants.DEAD_QUEUE)
public void deadConsumer(String msg,Channel channel,Message message) throws IOException {
    System.out.println("死信队列处理:"+msg);
    channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}

启动两个模块,这个时候要先去控制台删除之前配置过的业务队列defaultDirectQueue,否则会报错,显示已注册的队列找不到x-dead-letter-exchange。
在这里插入图片描述

RabbitMQ的延时队列

在rabbitmq中不存在延时队列,但是我们可以通过设置消息的过期时间(过期消息会变为死信)和死信队列来模拟出延时队列。

在 rabbitmq 中存在2种方可设置消息的过期时间,第一种通过对队列进行设置,这种设置后,该队列中所有的消息都存在相同的过期时间,第二种通过对消息本身进行设置,那么每条消息的过期时间都不一样。如果同时使用这2种方法,那么以过期时间小的那个数值为准。当消息达到过期时间还没有被消费,那么那个消息就成为了一个 死信 消息。

  • 队列设置:在队列申明的时候使用 x-message-ttl 参数(和上面死信队列设置一样),单位为 毫秒
  • 单个消息设置:发送消息时设置消息属性的 expiration 参数的值,单位为 毫秒

这里只演示单个消息设置,按消息设置会更灵活一些

producer模块
发送消息时设置过期时间

@RequestMapping("/direct/sendMsg")
    public void directSendMsg(){
        String msg = "direct-msg";
        MessagePostProcessor processor = (message)->{
        	//设置消息过期时间为3s
            message.getMessageProperties().setExpiration("3000");
            return message;
        };
        //发送消息到交换机,并携带路由键
        rabbitTemplate.convertAndSend(Constants.DEFAULT_DIRECT_EXCHANGE,Constants.DEFAULT_DIRECT_ROUTING_KEY,msg,processor);
    }

consumer模块,将监听业务队列的方法注释掉,这样等3s消息没有被消费,就会被发送到死信队列,由死信队列接收处理
在这里插入图片描述

RabbitMQ的延迟插件

RabbitMQ消费延时消息,需要满足两个条件
1、消息TTL到期,也就是说该消息变为死信
2、 该消息在队列首部 ,众所周知队列是先进先出的,只有排在队列首部的消息,才可以被消费,但是很多人都会忽略这一点
来一个实际的应用场景:加入A消息过期时间是一个小时,A消息先进队列,B消息过期时间是10分钟,B消息后进队列,等到十分钟过去了,B消息并没有被死信队列消费,而是会等到一个小时过去,先消费A消息,A消息消费成功,这时B消息才排到队列首部,满足消费的两个条件,接着消费B消息

为了解决这种问题RabbitMQ提供了一个延迟插件 rabbitmq_delayed_message_exchange
下载地址:https://www.rabbitmq.com/community-plugins.html
下载完成之后放到plugins文件夹下,接着进入bin文件夹下,
输入命令: rabbitmq-plugins enable rabbitmq_delayed_message_exchange,启动延迟插件
输入命令:rabbitmq-plugins list 查看插件是否安装成功
在这里插入图片描述
如上图,说明插件安装成功

接着修改死信交换机和死信队列配置

    @Bean
    public Queue deadQueue(){
        //死信队列
        return new Queue(MqConstants.DEFAULT_REPEAT_TRADE_QUEUE_NAME,true,false,false);
    }

    @Bean
    public Binding deadQueueBiding(){
        //绑定死信队列和死信交换机、路由key
        return BindingBuilder.bind(deadQueue()).to(deadExchange()).with(MqConstants.DEFAULT_REPEAT_TRADE_QUEUE_NAME).noargs();
    }

    @Bean
    public CustomExchange deadExchange(){
        //死信交换机,使用插件rabbitmq_delayed_message_exchange
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-delayed-type", "direct");
        return new CustomExchange(MqConstants.DEAD_EXCHANGE, "x-delayed-message",true,false, arguments);
    }

启动服务,进入控制台可以看到,死信交换机类型为:x-delayed-type
在这里插入图片描述

发送延迟消息

	public void sendDelay(String queueName, String msg, Integer times) {
        logger.info("延时队列发送:队列名:{},消息:{},延时时间(毫秒):{}",queueName, msg, times);
        rabbitTemplate.convertAndSend(MqConstants.DEAD_EXCHANGE,MqConstants.DEFAULT_REPEAT_TRADE_QUEUE_NAME,
                JSON.toJSONString(msg), message->{
                	//设置延迟时间,单位:毫秒
                    message.getMessageProperties().setDelay(times);
                    return message;
                });
    }

消费消息和之前一样,就不写了,亲测可用

项目代码:项目代码
相关阅读:
RabbitMQ入门
SpringBoot整合RabbitMQ
RabbitMQ的消息确认机制,消息重试机制

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值