【RabbitMQ】RabbitMQ详解(二)

news2025/1/15 6:36:23

RabbitMQ详解

  • 死信队列
    • 死信来源
      • 消息TTL过期
      • 队列达到最大长度
      • 消息被拒绝
  • RabbitMQ延迟队列
    • TTL的两种设置
      • 队列设置TTL
      • 消息设置TTL
    • 整合SrpingBoot
      • 队列TTL
      • 延时队列TTL优化
      • Rabbtimq插件实现延迟队列

死信队列

先从概念解释上搞清楚这个定义,死信,顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,producer 将消息投递到 broker 或者直接到queue 里了,consumer 从 queue 取出消息 进行消费,但某些时候由于特定的原因导致 queue 中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信自然就有了死信队列。

应用场景:为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息消费发生异常时,将消息投入死信队列中。还有比如说:用户在商城下单成功并点击去支付后在指定时间未支付时自动失效。

死信来源

  1. 消息 TTL 过期:TTL是 Time To Live 的缩写, 也就是生存时间

  2. 队列达到最大长度:队列满了,无法再添加数据到 MQ 中

  3. 消息被拒绝:(basic.reject 或 basic.nack) 并且 requeue = false

举例
交换机类型是 direct,两个消费者,一个生产者,两个队列:消息队列和死信队列
在这里插入图片描述

消息TTL过期

生产者

    package eight;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import utils.RabbitMQUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

public class Producer {
    //普通交换机的名称
    public static final String NORMAL_EXCHANGE = "normal_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMQUtils.getChannel();

        //死信消息
        //死信消息 设置ttl时间 live to time 单位是ms
        AMQP.BasicProperties properties =
                new AMQP.BasicProperties().builder().expiration("10000").build();
        for (int i = 1; i < 11; i++) {
            String message = "info" + i;
            System.out.println("发送消息:" + message);
            channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",properties,message.getBytes(StandardCharsets.UTF_8));
        }
    }
}

消费者

package eight;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import com.rabbitmq.client.Delivery;
import utils.RabbitMQUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeoutException;

public class Consumer01 {
    //普通交换机的名称
    public static final String NORMAL_EXCHANGE = "normal_exchange";
    //死信交换机的名称
    public static final String DEAD_EXCHANGE = "dead_exchange";
    //普通队列的名称
    public static final String NORMAL_QUEUE = "normal_queue";
    //死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMQUtils.getChannel();

        //声明死信和普通交换机
        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);

        //声明普通队列
        //参数map
        Map<String, Object> arguments = new HashMap<>();
        //普通队列设置死信队列
        arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
        //设置死信routingKey
        arguments.put("x-dead-letter-routing-key","lisi");

        channel.queueDeclare(NORMAL_QUEUE,false,false,false,arguments);

        //声明死信队列
        channel.queueDeclare(DEAD_QUEUE,false,false,false,null);
        //绑定普通交换机和队列
        channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"zhangsan");

        //绑定死信交换机和死信队列
        channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"lisi");
        System.out.println("等待接收消息...");


        //接收消息
        DeliverCallback deliverCallback = (consumerTag, message)->{
            System.out.println("Consumer01接受的消息是:" + new String(message.getBody()));
        };
        channel.basicConsume(NORMAL_QUEUE,true,deliverCallback,consumerTag -> {});
    }
}

package eight;


import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import utils.RabbitMQUtils;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Consumer02 {

    //死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws IOException, TimeoutException {

        Channel channel = RabbitMQUtils.getChannel();

        System.out.println("等待接收死信消息...");

        DeliverCallback deliverCallback = (consumerTag, message) ->{
            System.out.println("Consumer02接受的消息是:"+new String(message.getBody(),"UTF-8"));
        };

        channel.basicConsume(DEAD_QUEUE,true,deliverCallback,consumerTag -> {});
    }
}

在启动消费者01后 将消费者01任务结束模拟消费者 模拟其接收不到消息 开启生产者和死信队列02 此时发现死信队列收到了消息

在这里插入图片描述

