rabbitmq中的优先级队列,死信队列的实现

应用的场景:主要的就是生产者的生产速度大于消费速度,如果低于那么优先就没有任何的意义了
优先级队列的实现主要有两个方面:队列的优先级 发送消息时的优先这两个问题
代码是在spriingboot整合rabbitmq基础上改造过来的,创建队列时,给队列设置一个优先级

/**
     * 直连的队列名称
     * @return
     */
    @Bean
    public Queue testDirectQueue(){
        /**
         * dureable:是否持久化,默认是false, 持久化队列: 会被存储在磁盘上,当消息代理重启时仍然存在
         * exclusive: 默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列会被关闭
         * autoDelete:是否自动删除,当没有生产者或者消费者使用队列,该队列会自动删除
         * return new Queue("testDirectQueue", true, true, fasle,)
         */
        Map<String, Object> map = new HashMap<>();
        //在这里配置好队列的优先级0-10  默认的是5 注意这里Map中的key需要注意下,是否正确的
        map.put("x-max-priority", 10);
        return new  Queue("firstQueue", true, false,false,map);
    }

在发送消息时,给消息设置下优先级:

@RequestMapping(value = "sendMessage", method = RequestMethod.GET)
    public String sendDirectMessage(){
        //现在发送一个map的消息过去
        String messageId = UUID.randomUUID().toString();
        String messageData = "test message, hello";
        //将lcoaldateTime转换为string类型的
        String format = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        Map<String, Object> map = new HashMap<>();
        map.put("messageId", messageId);
        map.put("messageData", messageData);
        map.put("createTime", format);
        rabbitTemplate.convertAndSend("exchange", "testDirectRouting", map, new MessagePostProcessor() {

            //这里给消息设置优先级
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {

                //设置消息的优先级别为最大的
                message.getMessageProperties().setPriority(10);
                return message;
            }
        });
        return "ok";
    }

死信队列:
死信队列的场景主要是用来确保重要的业务数据能够记录下来,同时不需要再次入队操作
实现死信队列的流程比较简单:首先就是定义好队列,交换机(包括死信队列,死信交换机) 这里说明下死信队列就是平常的队列并不特殊,死信交换也是如此

@SpringBootConfiguration
public class DeadLetterConfig {


    /**
     * 声明一个业务的交换机
     * @return
     */
    @Bean("businessExchange")
    public FanoutExchange businessExchange(){
        return new FanoutExchange("businessExcahnge");
    }


    /**
     * 声明一个死信交换机
     * @return
     */
    @Bean("deadLetterExchange")
    public FanoutExchange deadLetterExchange(){
        return new FanoutExchange("deadLetterExchange", false, false );
    }


    /**
     * 声明一个业务队列
     * @return
     */
    @Bean("businessQueueA")
    public Queue businessQueueA(){
        Map<String, Object> map = new HashMap(2);
        //队列绑定一个死信交换机属性
        map.put("x-dead-letter-exchange", "deadLetterExchange");
        //给队列中的死信路由键设置一个值
        map.put("x-dead-letter-routing-key", "deadLeeterA");
        //声明一个队列
        return new Queue("businessQueueA", true, false, false, map);
    }


    /**
     * 声明另外一个队列,不同于上面的队列
     * 他们之间的区别是,设置同意死信交换机
     * 但是设置不同的routyKey值
     * @return
     */
    @Bean("businessQueueB")
    public Queue businessQueueB(){
        Map<String, Object> map = new HashMap(2);
        //同上面那个队列一样,声明并且设置一些属性,绑定同一个死信交换机,设置不同的routyKey
        //队列绑定一个死信交换机属性,注意这是以个交换机名称
        map.put("x-dead-letter-exchange", "deadLetterExchange");
        //给队列中的死信路由键设置一个值
        //其实这个路由值是没有任何用的,因为是Fanout类型的交换机
        map.put("x-dead-letter-routing-key", "deadLeeterB");
        return new Queue("businessQueueB", true, false, false, map);
    }

