RabbitMQ消息队列——三个核心特性

1、消息过期机制

可以给每条消息指定一个有效期,一段时间内未被消费者处理,就过期了。

  • 示例场景:消费者(库存系统)挂了,一个订单15分钟还没被库存系统处理,这个订单其实已经失效了,哪怕库存系统再恢复,其实也不用扣减库存。
  • 示例场景:清理过期数据、模拟延迟队列的实现(不开会员就慢速)、专门让某个程序出路过期请求。

第一种方式是给队列中的所有消息指定一个统一的过期时间。也就是说,无论何时进入这个队列的消息,在特定的时间点都会过期失效。这种方式是针对整个队列而言。

第二种方式是给某条具体的消息指定过期时间。这意味着,针对特定的消息我们可以指定一个独立的过期时间。这样,在达到指定的时间后,这条消息将会过期并自动失效。

第一种方式
生产者代码:

package com.yupi.springbootinit.mq;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.nio.charset.StandardCharsets;

public class TtlProducer {
	// 定义队列名称为"ttl_queue"
    private final static String QUEUE_NAME = "ttl_queue";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        // 建立连接、创建频道
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            // 消息虽然可以重复声明,必须指定相同的参数,在消费者的创建队列要指定过期时间,
            // 后面要放args,在生产者你又想重新创建队列,又不指定参数,那肯定会有问题,
            // 所以要把这里的创建队列注释掉。
            // channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            
            // 发送消息
            String message = "Hello World!";
        	// 使用默认的交换机,将消息发送到指定队列
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}

消费者代码:

package com.yupi.springbootinit.mq;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

public class TtlConsumer {
	// 定义我们正在监听的队列名称"ttl_queue"
    private final static String QUEUE_NAME = "ttl_queue";

    public static void main(String[] argv) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置连接工厂的主机地址为 "localhost"
        factory.setHost("localhost");
        // 建立连接
        Connection connection = factory.newConnection();
        // 创建频道
        Channel channel = connection.createChannel();

        // 创建队列,指定消息过期参数
        Map<String, Object> args = new HashMap<String, Object>();
        // 设置消息过期时间为5秒
        args.put("x-message-ttl", 5000);
        // 创建队列,并传入队列名称、是否持久化、是否私有、是否自动删除,args 指定参数
        channel.queueDeclare(QUEUE_NAME, false, false, false, args);
    	// 打印等待消息的提示信息
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
        // 定义了如何处理消息的回调函数
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
        };
        // 消费消息,该方法会持续阻塞,等待接收消息
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
    }
}

第二种方式
生产者代码:

// 给消息指定过期时间
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
    	// 设置消息的过期时间为1000毫秒
        .expiration("1000")
        .build();
// 发布消息到指定的交换机("my-exchange")和路由键("routing-key")
// 使用指定的属性(过期时间)和消息内容(UTF-8编码的字节数组)
channel.basicPublish("my-exchange", "routing-key", properties, message.getBytes(StandardCharsets.UTF_8));

2、消息确认机制

为了保证消息成功被消费(快递成功被取走),rabbitmg 提供了消息确认机制,当消费者接收到消息后,比如要给一个反馈:

  • ack:消费成功
  • nack:消费失败
  • reject:拒绝

如果告诉 rabbitmg 服务器消费成功,服务器才会放心地移除消息。

3、死信队列

