代码之家  ›  专栏  ›  技术社区  ›  Dherik

在第一次重试时执行操作,以将消息保存到数据库中

  •  0
  • Dherik  · 技术社区  · 8 年前

    我试图将到达数据库上RabbitMQ队列的每条消息都保存下来,只是为了记录日志。之后,需要正常处理消息。

    问题是:此队列的重试策略配置为 RetryOperationsInterceptor ,并且每次处理消息时出现错误,都会重新查询并再次处理该消息。保存消息的逻辑是在读取队列的侦听器中,因此,我没有只在数据库中保存一条消息,而是 3 (我配置的重试次数)。

    查看我的 RetryOperationsInterceptor :

    @Bean
    public RetryOperationsInterceptor defaultRetryOperationsInterceptor() {
        return RetryInterceptorBuilder.stateless()
                .maxAttempts(3) 
                .backOffOptions(2000, 2.0, 10000)
                .build();
    }
    

    集装箱工厂:

    @Bean(name = FACTORY_CONTAINER_NAME)
    public SimpleRabbitListenerContainerFactory factoryQueueExample(ConnectionFactory connectionFactory,
                                                                            RetryOperationsInterceptor defaultRetryOperationsInterceptor) {
    
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(getMessageConverter());
        factory.setDefaultRequeueRejected(false);
    
        Advice[] adviceChain = {defaultRetryOperationsInterceptor};
        factory.setAdviceChain(adviceChain);
    
        return factory;
    }
    

    队列侦听器:

    @Slf4j
    @Component
    @AllArgsConstructor
    public class MessageListener {
    
        private final MessageRepository messageRepository;
        private final MessageService messageService;
    
        @RabbitListener(queues = MessageConfiguration.QUEUE,
            containerFactory = MessageConfiguration.FACTORY_CONTAINER_NAME)
        public void process(SomeMessage someMessage) {
    
            messageRepository.save(someMessage); // transform to a entity and save on database
            messageService.process(someMessage); // process message
        }
    }
    

    我不知道是否是相关信息,但此队列还关联了一个DLQ。重试后,消息将转到DLQ队列。

    我的想法是在重试拦截器中找到一些可以在第一次尝试时调用服务的东西,以便只保存一次消息。

    我也愿意用其他方法来解决这个问题,比如将尝试次数与消息一起保存,只是为了表明不是在数据库中保存重复的消息,而是在不同的尝试中保存相同的消息。

    2 回复  |  直到 8 年前
        1
  •  1
  •   Artem Bilan    8 年前

    请注意如何应用重试:

    Advice[] adviceChain = {defaultRetryOperationsInterceptor};
    factory.setAdviceChain(adviceChain);
    

    就像你可以自己写一样 MethodInterceptor 写入数据库。 当您按顺序定义此自定义建议时 之前 那个 defaultRetryOperationsInterceptor ,DB save将只调用一次,因为这样的操作将在重试之外甚至之前发生。

    有关AOP的更多信息,请参见文档: https://docs.spring.io/spring/docs/5.0.6.RELEASE/spring-framework-reference/core.html#aop

        2
  •  0
  •   Dherik    8 年前

    首先,我要感谢 Artem 和 Garry 帮助我解决问题。

    至少有四种解决方案:

    1. 这个 Advice 解决方案(参见Garry的 answer )
    2. 计数器来自 RetrySynchronizationManager
    3. 这个 messageId 解决方案
    4. 使用statefull RetryOperationsInterceptor

    计数器来自 RetrySynchronizationManager 选项可轻松用于我的案例:

    int attempts = RetrySynchronizationManager.getContext().getRetryCount();
    boolean canSaveOnDatabase = attempts == 0;
    

    这个 消息ID 选项是发件人发送唯一标识该邮件的ID所必需的。因此,我可以在数据库中保存并使用这些信息,以了解消息是否已经持久化。可以对此进行配置 by the MessagePostProcessor 打电话的时候 send 方法来自 RabbitTemplate 。

    最后一个选项,带statefull RetryOperationsInterceptor 我可以找一下头球 @Header(AmqpHeaders.REDELIVERED) boolean redelivered 在 RabbitListener 。如果使用无状态 RetryOperationsInterceptor (我的案例)并且此选项还需要 消息ID 。

    推荐文章