    /**
     * 设置一个死信队列,但是它并没有其他的属性
     * @return
     */
    @Bean("deadLetterQueueA")
    public Queue deadLetterQueueA(){
        return new Queue("deadLetterQueueA", true, false, false);
    }

    /**
     * 设置好另外一个死信队列
     * @return
     */
    @Bean("deadLetterQueueB")
    public Queue deadLetterQueueB(){
        return new Queue("deadLetterQueueB", true, false,false);
    }


    /**
     * 绑定一个业务队列与业务交换机的绑定
     * 这里是只能设置一个routyKey值的
     * @return
     */
    @Bean("bindBusinessA")
    Binding bindbusinessQueueA(){
        return BindingBuilder.bind(businessQueueA()).to(businessExchange());
    }


    /**
     * 绑定queueB与交换
     * @return
     */
    @Bean("bindBusinessB")
    Binding bindBusinessQueueB(){
        return BindingBuilder.bind(businessQueueB()).to(businessExchange());
    }


    /**
     * 指定队列与Fanout交换机之间的绑定
     * @return
     */
    @Bean
    Binding bindingDeadWithExchange(){
        return BindingBuilder.bind(deadLetterQueueA()).to(deadLetterExchange());
    }


    /**
     * 将死信队列与死信交换机进行绑定
     * @return
     */
    @Bean
    Binding bindingDeadB(){
        return BindingBuilder.bind(deadLetterQueueB()).to(deadLetterExchange());
    }

第二步就是在消费队列时进行的代码:

@Slf4j
@Component
public class DeadLetterLlistener {

