RabbitMQ之Producer(三)

本文深入探讨了消息队列中的死信队列、延迟队列和优先级队列的实现原理与应用场景,通过代码示例展示了如何配置这些高级特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

死信队列

  1. 死信队列(DLQ(Dead-Letter-Queue))用来保存处理失败或者过期的消息。当一个消息变为死信消息(Dead Message)之后,消息会被投递到死信交换器(DLX(Dead-Letter-Exchange))中,DLX会将消息投递到死信队列(DLQ)中

  2. 消息变为死信消息的情况

    • 消息没有被正确消费————被取消确认(Basic.Nack或者Basic.Reject),并且设置requeue参数为false;
    • 消息TTL过期
    • 队列达到最大长度
  3. 死信交换器(DLX(Dead-Letter-Exchange))就是普通的Exchange.RabbitMQ 不会去验证死信交换器(DLX(Dead-Letter-Exchange))设置是否有效,当死信消息找不到指定的交换器时,死信消息会被RabbitMQ安静的丢弃,而不是抛出异常。

  4. 当在某一个队列中设置x-dead-letter-exchange属性时,如果这个队列中有消息变为死信消息,则Broker会自动的将这个消息重新发布到设置的Exchange上(x-dead-letter-exchange对应的那个交换器)。

  5. 代码示例

      /**
         * 死信队列
         */
        @Test
        public void testDLX() {
            ConnectionFactory factory = new ConnectionFactory();
    
            String userName = "jannal";
            String password = "jannal";
            String virtualHost = "jannal-vhost";
            String dlxQueueName = "jannal.direct.queue.dlx";
            String queueNameNormal = "jannal.direct.queue.normal";
            String dlxExchange = "jannal.direct.exchange.dlx";
            String exchangeNormal = "jannal.direct.exchange.normal";
            String routingKey = "DeadMessage";
            String bindingKey = "DeadMessage";
            String hostName = "jannal.mac.com";
            int portNumber = 5672;
    
            factory.setUsername(userName);
            factory.setPassword(password);
            factory.setVirtualHost(virtualHost);
            factory.setHost(hostName);
            factory.setPort(portNumber);
            factory.setAutomaticRecoveryEnabled(false);
    
            Connection conn = null;
            try {
                conn = factory.newConnection();
                Channel channel = conn.createChannel();
                boolean durable = true;
                boolean exclusive = false;
                boolean autoDelete = false;
    
    
                //普通队列添加DLX
                HashMap<String, Object> queueArgs = new HashMap<>();
                queueArgs.put("x-dead-letter-exchange", dlxExchange);
                //如果不为DLX执行routingKey,则使用原来队列的
                //args.put("x-dead-letter-routing-key","dlx");
                queueArgs.put("x-message-ttl", 10000); // 设置队列中消息存活时间为10秒
                queueArgs.put("x-max-length", 5); // 设置队列最大消息数量为5
                channel.queueDeclare(queueNameNormal, durable, exclusive, autoDelete, queueArgs);
                channel.exchangeDeclare(exchangeNormal, "direct", true);
                channel.queueBind(queueNameNormal, exchangeNormal, bindingKey);
    
                //死信
                channel.queueDeclare(dlxQueueName, durable, exclusive, autoDelete,null);
                channel.exchangeDeclare(dlxExchange, "direct", true);
                channel.queueBind(dlxQueueName, dlxExchange, bindingKey);
    
                boolean mandatory = false;
                boolean immediate = false;
                String msg = "Hello, world ";
                for (int i = 0; i < 12; i++) {
                    channel.basicPublish(exchangeNormal,
                            routingKey,
                            mandatory,
                            immediate,
                            MessageProperties.PERSISTENT_TEXT_PLAIN,
                            msg.getBytes("UTF-8"));
                }
    
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
            } catch (TimeoutException e) {
                logger.error(e.getMessage(), e);
            } finally {
                if (conn != null) {
                    try {
                        conn.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        
    
  6. 查看web控制台,Lim表示x-max-length=5,D表示durable=true,TTL表示x-message-ttl=10000,DLX表示x-dead-letter-exchange=jannal.direct.exchange.dlx。 因为队列最大长度为5,所以发送12条消息其中5条是进入jannal.direct.queue.normal,其中7条被放入到jannal.direct.queue.dlx死信队列中。当普通队列中的5条消息10s后过期时,此时12条消息都被投递到死信队列中

延迟队列

  1. 延迟队列存储的是延迟的消息,即consumer(消费者)只有在等待特定的时间后才能去消费消息或者看到消息。

  2. 延迟消息应用场景

    • 一个A任务执行之后,等待特定的时间T,B任务才能去执行。比如用户买火车票,必须在30分钟以内支付,否则席位会被取消。此时【取消席位】这个任务就可以使用延迟队列来处理。再比如滴滴打车之后,用户一直不评价,24小时之后自动评价
  3. x-message-ttl设置队列中消息的存活时间,当消息过期变为死信队列之后,此时Consumer(消息者)就可以从死信队列中消费消息,这样就达到了延迟特定时间的目的了。

  4. 为不同的延迟时间设置不同的队列,代码示例

     /**
         * 延迟队列
         */
        @Test
        public void testDelayQueue() {
            ConnectionFactory factory = new ConnectionFactory();
    
            String userName = "jannal";
            String password = "jannal";
            String virtualHost = "jannal-vhost";
            String dlxQueueName_5 = "jannal.direct.queue.dlx.5";
            String dlxQueueName_10 = "jannal.direct.queue.dlx.10";
            String queueNameNormal_5 = "jannal.direct.queue.normal_5";
            String queueNameNormal_10 = "jannal.direct.queue.normal_10";
            String dlxExchange_5 = "jannal.direct.exchange.dlx_5";
            String dlxExchange_10 = "jannal.direct.exchange.dlx_10";
            String exchangeNormal = "jannal.direct.exchange.normal";
    
            String hostName = "jannal.mac.com";
            int portNumber = 5672;
    
            factory.setUsername(userName);
            factory.setPassword(password);
            factory.setVirtualHost(virtualHost);
            factory.setHost(hostName);
            factory.setPort(portNumber);
            factory.setAutomaticRecoveryEnabled(false);
    
            Connection conn = null;
            try {
                conn = factory.newConnection();
                Channel channel = conn.createChannel();
                boolean durable = true;
                boolean exclusive = false;
                boolean autoDelete = false;
    
                HashMap<String, Object> queueArgs = new HashMap<>();
                queueArgs.put("x-dead-letter-exchange", dlxExchange_5);
    
                //设置5s
                queueArgs.put("x-message-ttl", 5);
                channel.queueDeclare(queueNameNormal_5, durable, exclusive, autoDelete, queueArgs);
                channel.exchangeDeclare(exchangeNormal, "direct", true);
                channel.queueBind(queueNameNormal_5, exchangeNormal, "5s");
                //设置10s
                queueArgs.put("x-dead-letter-exchange", dlxExchange_10);
                queueArgs.put("x-message-ttl", 10);
                channel.queueDeclare(queueNameNormal_10, durable, exclusive, autoDelete, queueArgs);
                channel.exchangeDeclare(exchangeNormal, "direct", true);
                channel.queueBind(queueNameNormal_10, exchangeNormal, "10s");
    
                //死信
                channel.queueDeclare(dlxQueueName_5, durable, exclusive, autoDelete,null);
                channel.exchangeDeclare(dlxExchange_5, "direct", true);
                channel.queueBind(dlxQueueName_5, dlxExchange_5, "5s");
    
                channel.queueDeclare(dlxQueueName_10, durable, exclusive, autoDelete,null);
                channel.exchangeDeclare(dlxExchange_10, "direct", true);
                channel.queueBind(dlxQueueName_10, dlxExchange_10, "10s");
    
    
                boolean mandatory = false;
                boolean immediate = false;
                String msg = "Hello, world ";
                //过期时间5s的12条
                for (int i = 0; i < 12; i++) {
                    channel.basicPublish(exchangeNormal,
                            "5s",
                            mandatory,
                            immediate,
                            MessageProperties.PERSISTENT_TEXT_PLAIN,
                            msg.getBytes("UTF-8"));
                }
    
                //过期时间10s的10条
                for (int i = 0; i < 10; i++) {
                    channel.basicPublish(exchangeNormal,
                            "10s",
                            mandatory,
                            immediate,
                            MessageProperties.PERSISTENT_TEXT_PLAIN,
                            msg.getBytes("UTF-8"));
                }
    
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
            } catch (TimeoutException e) {
                logger.error(e.getMessage(), e);
            } finally {
                if (conn != null) {
                    try {
                        conn.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    
    
    
  5. web控制台查看

优先级队列

  1. 对于消息在Broker中堆积的情况下,优先级高的消息具有优先被消费的特权。

  2. 代码示例

            /**
             * 优先级队列
             */
            @Test
            public void testPriority(){
                ConnectionFactory factory = new ConnectionFactory();
        
                String userName = "jannal";
                String password = "jannal";
                String virtualHost = "jannal-vhost";
                String queueName = "jannal.direct.queue.priority";
                String exchange = "jannal.direct.exchange.priority";
                String routingKey = "priority";
                String bindingKey = "priority";
                String hostName = "jannal.mac.com";
                int portNumber = 5672;
        
                factory.setUsername(userName);
                factory.setPassword(password);
                factory.setVirtualHost(virtualHost);
                factory.setHost(hostName);
                factory.setPort(portNumber);
                factory.setAutomaticRecoveryEnabled(false);
        
                Connection conn = null;
                try {
                    conn = factory.newConnection();
                    Channel channel = conn.createChannel();
                    boolean durable = true;
                    boolean exclusive = false;
                    boolean autoDelete = false;
        
        
                    HashMap<String, Object> args = new HashMap<>();
                    args.put("x-max-priority", 10);//设置一个队列的最大优先级
                    channel.queueDeclare(queueName, durable, exclusive, autoDelete, args);
                    channel.exchangeDeclare(exchange, "direct", true);
                    channel.queueBind(queueName, exchange, bindingKey);
        
                    boolean mandatory = false;
                    boolean immediate = false;
                    String msg = "Hello, world ";
        
                    //消息的优先级默认是0,最高为队列设置的优先级,优先级高的消息会被先消费
                    AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().priority(3).build();
                    channel.basicPublish(exchange,
                            routingKey,
                            mandatory,
                            immediate,
                            properties,
                            msg.getBytes("UTF-8"));
        
                } catch (IOException e) {
                    logger.error(e.getMessage(), e);
                } catch (TimeoutException e) {
                    logger.error(e.getMessage(), e);
                } finally {
                    if (conn != null) {
                        try {
                            conn.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
    
  3. web控制台
    -w650

RabbitMQ 是一个开源的消息中间件,主要用于实现系统间的异步通信和解耦。其核心概念包括生产者(Producer)、消费者(Consumer)、队列(Queue)、交换机(Exchange)和绑定(Binding)。生产者将消息发送到交换机,交换机根据绑定规则将消息路由到一个或多个队列中,消费者从队列中获取并处理消息 [^3]。 ### 核心组件与工作流程 - **生产者(Producer)**:负责生成消息并将其发送到交换机。 - **交换机(Exchange)**:接收来自生产者的消息,并根据绑定规则决定将消息发送到哪些队列。RabbitMQ 支持多种类型的交换机,如直连(Direct)、扇出(Fanout)、主题(Topic)和头部(Headers)交换机 [^2]。 - **队列(Queue)**:存储消息的缓冲区,直到消费者处理它们。 - **消费者(Consumer)**:从队列中获取消息并进行处理。 - **绑定(Binding)**:定义了交换机与队列之间的关系,通常通过路由键(Routing Key)来指定消息的路由规则 [^3]。 ### 消息确认机制 为了确保消息不会因为消费者崩溃而丢失,RabbitMQ 提供了手动确认(ACK)机制。消费者在处理完消息后,必须显式地向 RabbitMQ 发送确认信号,告知该消息已经成功处理。如果消费者在处理消息时发生错误或崩溃,RabbitMQ 将不会收到确认信号,并会将消息重新放入队列中,以便其他消费者可以处理它 [^1]。 ### 流量控制与公平分发 当多个消费者同时从同一个队列中消费消息时,可能会出现某些消费者负载过高的情况。为了解决这个问题,RabbitMQ 提供了 `basicQos` 方法,允许设置 `prefetchCount` 参数。通过将 `prefetchCount` 设置为 1,可以确保 RabbitMQ 不会在消费者尚未确认前一条消息之前向其发送新的消息。这样可以实现更公平的消息分发,避免某些消费者过载 [^1]。 ### 主题交换机(Topic Exchange) 主题交换机是一种灵活的路由方式,它使用通配符来匹配路由键。`*` 可以匹配一个单词,而 `#` 可以匹配零个或多个单词。这种方式非常适合需要根据多个条件进行路由的场景 [^2]。 ### 示例代码:使用 Python 和 `pika` 库发送和接收消息 ```python import pika # 连接到 RabbitMQ 服务器 connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() # 声明一个名为 'hello' 的队列 channel.queue_declare(queue='hello') # 发送消息到 'hello' 队列 channel.basic_publish(exchange='', routing_key='hello', body='Hello World!') print(" [x] Sent 'Hello World!'") # 关闭连接 connection.close() ``` ```python import pika # 连接到 RabbitMQ 服务器 connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() # 声明一个名为 'hello' 的队列 channel.queue_declare(queue='hello') # 定义回调函数,用于处理接收到的消息 def callback(ch, method, properties, body): print(f" [x] Received {body}") # 手动确认消息 ch.basic_ack(delivery_tag=method.delivery_tag) # 开始消费消息 channel.basic_consume(queue='hello', on_message_callback=callback) print(' [*] Waiting for messages. To exit press CTRL+C') # 启动消费者循环 channel.start_consuming() ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值