为了保证消息的可靠性,比如每条消息都成功消费,需要提供一个容错机制,即:失败的消息怎么处理?

  • 死信:指过期的消息、被拒收的消息、消息队列已满以及处理失败的消息的统称。
  • 死信队列:专门用来处理死信的队列(实际上是一个普通的队列,但被专门用来接收并处理死信消息。可以将它理解为名为"死信队列"的队列)。
  • 死信交换机:用于将死信消息转发到死信队列的交换机,也可以设置路由绑定来确定消息的路由规则(是一个普通的交换机,只是被专门用来将消息发送到死信队列。可以将其理解为名为"死信交换机“的交换机)。

死信可以通过将死信交换机绑定到死信队列来实现。这样,当消息被标记为死信时,它将被转发到死信交换机,并最终路由到死信队列进行处理。

在这里插入图片描述
死信队列主要用于处理以下情况下的死信消息。根据官方文档的说明,有以下三种情况:
1、消息被拒绝:当消费者使用 basic.reject或 basic.nack拒绝消息,并将 requeue参数设置为 false ,意味着不将消息重新放回队列,这时消息就成为了死信。
2、消息过期:当消息的过期时间设置,并且消息在队列中等待时间超过了设定的过期时间,该消息就会变成死信。
3、队列长度限制:当队列达到了设置的最大长度限制,新进入的消息将无法被存储,而被直接丢弃。这些无法进入队列的消息被视为死信。

生产者代码:

package com.yupi.springbootinit.mq;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;

import java.util.Scanner;

public class DlxDirectProducer {
	// 定义死信交换机名称为"dlx-direct-exchange"
    private static final String DEAD_EXCHANGE_NAME = "dlx-direct-exchange";
    // 定义本来的业务交换机名称为"direct2-exchange"
    private static final String WORK_EXCHANGE_NAME = "direct2-exchange";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            // 声明死信交换机
            channel.exchangeDeclare(DEAD_EXCHANGE_NAME, "direct");

            // 创建老板的死信队列,随机分配一个队列名称
            String queueName = "laoban_dlx_queue";
            channel.queueDeclare(queueName, true, false, false, null);
            channel.queueBind(queueName, DEAD_EXCHANGE_NAME, "laoban");
        	// 创建外包的死信队列,随机分配一个队列名称
            String queueName2 = "waibao_dlx_queue";
            channel.queueDeclare(queueName2, true, false, false, null);
            channel.queueBind(queueName2, DEAD_EXCHANGE_NAME, "waibao");
        	// 创建用于处理老板死信队列消息的回调函数,当接收到消息时,拒绝消息并打印消息内容
            DeliverCallback laobanDeliverCallback = (consumerTag, delivery) -> {
                String message = new String(delivery.getBody(), "UTF-8");
                // 拒绝消息,并且不要重新将消息放回队列,只拒绝当前消息
                channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false);
                System.out.println(" [laoban] Received '" +
                        delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
            };
        	// 创建用于处理外包死信队列消息的回调函数,当接收到消息时,拒绝消息并打印消息内容
            DeliverCallback waibaoDeliverCallback = (consumerTag, delivery) -> {
                String message = new String(delivery.getBody(), "UTF-8");
                // 拒绝消息,并且不要重新将消息放回队列,只拒绝当前消息
                channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false);
                System.out.println(" [waibao] Received '" +
                        delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
            };
        	// 注册消费者,用于消费老板的死信队列,绑定回调函数
            channel.basicConsume(queueName, false, laobanDeliverCallback, consumerTag -> {
            });
            // 注册消费者,用于消费外包的死信队列,绑定回调函数
            channel.basicConsume(queueName2, false, waibaoDeliverCallback, consumerTag -> {
            });
        	// 创建一个Scanner对象用于从控制台读取用户输入
            Scanner scanner = new Scanner(System.in);
            // 进入循环,等待用户输入消息和路由键
            while (scanner.hasNext()) {
                String userInput = scanner.nextLine();
                String[] strings = userInput.split(" ");
                if (strings.length < 1) {
                    continue;
                }
                String message = strings[0];
                String routingKey = strings[1];
            	// 发布消息到业务交换机,带上指定的路由键
                channel.basicPublish(WORK_EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8"));
                // 打印发送的消息和路由键
                System.out.println(" [x] Sent '" + message + " with routing:" + routingKey + "'");
            }

        }
    }
    //..
}

消费者代码:

package com.yupi.springbootinit.mq;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;

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

public class DlxDirectConsumer {
	// 定义我们正在监听的死信交换机名称"dlx-direct-exchange"
    private static final String DEAD_EXCHANGE_NAME = "dlx-direct-exchange";
	// 定义我们正在监听的业务交换机名称"direct2-exchange"
    private static final String WORK_EXCHANGE_NAME = "direct2-exchange";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(WORK_EXCHANGE_NAME, "direct");

        // 创建用于指定死信队列的参数的Map对象
        Map<String, Object> args = new HashMap<>();
        // 将要创建的队列绑定到指定的交换机,并设置死信队列的参数
        args.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
        // 指定死信要转发到外包死信队列
        args.put("x-dead-letter-routing-key", "waibao");

        // 创建新的小狗队列,并将其绑定到业务交换机,使用"xiaodog"作为路由键
        String queueName = "xiaodog_queue";
        channel.queueDeclare(queueName, true, false, false, args);
        channel.queueBind(queueName, WORK_EXCHANGE_NAME, "xiaodog");

        Map<String, Object> args2 = new HashMap<>();
        args2.put("x-dead-letter-exchange", DEAD_EXCHANGE_NAME);
        // 指定死信要转发到老板死信队列
        args2.put("x-dead-letter-routing-key", "laoban");

        // 创建新的小猫队列,并将其绑定到业务交换机,使用"xiaocat"作为路由键
        String queueName2 = "xiaocat_queue";
        channel.queueDeclare(queueName2, true, false, false, args2);
        channel.queueBind(queueName2, WORK_EXCHANGE_NAME, "xiaocat");
    	// 打印等待消息的提示信息
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
    	// 创建用于处理小狗队列消息的回调函数,当接收到消息时,拒绝消息并打印消息内容
        DeliverCallback xiaoyuDeliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            // 拒绝消息,并且不要重新将消息放回队列,只拒绝当前消息
            channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false);
            System.out.println(" [xiaodog] Received '" +
                    delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
        };
    	// 创建用于处理小猫队列消息的回调函数,当接收到消息时,拒绝消息并打印消息内容
        DeliverCallback xiaopiDeliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
        	// 拒绝消息,并且不要重新将消息放回队列,只拒绝当前消息
            channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false);
            System.out.println(" [xiaocat] Received '" +
                    delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
        };
    	// 注册消费者,用于消费小狗队列,绑定回调函数,自动确认消息改为false
        channel.basicConsume(queueName, false, xiaoyuDeliverCallback, consumerTag -> {
        });
        // 注册消费者,用于消费小猫队列,绑定回调函数
        channel.basicConsume(queueName2, false, xiaopiDeliverCallback, consumerTag -> {
        });
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值