队列达到最大长度

消息生产者代码去掉 TTL 属性,basicPublish 的第三个参数改为 null

package night;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import utils.RabbitMQUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

public class Producer {
    //普通交换机的名称
    public static final String NORMAL_EXCHANGE = "normal_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMQUtils.getChannel();
        
        for (int i = 1; i < 11; i++) {
            String message = "info" + i;
            System.out.println("发送消息:" + message);
            channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",null,message.getBytes(StandardCharsets.UTF_8));
        }
    }
}

消费者参数添加

arguments.put("x-max-length",6);
package night;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import utils.RabbitMQUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;

public class Consumer01 {
    //普通交换机的名称
    public static final String NORMAL_EXCHANGE = "normal_exchange";
    //死信交换机的名称
    public static final String DEAD_EXCHANGE = "dead_exchange";
    //普通队列的名称
    public static final String NORMAL_QUEUE = "normal_queue";
    //死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMQUtils.getChannel();

        //声明死信和普通交换机
        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);

        //声明普通队列
        //参数map
        Map<String, Object> arguments = new HashMap<>();
        //普通队列设置死信队列
        arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
        //设置死信routingKey
        arguments.put("x-dead-letter-routing-key","lisi");

        arguments.put("x-max-length",6);

        channel.queueDeclare(NORMAL_QUEUE,false,false,false,arguments);

        //声明死信队列
        channel.queueDeclare(DEAD_QUEUE,false,false,false,null);
        //绑定普通交换机和队列
        channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"zhangsan");

        //绑定死信交换机和死信队列
        channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"lisi");
        System.out.println("等待接收消息...");


        //接收消息
        DeliverCallback deliverCallback = (consumerTag, message)->{
            System.out.println("Consumer01接受的消息是:" + new String(message.getBody()));
        };
        channel.basicConsume(NORMAL_QUEUE,true,deliverCallback,consumerTag -> {});
    }
}

注意
因为参数改变了,所以需要把原先队列删除

开启消费者C1,创建出队列,然后停止该 C1 的运行,启动生产者 启动消费者 C1 等待 10 秒之后,再启动消费者 C2

在这里插入图片描述
此时超过消息超过我们设置的最大长度6 超过6的消息就会变成死信 进入到死信队列中 此时 死信队列中就会收到4条死信

消息被拒绝

消息生产者代码同上生产者一致
需求:消费者 C1 拒收消息 “info5”,开启手动应答

package night;

import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import utils.RabbitMQUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;

public class Consumer01 {
    //普通交换机的名称
    public static final String NORMAL_EXCHANGE = "normal_exchange";
    //死信交换机的名称
    public static final String DEAD_EXCHANGE = "dead_exchange";
    //普通队列的名称
    public static final String NORMAL_QUEUE = "normal_queue";
    //死信队列的名称
    public static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMQUtils.getChannel();

        //声明死信和普通交换机
        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE,BuiltinExchangeType.DIRECT);

        //声明普通队列
        //参数map
        Map<String, Object> arguments = new HashMap<>();
        //普通队列设置死信队列
        arguments.put("x-dead-letter-exchange",DEAD_EXCHANGE);
        //设置死信routingKey
        arguments.put("x-dead-letter-routing-key","lisi");


        channel.queueDeclare(NORMAL_QUEUE,false,false,false,arguments);

        //声明死信队列
        channel.queueDeclare(DEAD_QUEUE,false,false,false,null);
        //绑定普通交换机和队列
        channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"zhangsan");

        //绑定死信交换机和死信队列
        channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"lisi");
        System.out.println("等待接收消息...");


        //接收消息
        DeliverCallback deliverCallback = (consumerTag, message)->{
            String msg = new String(message.getBody(), StandardCharsets.UTF_8);
            if(msg.equals("info5")){
                System.out.println("Consumer01接受的消息是:"+msg+": 此消息是被C1拒绝的");
                //requeue 设置为 false 代表拒绝重新入队 该队列如果配置了死信交换机将发送到死信队列中
                channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
            }else {
                System.out.println("Consumer01接受的消息是:"+msg);
                channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
            }
        };
        //将自动应答改为手动应答
        channel.basicConsume(NORMAL_QUEUE,false,deliverCallback,consumerTag -> {});
    }
}