    /**
     * 监听队列名
     * 接收到业务队列的消息
     */
    @RabbitListener(queues = "businessQueueA")
    public void receive(Message message, Channel channel) throws IOException {
        /**
         * 死信消息的原理:
         * 如果队列配置了参数 x-dead-letter-routing-key 的话,“死信”的路由key将会被替换成该参数对应的值。如果没有设置,则保留该消息原有的路由key。
         * 举例:如果原有消息的路由key是testA,被发送到业务Exchage中,然后被投递到业务队列QueueA中,
         * 如果该队列没有配置参数x-dead-letter-routing-key,则该消息成为死信后,将保留原有的路由keytestA,如果配置了该参数,
         * 并且值设置为testB,那么该消息成为死信后,路由key将会被替换为testB,然后被抛到死信交换机中。
         */
        String msg  = new String(message.getBody());
        log.info("接收到的消息为:{}", msg);
        boolean ack = true;
        Exception exception = null;
        try {
            if (msg.contains("deadletter")){
                throw new RuntimeException("dead letter exception");
            }
        }catch (Exception e){
            ack = false;
            exception = e;
        }

        /**
         * 消息成为死信的几个先决条件:
         * 1:消息是被否定确认(具体就是指:basicNack()或者是basicReject)
         * 2:消息中的requeue属性是被确认为false的
         * 3:具体的成为死信的原因有如下几个:过期消息, 超过规定的队列长度  消费者在进行消费时出现异常
         */
        if (!ack){
            log.info("消息消费发生异常,error msg:{}", exception.getStackTrace());
            //这个方法为不确认该tag对应的消息,b 代表的是确认多条数据信息 b1代表的requeue重新入列
            //这个方法代表的就是将消息丢弃到死信队列中去处理
            channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
        }else{
            //肯定消息消费的方法,第二个参数代表的就是确认多个消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }
    }


    /**
     * 接收到业务队列的方法B
     * @param message
     * @param channel
     * @throws IOException
     */
    @RabbitListener(queues = "businessQueueB")
    public void receiveB(Message message, Channel channel) throws IOException {
        System.out.println("接收到的信息数据为:" + new String(message.getBody()));
        //此方法代表的就只是将消息做手动确认处理
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }


    /**
     * 接收到死信队列的信息方法
     * @param message
     * @param channel
     * @throws IOException
     */
    @RabbitListener(queues = "deadLetterQueueA")
    public void receiveDead(Message message, Channel channel) throws IOException {
        System.out.println("接收到死信队列的消息数据为:" + new String(message.getBody()));
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }


    @RabbitListener(queues = "deadLetterQueueB")
    public void receiveDeadB(Message message, Channel channel) throws IOException {
        System.out.println("queueB接收到信息数据为:" + new String(message.getBody()));
        /**
         * x-first-death-exchange:第一次死信所在的交换机名称
         * x-first-death-reason: 成为死信的原因
         * x-first-death-queue: 第一次成为死信所在的队列
         */
        log.info("死信队列中的信息为:{}", message.getMessageProperties());
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
### 如何使用 RabbitMQ 实现延迟队列 #### 使用 RabbitMQ 的死信机制实现延迟队列 RabbitMQ 提供了一种通过 **死信交换器 (Dead Letter Exchange, DLX)** 和普通队列相结合的方式来实现延迟队列的功能。这种方法的核心在于利用消息的 TTL(Time To Live)属性,当消息超过设定的时间后会被自动丢弃并进入死信队列。 以下是具体的实现方法: 1. 配置两个队列:一个是带有 TTL 属性的消息队列,另一个是用于接收过期消息的死信队列。 2. 将死信队列绑定到一个死信交换器上,并配置相应的路由键。 3. 发布一条具有 TTL 的消息至初始队列,在消息到期后会自动被转移到死信队列中。 此过程的具体操作步骤已在参考资料中有提及[^2]。 #### 示例代码展示 下面是一个基于 Python 的 Pika 库实现 RabbitMQ 延迟队列的例子: ```python import pika import json from datetime import timedelta def publish_delayed_message(channel, exchange_name, routing_key, message_body, delay_ms): """ Publish a delayed message to the RabbitMQ queue. :param channel: The RabbitMQ channel object :param exchange_name: Name of the dead-letter exchange :param routing_key: Routing key for the dead-letter queue :param message_body: Message content as dictionary :param delay_ms: Delay time in milliseconds """ # Convert message body to JSON string message_json = json.dumps(message_body) # Set up properties with expiration and delivery mode props = pika.BasicProperties( expiration=str(delay_ms), # Expiration is set in milliseconds delivery_mode=2 # Persistent messages ) # Publish the message to the initial queue channel.basic_publish(exchange='', routing_key='delay_queue', properties=props, body=message_json) # Establish connection to RabbitMQ server connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) channel = connection.channel() # Declare queues channel.queue_declare(queue='delay_queue', arguments={ 'x-dead-letter-exchange': 'dl_exchange', 'x-dead-letter-routing-key': 'dead_letter_routing' }) channel.exchange_declare(exchange='dl_exchange', exchange_type='direct') channel.queue_declare(queue='dead_letter_queue') # Bind dead letter queue to exchange channel.queue_bind(exchange='dl_exchange', queue='dead_letter_queue', routing_key='dead_letter_routing') # Example usage message_content = {"order_id": "1001", "status": "pending"} publish_delayed_message(channel, '', 'dead_letter_routing', message_content, 5000) # 5 seconds delay print("Message published successfully.") # Close connection connection.close() ``` 上述代码展示了如何创建带延迟特性的队列以及发送延迟消息的过程。其中 `expiration` 参数定义了消息存活时间,单位为毫秒;一旦该时间段结束,未消费的消息即转入指定的死信队列。 对于更高级别的需求,还可以考虑启用官方插件 rabbitmq_delayed_message_exchange 来进一步优化延迟队列的行为[^4]。 #### 设置消息优先级 如果希望在延迟队列的基础上增加对不同重要程度事件的支持,则可通过设置队列的最大优先级参数 (`x-max-priority`) 并结合每条消息单独赋予其优先权值的方式达成目标[^5]。 --- ### 性能与可靠性考量 采用 RabbitMQ 构建延迟队列能够显著提升业务场景下的灵活性和稳定性,特别是在电商领域常见的订单超时取消等功能模块里表现尤为突出[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值