如何在死信函队列和特定情景中的TTL循环之后,将带有Spring的AMQP消息发布到停车场队列?

时间:2018-03-27 19:45:58

标签: java spring rabbitmq amqp spring-amqp

我想在我的应用程序中实现以下场景:

  1. 如果发生业务错误,邮件应该从incomingQueue发送到deadLetter队列并在那里延迟10秒
  2. 步骤编号1应重复3次
  3. 该消息应发布到parkingLot Queue
  4. Scenario to achieve

    Current RabbitMQ Queues setup

    我能够(请参阅下面的代码)在deadLetter队列中延迟消息一段时间。并且消息在传入的Queue和deadLetter队列之间无限循环。到目前为止一切都很好。

    主要问题:如何拦截流程并手动将消息(如步骤3中所述)路由到parkingLot队列以供以后进一步分析?

    第二个问题:我只能通过一次交换实现相同的流程吗?

    以下是我的两个课程的缩短版本:

    配置类

    @Configuration
    public class MailRabbitMQConfig {
    
        @Bean
        TopicExchange incomingExchange() {
           TopicExchange incomingExchange = new TopicExchange(incomingExchangeName);
            return incomingExchange;
        }
    
        @Bean
        TopicExchange dlExchange() {
            TopicExchange dlExchange = new TopicExchange(deadLetterExchangeName);
            return dlExchange;
        }
    
        @Bean
        Queue incomingQueue() {
    
            return QueueBuilder.durable(incomingQueueName)
                    .withArgument(
                            "x-dead-letter-exchange",
                            dlExchange().getName()
                    )
                    .build();
        }
    
        @Bean
        public Queue parkingLotQueue() {
            return new Queue(parkingLotQueueName);
        }
    
        @Bean
        Binding incomingBinding() {
            return BindingBuilder
                    .bind(incomingQueue())
                    .to(incomingExchange())
                    .with("#");
        }
    
        @Bean
        public Queue dlQueue() {
            return QueueBuilder
                    .durable(deadLetterQueueName)
                    .withArgument("x-message-ttl", 10000)
                    .withArgument("x-dead-letter-exchange", incomingExchange()
                        .getName())
                    .build();
        }
    
        @Bean
        Binding dlBinding() {
            return BindingBuilder
                    .bind(dlQueue())
                    .to(dlExchange())
                    .with("#");
        }
    
        @Bean
        public Binding bindParkingLot(
                Queue parkingLotQueue,
                TopicExchange dlExchange
        ) {
    
            return BindingBuilder.bind(parkingLotQueue)
                        .to(dlExchange)
                        .with(parkingLotRoutingKeyName);
        }
    }
    

    消费者类

    @Component
    public class Consumer {
    
        private final Logger logger = LoggerFactory.getLogger(Consumer.class);
    
        @RabbitListener(queues = "${mail.rabbitmq.queue.incoming}")
        public Boolean receivedMessage(MailDataExternalTemplate mailDataExternalTemplate) throws Exception {
    
            try {
                // business logic here
            } catch (Exception e) {
                throw new AmqpRejectAndDontRequeueException("Failed to handle a business logic");
            }
    
            return Boolean.TRUE;
        }
    }
    

    我知道我可以在Consumer类中为deadLetter Queue定义一个额外的侦听器:

    @RabbitListener(queues = "${mail.rabbitmq.queue.deadletter}")
    public void receivedMessageFromDlq(Message failedMessage) throws Exception {
        // Logic to count x-retries header property value and send a failed message manually
        // to the parkingLot Queue
    }
    

    但是它没有按预期工作,因为只要消息到达deadLetter队列的头部而不被延迟就会调用此侦听器。

    提前谢谢。

    编辑:我能够与@ArtemBilan和@GaryRussell帮助解决问题。主要的解决方案提示在他们对已接受答案的评论中。谢谢你们的帮助!您将在下面找到一个新图表,其中显示了消息传递过程以及Configuration和Consumer类。主要变化是:

    • 传入交换之间路由的定义 - >传入队列和死信交换 - > MailRabbitMQConfig类中的死信队列。
    • 通过手动将消息发布到Consumer
    • 中的停车场队列进行循环处理

    enter image description here

    配置类

    @Configuration
    public class MailRabbitMQConfig {
        @Autowired
        public MailConfigurationProperties properties;
    
        @Bean
        TopicExchange incomingExchange() {
            TopicExchange incomingExchange = new TopicExchange(properties.getRabbitMQ().getExchange().getIncoming());
            return incomingExchange;
        }
    
        @Bean
        TopicExchange dlExchange() {
            TopicExchange dlExchange = new TopicExchange(properties.getRabbitMQ().getExchange().getDeadletter());
            return dlExchange;
        }
    
        @Bean
        Queue incomingQueue() {
            return QueueBuilder.durable(properties.getRabbitMQ().getQueue().getIncoming())
                .withArgument(                 
                    properties.getRabbitMQ().getQueue().X_DEAD_LETTER_EXCHANGE_HEADER,
                    dlExchange().getName()
                )
                .withArgument(
                    properties.getRabbitMQ().getQueue().X_DEAD_LETTER_ROUTING_KEY_HEADER,
                    properties.getRabbitMQ().getRoutingKey().getDeadLetter()
                )
                .build();
        }
    
        @Bean
        public Queue parkingLotQueue() {
            return new Queue(properties.getRabbitMQ().getQueue().getParkingLot());
        }
    
        @Bean
        Binding incomingBinding() {
            return BindingBuilder
                .bind(incomingQueue())
                .to(incomingExchange())
                .with(properties.getRabbitMQ().getRoutingKey().getIncoming());
       }
    
        @Bean
        public Queue dlQueue() {
            return QueueBuilder
                .durable(properties.getRabbitMQ().getQueue().getDeadLetter())
                .withArgument(                      
                    properties.getRabbitMQ().getMessages().X_MESSAGE_TTL_HEADER,
                    properties.getRabbitMQ().getMessages().getDelayTime()
                )
                .withArgument(
                    properties.getRabbitMQ().getQueue().X_DEAD_LETTER_EXCHANGE_HEADER,
                    incomingExchange().getName()
                )
                .withArgument(
                    properties.getRabbitMQ().getQueue().X_DEAD_LETTER_ROUTING_KEY_HEADER,
                    properties.getRabbitMQ().getRoutingKey().getIncoming()
                )
                .build();
        }
    
        @Bean
        Binding dlBinding() {
            return BindingBuilder
                .bind(dlQueue())
                .to(dlExchange())
                .with(properties.getRabbitMQ().getRoutingKey().getDeadLetter());
        }
    
        @Bean
        public Binding bindParkingLot(
            Queue parkingLotQueue,
            TopicExchange dlExchange
        ) {
            return BindingBuilder.bind(parkingLotQueue)
                .to(dlExchange)
                .with(properties.getRabbitMQ().getRoutingKey().getParkingLot());
        }
    }
    

    消费者类

    @Component
    public class Consumer {
        private final Logger logger = LoggerFactory.getLogger(Consumer.class);
    
        @Autowired
        public MailConfigurationProperties properties;
    
        @Autowired
        protected EmailClient mailJetEmailClient;
    
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
        @RabbitListener(queues = "${mail.rabbitmq.queue.incoming}")
        public Boolean receivedMessage(
            @Payload MailDataExternalTemplate mailDataExternalTemplate,
            Message amqpMessage
        ) {
            logger.info("Received message");
    
            try {
                final EmailTransportWrapper emailTransportWrapper = mailJetEmailClient.convertFrom(mailDataExternalTemplate);
    
                mailJetEmailClient.sendEmailUsing(emailTransportWrapper);
                logger.info("Successfully sent an E-Mail");
            } catch (Exception e) {
                int count = getXDeathCountFromHeader(amqpMessage);
                logger.debug("x-death count: " + count);
    
                if (count >= properties.getRabbitMQ().getMessages().getRetryCount()) {
                    this.rabbitTemplate.send(
                         properties.getRabbitMQ().getExchange().getDeadletter(),
                         properties.getRabbitMQ().getRoutingKey().getParkingLot(),
                         amqpMessage
                    );
                    return Boolean.TRUE;
                }
    
                throw new AmqpRejectAndDontRequeueException("Failed to send an E-Mail");
            }
    
            return Boolean.TRUE;
        }
    
        private int getXDeathCountFromHeader(Message message) {
            Map<String, Object> headers = message.getMessageProperties().getHeaders();
            if (headers.get(properties.getRabbitMQ().getMessages().X_DEATH_HEADER) == null) {
                return 0;
            }
    
            //noinspection unchecked
            ArrayList<Map<String, ?>> xDeath = (ArrayList<Map<String, ?>>) headers
                .get(properties.getRabbitMQ().getMessages().X_DEATH_HEADER);
            Long count = (Long) xDeath.get(0).get("count");
            return count.intValue();
        }
    

1 个答案:

答案 0 :(得分:1)

要延迟消息在队列中可用,您应该考虑使用DelayedExchangehttps://docs.spring.io/spring-amqp/docs/2.0.2.RELEASE/reference/html/_reference.html#delayed-message-exchange

至于手动发送到parkingLot队列,这只是易于使用RabbitTemplate并使用其名称发送消息:

/**
 * Send a message to a default exchange with a specific routing key.
 *
 * @param routingKey the routing key
 * @param message a message to send
 * @throws AmqpException if there is a problem
 */
void send(String routingKey, Message message) throws AmqpException;

所有队列都通过名称作为路由键绑定到默认交换。