开启消费者C1,创建出队列,然后停止该 C1 的运行,启动生产者,启动消费者 C1 等待 10 秒之后,再启动消费者 C2
在这里插入图片描述

RabbitMQ延迟队列

延时队列,队列内部是有序的,最重要的特性就体现在它的延时属性上,延时队列中的元素是希望 在指定时间到了以后或之前取出和处理,简单来说,延时队列就是用来存放需要在指定时间被处理的 元素的队列。

延迟队列使用场景

订单在十分钟之内未支付则自动取消
新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒
用户注册成功后,如果三天内没有登陆则进行短信提醒
用户发起退款,如果三天内没有得到处理则通知相关运营人员
预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议

这些场景都有一个特点,需要在某个事件发生之后或者之前的指定时间点完成某一项任务,如: 发生订单生成事件,在十分钟之后检查该订单支付状态,然后将未支付的订单进行关闭;那我们一直轮询数据,每秒查一次,取出需要被处理的数据,然后处理不就完事了吗?

如果数据量比较少,确实可以这样做,比如:对于「如果账单一周内未支付则进行自动结算」这样的需求, 如果对于时间不是严格限制,而是宽松意义上的一周,那么每天晚上跑个定时任务检查一下所有未支付的账单,确实也是一个可行的方案。但对于数据量比较大,并且时效性较强的场景,如:「订单十分钟内未支付则关闭」,短期内未支付的订单数据可能会有很多,活动期间甚至会达到百万甚至千万级别,对这么庞大的数据量仍旧使用轮询的方式显然是不可取的,很可能在一秒内无法完成所有订单的检查,同时会给数据库带来很大压力,无法满足业务要求而且性能低下。

TTL的两种设置

TTL 是什么呢?TTL 是 RabbitMQ 中一个消息或者队列的属性,表明一条消息或者该队列中的所有消息的最大存活时间,单位是毫秒。

换句话说,如果一条消息设置了 TTL 属性或者进入了设置 TTL 属性的队列,那么这条消息如果在 TTL 设置的时间内没有被消费,则会成为「死信」。如果同时配置了队列的 TTL 和消息的 TTL,那么较小的那个值将会被使用,有两种方式设置 TTL。

队列设置TTL

在创建队列的时候设置队列的 x-message-ttl 属性

Map<String, Object> params = new HashMap<>();
params.put("x-message-ttl",5000);
return QueueBuilder.durable("QA").withArguments(args).build(); // QA 队列的最大存活时间位 5000 毫秒

消息设置TTL

针对每条消息设置 TTL

rabbitTemplate.converAndSend("X","XC",message,correlationData -> {
    correlationData.getMessageProperties().setExpiration("5000");
});

两者区别

如果设置了队列的 TTL 属性,那么一旦消息过期,就会被队列丢弃(如果配置了死信队列被丢到死信队列中),而第二种方式,消息即使过期,也不一定会被马上丢弃,因为消息是否过期是在即将投递到消费者之前判定的,如果当前队列有严重的消息积压情况,则已过期的消息也许还能存活较长时间

另外,还需要注意的一点是,如果不设置 TTL,表示消息永远不会过期,如果将 TTL 设置为 0,则表示除非此时可以直接投递该消息到消费者,否则该消息将会被丢弃

整合SrpingBoot

添加依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.5</version>
    <relativePath/> 
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.47</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <!--RabbitMQ 依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
</dependencies>

设置配置文件

server:
  port: 8080
spring:
  rabbitmq:
    host: 62.234.167.47
    port: 5672
    username: root
    password: 123456

队列TTL

创建两个队列 QA 和 QB,两个队列的 TTL 分别设置为 10S 和 40S,然后再创建一个交换机 X 和死信交换机 Y,它们的类型都是 direct,创建一个死信队列 QD,它们的绑定关系如下
在这里插入图片描述
原先配置队列信息,写在了生产者和消费者代码中,现在可写在配置类中,生产者只发消息,消费者只接受消息

