Springboot实现Rabbitmq死信队列以及延迟队列的优化

开发 前端
由于特定原因导致队列中的消息不能被消费,这样的消息如果没有后续处理就可以放入死信队列中,例如一个订单如果超时未被支付从而自动失效,就将这个订单放到死信队列中。

导入依赖:

后续延迟队列优化用Springboot整合,先理解死信队列

<!--RabbitMQ依赖-->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.12.0</version>
        </dependency>

死信队列

由于特定原因导致队列中的消息不能被消费,这样的消息如果没有后续处理就可以放入死信队列中,例如一个订单如果超时未被支付从而自动失效,就将这个订单放到死信队列中。(死信队列中的消息是可以被消费的)

死信队列产生的原因

消息TTL过期

就是在规定的时间内消息没有被消费,(和延迟队列不同,延迟队列时表示到达时间消息才可以被消费)

在生产者代码中设置消息过期时间:

//生产者发送消息,将消息设置为TTL消息
        AMQP.BasicProperties properties =
                new AMQP.BasicProperties().builder().expiration("10000").build();

修改队列参数argument的特殊属性:

arguments.put("x-dead-letter-exchange", EXCHANGE_DIRECT_DEAD);//死信交换机
arguments.put("x-dead-letter-routing-key", "routingkey_direct-dead");//死信rotingkey
arguments.put("x-message-TTL", 10000);//设置过期时间(单位毫秒)  
//将死信交换机与死信队列绑定

模拟代码:

消费者1

public class Consumer01 {
    public static final String EXCHANGE_DIRECT = "exchange_direct";//普通交换机的名称
    public static final String EXCHANGE_DIRECT_DEAD = "exchange_direct_dead";//死信交换机的名称
    public static final String QUEUE_PLAIN = "queue_plain";//普通队列的名称
    public static final String QUEUE_PLAIN_DEAD = "queue_plain_dead";//死信队列的名称
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        Channel channel = RabbitMqUtils.createChannel();
        //声明死信交换机和普通交换机
        channel.exchangeDeclare(EXCHANGE_DIRECT, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(EXCHANGE_DIRECT_DEAD, BuiltinExchangeType.DIRECT);
        //声明普通队列(绑定普通队列与死信交换机的关系,在通过rotingkey绑定死信队列
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-dead-letter-exchange", EXCHANGE_DIRECT_DEAD);//死信交换机
        arguments.put("x-dead-letter-routing-key", "routingkey_direct-dead");//死信rotingkey
        //设置过期时间(单位毫秒)
        arguments.put("x-message-TTL", 10000);
        channel.queueDeclare(QUEUE_PLAIN, false, false, false, arguments);
        //声明死信队列
        channel.queueDeclare(QUEUE_PLAIN_DEAD, false, false, false, null);
        //普通交换机和队列的绑定
        channel.queueBind(QUEUE_PLAIN, EXCHANGE_DIRECT, "routingkey_direct");
        //死信交换机和死信队列的绑定
        channel.queueBind(QUEUE_PLAIN_DEAD, EXCHANGE_DIRECT_DEAD, "routingkey_direct-dead");
        //模拟超时时间消息未被消费
        Thread.sleep(1000000);
        channel.basicConsume(QUEUE_PLAIN, true, (consumerTag, message) -> {
            System.out.println("Consumer01.main接受到消息:" + new String(message.getBody()));
        }, (consumerTag, sig) -> {
        });
    }
}

生产者

public class Produce {
    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMqUtils.createChannel();
        //生产者发送消息,将消息设置为TTL消息
        AMQP.BasicProperties properties =
                new AMQP.BasicProperties().builder().expiration("10000").build();

        for (int i = 0; i < 10; i++) {
            String message = i + "";
            channel.basicPublish(Consumer01.EXCHANGE_DIRECT,"routingkey_direct",properties,message.getBytes(StandardCharsets.UTF_8));

        }

    }
}

消费者2

public class Consumer2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitMqUtils.createChannel();
        channel.basicConsume(Consumer01.QUEUE_PLAIN_DEAD, true, (consumerTag, message) -> {
            System.out.println("Consumer2.main接受死信队列的消息:" + new String(message.getBody()));
        }, (consumerTag, sig) -> {
        });

    }
}
/**输出结果:
Consumer2.main接受死信队列的消息:0
Consumer2.main接受死信队列的消息:1
Consumer2.main接受死信队列的消息:2
Consumer2.main接受死信队列的消息:3
Consumer2.main接受死信队列的消息:4
Consumer2.main接受死信队列的消息:5
Consumer2.main接受死信队列的消息:6
Consumer2.main接受死信队列的消息:7
Consumer2.main接受死信队列的消息:8
Consumer2.main接受死信队列的消息:9
    */

队列达到了最大长度

将RabbiMQ的队列的argument属性的键设置为 x-max-length 表示队列可以容纳的最大条数

消息被拒绝

将自动应答设为false

在消费者调一个Channel.basicReject,设置参数requeue为false,表示不重新排队,将消息丢到死信队列

延迟队列优化

延迟队列就是讲一个消息延迟发送,例如消息在队列中10s后才能被取出,可以通过RabbitMQ的插件或者死信队列来实现

用死信队列实现延迟队列的思路:

在于死信队列绑定的普通队列不设置消费者,利用TTL延迟消息,当TTL时间过期后,到达死信队列被消费这样就形成一个延迟队列。

延迟队列的使用场景:①典型的就是流量削峰,对于不重要的消息,可以延迟消费,有助于减轻数据库的压力,强化分布式系统的高可用和并发性能。②还可以实现一个消息提醒,例如用户三天未登录发送一个消息提醒。

在实际生产中可能存在很多不同的延迟时间要求,不可能每一个延迟要求就创造一个队列,我们可以用生产者实现延迟信息,而队列不设置TTL就可以根据生产的延迟消息进行延迟发送。

但是此方法虽然实现了一个队列就可以转发不同延时时间的消息,但是有缺陷,队列中的消息是排队发送的,也就是说如果我第一条消息发送20s延时,接着第二条消息发送2s延时。最后却是20s消息先消费,而2s消息后消费,因为RabbitMQ在检测一条消息时发生了20s的阻塞。如下:

###
GET http://localhost:8080/ttl/sendExpirationMessage/aaaaa/20000
###
GET http://localhost:8080/ttl/sendExpirationMessage/bbbbb/2000
最后输出结果是先消费aaaa后消费bbbb

可以通过RabbitMQ的插件实现延时队列,此方法没有这缺陷

从官网上下载对应版本的延迟插件,下载后如图:交换机类型会多出一个 x-delayed-message


在我们自定义的交换机中,这是一种新的交换机类型,该类型消息支持延迟投递机制,消息传递后并不会立即投递到目标队列中,而是存储在mnesia(一个分布式数据系统)表中,当达到投递时间时,才会投递到目标队列中。

代码实例:

配置类:

@Configuration
public class RabbitDelayedConfig {
    //延迟交换机
    public static final String DELAYED_EXCHANGE = "delayed.exchange";
    //延迟队列b
    public static final String DELAYED_QUEUE = "delayed.queue";
    //延迟交换机和队列的routingkey
    public static final String DELAYED_ROTINGKEY = "delayed.routingkey";

    //public CustomExchange(String name, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments) {
    //		super(name, durable, autoDelete, arguments);
    //		this.type = type;
    //	}
    @Bean
    public CustomExchange delayedExchange() {
        Map<String, Object> arguments = new HashMap<>();
        //定义延迟消息类型由那种交换机规则处置
        arguments.put("x-delayed-type", "direct");
        return new CustomExchange(DELAYED_EXCHANGE, "x-delayed-message", false, false, arguments);
    }

    @Bean
    public Queue delayedQueue() {
        return QueueBuilder
                .nonDurable(DELAYED_QUEUE)
                .build();
    }

    @Bean
    public Binding delayedBinding() {
        return BindingBuilder.bind(delayedQueue()).to(delayedExchange()).with(DELAYED_ROTINGKEY).noargs();

    }
}

生产者:

/*延迟交换机发送消息*/
    @GetMapping("/sendDelayedMessage/{message}/{delayedTTL}")
    public void sendDelayedMessage(@PathVariable String message, @PathVariable Integer delayedTTL) {
        log.info("当前时间:{},发送一条延迟时间为{}的延迟消息给延迟队列:{}", new Date().toString(), delayedTTL, message);
        rabbitTemplate.convertAndSend(RabbitDelayedConfig.DELAYED_EXCHANGE,
                RabbitDelayedConfig.DELAYED_ROTINGKEY,
                message,
                msg -> {
                    msg.getMessageProperties().setDelay(delayedTTL);//设置消息的延迟消息时间
                    return msg;
                });
    }

消费者:

@Slf4j
@Component
public class DelayedQueueConsumer {

    @RabbitListener(queues = RabbitDelayedConfig.DELAYED_QUEUE)
    public void queue(Message message) {
        log.info("接受到延迟队列的消息,当前时间:{},消息:{}",new Date().toString(),new String(message.getBody()));
    }
}
责任编辑:武晓燕 来源: 今日头条
相关推荐

2023-04-27 07:43:22

RabbitMQ重试队列死信队列

2023-09-05 15:48:14

RabbitMQ延迟队列

2024-03-18 00:00:03

RabbitMQ代码延迟队列

2024-04-15 00:00:00

RabbitMQ死信队列消息

2023-08-08 08:28:03

消息消费端Spring

2024-04-19 00:47:07

RabbitMQ消息机制

2023-10-23 10:02:58

RabbitMQ延迟队列

2021-12-08 10:47:35

RabbitMQ 实现延迟

2024-01-26 13:16:00

RabbitMQ延迟队列docker

2023-11-03 10:33:26

2021-10-15 10:39:43

RabbitMQ队列延迟

2018-07-20 09:16:04

链式存储结构

2020-07-30 08:03:36

MQ死信队列

2021-03-01 23:31:48

队列实现栈存储

2022-08-11 08:03:43

队列

2020-10-16 15:06:59

开发技术方案

2023-12-05 13:46:09

解密协程线程队列

2021-03-27 11:02:04

JavaScript队列编程语言

2024-01-31 08:01:36

Go延迟队列语言

2019-11-17 22:11:11

TCPSYN队列Accept队列
点赞
收藏

51CTO技术栈公众号