配置文件

package com.example.rabbitmqtest.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

@Configuration
public class TtlQueueConfig {
    //普通交换机的名称
    public static final String X_EXCHANGE="X";
    //死信交换机的名称
    public static final String Y_DEAD_LETTER_EXCHANGE="Y";
    //普通队列的名称
    public static final String QUEUE_A="QA";
    public static final String QUEUE_B="QB";
    //死信队列的名称
    public static final String DEAD_LETTER_QUEUE="QD";

    //声明xExchange
    @Bean("xExchange")
    public DirectExchange xExchange(){
        return new DirectExchange(X_EXCHANGE);
    }

    //声明yExchange 别名
    @Bean("yExchange")
    public DirectExchange yExchange(){
        return new DirectExchange(Y_DEAD_LETTER_EXCHANGE);
    }

    //声明普通队列
    @Bean("queueA")
    public Queue queueA(){
        Map<String, Object> arguments = new HashMap<>();
        //设置死信交换机
        arguments.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
        //设置死信routingKey
        arguments.put("x-dead-letter-routing-key","YD");
        //设置TTL
        arguments.put("x-message-ttl",40000);
        return QueueBuilder.durable(QUEUE_B).withArguments(arguments).build();
    }

    //声明普通队列  要有ttl 为40s
    @Bean("queueB")
    public Queue queueB(){
        Map<String,Object> arguments = new HashMap<>(3);
        //设置死信交换机
        arguments.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
        //设置死信RoutingKey
        arguments.put("x-dead-letter-routing-key","YD");
        //设置TTL 10s 单位是ms
        arguments.put("x-message-ttl",40000);
        return QueueBuilder.durable(QUEUE_B).withArguments(arguments).build();
    }
    //声明死信队列
    @Bean("queueD")
    public Queue queueD(){
        return QueueBuilder.durable(DEAD_LETTER_QUEUE).build();
    }

    //声明队列QA绑定x交换机
    @Bean
    public Binding queueABindingX(@Qualifier("queueA") Queue queueA, @Qualifier("xExchange") DirectExchange xExchange){
        return BindingBuilder.bind(queueA).to(xExchange).with("XA");
    }

    //声明队列 QB 绑定 X 交换机
    @Bean
    public Binding queueBBindingX(@Qualifier("queueB") Queue queueB,
                                  @Qualifier("xExchange") DirectExchange xExchange){
        return BindingBuilder.bind(queueB).to(xExchange).with("XB");
    }

    //声明队列 QD 绑定 Y 交换机
    @Bean
    public Binding queueDBindingY(@Qualifier("queueD") Queue queueD,
                                  @Qualifier("yExchange") DirectExchange yExchange){
        return BindingBuilder.bind(queueD).to(yExchange).with("YD");
    }

}

生产者

package com.example.rabbitmqtest.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMessageController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @RequestMapping("/sendMsg/{message}")
    public void sendMsg(@PathVariable("message") String message){
        log.info("当前时间:{},发送一条信息给两个TTL队列:{}",new Date().toString(),message);
        rabbitTemplate.convertAndSend("X","XA","消息来自ttl为10s的队列" + message);
        rabbitTemplate.convertAndSend("X","XB","消息来自ttl为40s的队列" + message);
    }
}

消费者代码

package com.example.rabbitmqtest.controller;

import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Date;

@Slf4j
@Component
public class DeadLetterQueueConsumer {

    @RabbitListener(queues = "QD")
    public void receiverD(Message message, Channel channel){
        String msg = new String(message.getBody());
        log.info("当前时间:{}, 收到死信队列消息{}",new Date().toString(),msg);
    }
}

在这里插入图片描述

第一条消息在 10S 后变成了死信消息,然后被消费者消费掉,第二条消息在 40S 之后变成了死信消息, 然后被消费掉,这样一个延时队列就打造完成了。

不过,如果这样使用的话,岂不是每增加一个新的时间需求,就要新增一个队列,这里只有 10S 和 40S 两个时间选项,如果需要一个小时后处理,那么就需要增加 TTL 为一个小时的队列,如果是预定会议室然后提前通知这样的场景,岂不是要增加无数个队列才能满足需求?

延时队列TTL优化

在这里新增了一个队列 QC,该队列不设置 TTL 时间,根据前端的请求确定 TTL 时间,绑定关系如下:
在这里插入图片描述
新增配置类

package com.example.rabbitmqtest.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

@Configuration
public class MsgTtlQueueConfig {
    //普通队列的名称
    public static final String QUEUE_C = "QC";

    //死信交换机的名称
    public static final String Y_DEAD_LETTER_EXCHANGE = "Y";

    //声明QC
    @Bean("queueC")
    public Queue QueueC(){
        Map<String,Object> arguments = new HashMap<>(3);
        //设置死信交换机
        arguments.put("x-dead-letter-exchange",Y_DEAD_LETTER_EXCHANGE);
        //设置死信routingKey
        arguments.put("x-dead-letter-routing-Key","XC");
        return QueueBuilder.durable(QUEUE_C).withArguments(arguments).build();
    }

    //声明队列QC 绑定 X 交换机
    @Bean
    public Binding queueCBindingX(@Qualifier("queueC") Queue queueC, @Qualifier("xExchange")DirectExchange xExchange){
        return BindingBuilder.bind(queueC).to(xExchange).with("XC");
    }
    
}

修改生产者

package com.example.rabbitmqtest.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMessageController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @RequestMapping("/sendMsg/{message}")
    public void sendMsg(@PathVariable("message") String message){
        log.info("当前时间:{},发送一条信息给两个TTL队列:{}",new Date().toString(),message);
        rabbitTemplate.convertAndSend("X","XA","消息来自ttl为10s的队列" + message);
        rabbitTemplate.convertAndSend("X","XB","消息来自ttl为40s的队列" + message);
    }

    //开始发消息
    @RequestMapping("/sendExpirationMsg/{message}/{ttlTime}")
    public void sendMsg(@PathVariable("message") String message, @PathVariable("ttlTime") String ttlTime){
        log.info("当前时间:{},发送一条时长是{}毫秒TTL信息给队列QC:{}",new Date().toString(),ttlTime,message);
        rabbitTemplate.convertAndSend("X","XC",message,message1 -> {
            message1.getMessageProperties().setExpiration(ttlTime);
            return message1;
        });
    }
}

Rabbtimq插件实现延迟队列

上文中提到的问题,确实是一个问题,如果不能实现在消息粒度上的 TTL,并使其在设置的 TTL 时间及时死亡,就无法设计成一个通用的延时队列。那如何解决呢,接下来我们就去解决该问题。

安装延时队列插件

可去官网下载 (opens new window)找到 rabbitmq_delayed_message_exchange 插件,放置到 RabbitMQ 的插件目录。

因为官网也是跳转去该插件的 GitHub 地址进行下载:点击跳转(opens new window)

打开 Linux,用 Xftp 将插件放到 RabbitMQ 的安装目录下的 plgins 目录,

RabbitMQ 与其 plgins 目录默认分别位于

# RabbitMQ 安装目录
cd /usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8   
# RabbitMQ 的 plgins 所在目录
cd /usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins

进入目录后执行下面命令让该插件生效,然后重启 RabbitMQ

# 安装
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
# 重启服务
systemctl restart rabbitmq-server

打开 Web 界面,查看交换机的新增功能列表,如果多出了如图所示,代表成功添加插件
在这里插入图片描述
实战

在这里新增了一个队列 delayed.queue,一个自定义交换机 delayed.exchange,绑定关系如下:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1331892.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用keytool查看Android APK签名

文章目录 一、找到JDK位置二、使用方法2.1 打开windows命令行工具2.2 查看签名 三、如何给APK做系统签名呢? 一、找到JDK位置 安卓AS之后&#xff0c;可选择继续安装JDK&#xff0c;如本文使用amazon版本默认位置&#xff1a;C:\Users\66176.jdks\corretto-1.8.0_342可通过自…

数据预处理:多重共线性_检测和解决办法

文章目录 1.多重共线性简介&#xff08;Collinearity and Multicollinearity&#xff09;1.1 多重共线性的后果1.2 处理多重共线性问题的方法 2. 设置2.1 导入库2.2 数据集特征波士顿房价BMI 数据集 2.3 导入数据 3. 相关矩阵3.1 聚类图 4. 方差膨胀因子4.1 两种多重共线性4.2 …

HackTheBox - Medium - Linux - Format

Format Format 是一种中等难度的 Linux 机器&#xff0c;它突出显示了由解决方案的结构方式引起的安全问题。立足点涉及PHP源代码审查&#xff0c;发现和利用本地文件读/写漏洞&#xff0c;并利用Nginx中的错误配置在Redis Unix套接字上执行命令。横向移动包括浏览 Redis 数据…

指针的含义

我们还取前面图片解释的道理&#xff1a; pa表示的意思就是这个地址&#xff0c;并不会显示出10这个数字 *pa就是指针&#xff0c;最后指向了a10&#xff0c;所以他最后程序输出是10 &pa这个含义就是取pa的地址&#xff0c;那么pa是一个虚拟的地址&#xff0c;只是简单的…

7种常见的网络安全设备及其功能

网络安全设备在现代网络环境中起着至关重要的作用&#xff0c;帮助保护个人和组织免受恶意攻击。本文将介绍7种常见的网络安全设备&#xff0c;包括防火墙、入侵检测系统、反病毒软件、数据加密设备、虚拟私人网络、安全信息和事件管理系统以及网络访问控制设备&#xff0c;并详…

Apache RocketMQ,构建云原生统一消息引擎

本文整理于 2023 年云栖大会林清山带来的主题演讲《Apache RocketMQ 云原生统一消息引擎》 演讲嘉宾&#xff1a; 林清山&#xff08;花名&#xff1a;隆基&#xff09;&#xff0c;Apache RocketMQ 联合创始人&#xff0c;阿里云资深技术专家&#xff0c;阿里云消息产品线负…

postgresql vacuum流程分析

概述 VACUUM是postgresql MVCC机制不可分割的组成部分。 postgresql在管理同一个元组的多个版本时&#xff0c;采取在堆表页面上从老版本到新版本放置元组的方法&#xff0c;每个元组都记录了xmax和xmin用于判断其可见性。这样的好处是&#xff08;1&#xff09;在索引键没有…

RasaGPT对话系统的工作原理

RasaGPT 结合了 Rasa 和 Langchain 这 2 个开源项目&#xff0c;当超出 Rasa 现有意图(out_of_scope)的时候&#xff0c;就会执行 ActionGPTFallback&#xff0c;本质上就是利用 Langchain 做了一个 RAG&#xff0c;调用 LLM API。RasaGPT 涉及的技术栈比较多而复杂&#xff0c…

OpenCV | 霍夫变换:以车道线检测为例

霍夫变换 霍夫变换只能灰度图&#xff0c;彩色图会报错 lines cv2.HoughLinesP(edge_img,1,np.pi/180,15,minLineLength40,maxLineGap20) 参数1&#xff1a;要检测的图片矩阵参数2&#xff1a;距离r的精度&#xff0c;值越大&#xff0c;考虑越多的线参数3&#xff1a;距离…

[python]用python实现对arxml文件的操作

目录 关键词平台说明一、背景二、方法2.1 库2.2 code 关键词 python、excel、DBC、openpyxl 平台说明 项目Valuepython版本3.6 一、背景 有时候需要批量处理arxml文件(ARXML 文件符合 AUTOSAR 4.0 标准)&#xff0c;但是工作量太大&#xff0c;阔以考虑用python。 二、方…

2023尚硅谷大数据项目之数据治理考评平台

教程下载&#xff1a;https://download.csdn.net/download/m0_66047725/88655186 【资源目录】: ├──1.笔记 | ├──课堂随笔.pptx 288.70kb | ├──尚硅谷大数据技术之数据治理考评平台完整V1.2.0.docx 1.87M | ├──数据治理总结.mmap 133.77kb | ├──自测题1.txt …

科研学习|论文解读——面向电商内容安全风险管控的协同过滤推荐算法研究

【论文完整内容详见知网链接】&#xff1a; 面向电商内容安全风险管控的协同过滤推荐算法研究 - 中国知网 (cnki.net) 面向电商内容安全风险管控的协同过滤推荐算法研究* 摘 要&#xff1a;[目的/意义]随着电商平台商家入驻要求降低以及商品上线审核流程简化&#xff0c;内容安…

冒泡排序之C++实现

描述 冒泡排序算法是一种简单的排序算法&#xff0c;它通过将相邻的元素进行比较并交换位置来实现排序。冒泡排序的基本思想是&#xff0c;每一轮将未排序部分的最大元素逐个向右移动到已排序部分的最右边&#xff0c;直到所有元素都按照从小到大的顺序排列。 冒泡排序的算法…

LangChain 31 模块复用Prompt templates 提示词模板

LangChain系列文章 LangChain 实现给动物取名字&#xff0c;LangChain 2模块化prompt template并用streamlit生成网站 实现给动物取名字LangChain 3使用Agent访问Wikipedia和llm-math计算狗的平均年龄LangChain 4用向量数据库Faiss存储&#xff0c;读取YouTube的视频文本搜索I…

HarmonyOS - 鸿蒙开发入门

文章目录 HarmonyOS核心资源特性&#xff1a;全场景终端HarmonyOS 版本 HarmonyOS 和 OpenHarmony教程资源开发环境开发工具 - DevEco开发语言 - ArkTS核心框架 - ArkUI 考证 HarmonyOS 开发交流秋秋群&#xff1a;23458659&#xff0c;V : ez-code&#xff0c;期待交流和合作 …

Arduino上U8g2库显示中文的经历

u8g2自带很多中文库&#xff1b;但是向u8g2_font_wqy12_t_chinese3 比较全的应该是u8g2_font_wqy12_t_gb2312 这个&#xff0c;只是我还没有调用成功 这个库&#xff0c;中文就显示不全&#xff1b;有些没有定义&#xff0c;如百家姓 #include <Arduino.h> #include <…

【微服务】:微服务最佳实践

关键需求 最大限度地提高团队的自主性&#xff1a;创建一个团队可以完成更多工作而不必与其他团队协调的环境。 优化开发速度&#xff1a;硬件便宜&#xff0c;人不是。使团队能够轻松快捷地构建强大的服务。 关注自动化&#xff1a;人们犯错误。更多的系统操作也意味着更多的…

大端和小端传输字节序

大端和小端传输字节序 大端和小端一、最高有效位、最低有效位1.MSB(Most significant Bit)最高有效位2.LSB(Least Significant Bit)最低有效位 二、内存地址三、大端和小端四、网络字节序和主机字节序五、C#位操作符六、C#中关于大端和小端的转换七、关于负数八、关于汉字编码以…

【内存泄漏】内存泄漏及常见的内存泄漏检测工具介绍

内存泄漏介绍 什么是内存泄漏 内存泄漏是指程序分配了一块内存&#xff08;通常是动态分配的堆内存&#xff09;&#xff0c;但在不再需要这块内存的情况下未将其释放。内存泄漏会导致程序浪费系统内存资源&#xff0c;持续的内存泄漏还导致系统内存的逐渐耗尽&#xff0c;最…

蓝牙技术在物联网中的应用

随着蓝牙技术的不断演进和发展&#xff0c;蓝牙已经从单一的传统蓝牙技术发展成集传统蓝牙。高速蓝牙和低耗能蓝牙于一体的综合技术&#xff0c;不同的应用标准更是超过40个越来越广的技术领域和越来越多的应用场景&#xff0c;使得目前的蓝牙技术成为包含传感器技术、识别技术…