繁体   English   中英

在Spring-AMQP中作用于403

[英]Acting on 403s in Spring-AMQP

我需要在不同的运行时中使用可变数量的使用者线程来保证使用者的专有性,这些线程在固定数量的队列中消耗(其中队列数量比使用者更多)。

我的一般想法是,我将让每个使用者线程尝试建立一个排他连接来清除队列,并且,如果它经过了给定的时间却没有从该队列接收到消息,则将其重定向到另一个队列。

即使暂时清除了队列,将来也有可能再次接收消息,因此不能简单地忘记该队列-相反,消费者应稍后再返回该队列。 为了实现这种轮换,我认为我将使用队列队列。 当使用者失败时,危险将是丢失对队列队列中队列的引用。 我认为这似乎可以通过以下方式解决。

本质上,每个使用者线程都在等待从队列队列中获取对队列(1)的引用的消息(A); 消息(A)最初仍未被确认。 使用者很高兴地尝试清除队列(1),并且一旦队列(1)在给定时间内保持为空,使用者就会从队列队列中请求新的队列名称。 收到第二条消息(B)和对新队列(2)的引用后,对队列(1)的引用作为新消息(C)放回队列的末尾,最后返回消息(A)被确认。

实际上,队列的交付至少一次且可能仅一次保证几乎让我在这里拥有了普通队列(1、2)的专有权,但是为了确保我绝对不会输引用队列,我需要确认消息(A) 之前将队列(1)重新发布为消息(C)。 这意味着,如果在将队列(1)重新发布为消息(C)之后但在确认(A)之前服务器发生故障,则队列中可能存在对队列(1)的两个引用,并且不再保证排他性。

因此,我需要使用AMQP的专用用户标志,这很好,但就目前而言,如果我收到队列“ 403 ACCESS REFUSED”,我也不想重新发布对队列的引用,以便重复参考文献不会大量增加。

但是,我使用的是Spring出色的AMQP库,但是我看不到如何使用错误处理程序来实现。 在容器上公开的setErrorHandler方法似乎没有出现“ 403 ACCESS REFUSED”错误。

我是否可以使用当前使用的框架在403s上执行操作? 或者,还有其他方法可以实现我需要的保证吗? 我的代码如下。

“监控服务”:

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Period;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpAuthenticationException;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

public class ListenerMonitoringService {

    private static final Logger log = LoggerFactory.getLogger(ListenerMonitoringService.class);

    private static final Period EXPIRATION_PERIOD = Period.millis(5000);

    private static final long MONTIORING_POLL_INTERVAL = 5000;
    private static final long MONITORING_INITIAL_DELAY = 5000;

    private final Supplier<AbstractMessageListenerContainer> messageListenerContainerSupplier;

    private final QueueCoordinator queueCoordinator;
    private final ScheduledExecutorService executorService;

    private final Collection<Record> records;

    public ListenerMonitoringService(Supplier<AbstractMessageListenerContainer> messageListenerContainerSupplier,
                                     QueueCoordinator queueCoordinator, ScheduledExecutorService executorService) {
        this.messageListenerContainerSupplier = messageListenerContainerSupplier;
        this.queueCoordinator = queueCoordinator;
        this.executorService = executorService;

        records = new ArrayList<>();
    }

    public void registerAndStart(MessageListener messageListener) {
        Record record = new Record(messageListenerContainerSupplier.get());

        // wrap with listener that updates record
        record.container.setMessageListener((MessageListener) (m -> {
            log.trace("{} consumed a message from {}", record.container, Arrays.toString(record.container.getQueueNames()));
            record.freshen(DateTime.now(DateTimeZone.UTC));
            messageListener.onMessage(m);
        }));

        record.container.setErrorHandler(e -> {
            log.error("{} received an {}", record.container, e);
            // this doesn't get called for 403s
        });

        // initial start up
        executorService.execute(() -> {
            String queueName = queueCoordinator.getQueueName();

            log.debug("Received queue name {}", queueName);
            record.container.setQueueNames(queueName);

            log.debug("Starting container {}", record.container);
            record.container.start();

            // background monitoring thread
            executorService.scheduleAtFixedRate(() -> {
                log.debug("Checking container {}", record.container);
                if (record.isStale(DateTime.now(DateTimeZone.UTC))) {
                    String newQueue = queueCoordinator.getQueueName();
                    String oldQueue = record.container.getQueueNames()[0];
                    log.debug("Switching queues for {} from {} to {}", record.container, oldQueue, newQueue);
                    record.container.setQueueNames(newQueue);

                    queueCoordinator.markSuccessful(queueName);
                }
            }, MONITORING_INITIAL_DELAY, MONTIORING_POLL_INTERVAL, TimeUnit.MILLISECONDS);
        });

        records.add(record);
    }

    private static class Record {
        private static final DateTime DATE_TIME_MIN = new DateTime(0);

        private final AbstractMessageListenerContainer container;
        private Optional<DateTime> lastListened;

        private Record(AbstractMessageListenerContainer container) {
            this.container = container;
            lastListened = Optional.empty();
        }

        public synchronized boolean isStale(DateTime now) {
            log.trace("Comparing now {} to {} for {}", now, lastListened, container);
            return lastListened.orElse(DATE_TIME_MIN).plus(EXPIRATION_PERIOD).isBefore(now);
        }

        public synchronized void freshen(DateTime now) {
            log.trace("Updating last listened to {} for {}", now, container);
            lastListened = Optional.of(now);
        }
    }
}

“队列队列”处理程序:

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.GetResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.Connection;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

private class MetaQueueCoordinator implements QueueCoordinator {

    private static final Logger log = LoggerFactory.getLogger(MetaQueueCoordinator.class);

    private final Channel channel;
    private final Map<String, Envelope> envelopeMap;
    private final RabbitTemplate rabbitTemplate;

    public MetaQueueCoordinator(ConnectionFactory connectionFactory) {
        Connection connection = connectionFactory.createConnection();
        channel = connection.createChannel(false);

        envelopeMap = new ConcurrentHashMap<>();
        rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setExchange("");
        rabbitTemplate.setRoutingKey("queue_of_queues");
    }

    @Override
    public String getQueueName() {
        GetResponse response;
        try {
            response = channel.basicGet("queue_of_queues", false);
        } catch (IOException e) {
            log.error("Unable to get from channel");
            throw new RuntimeException(e);
        }

        String queueName = new String(response.getBody());
        envelopeMap.put(queueName, response.getEnvelope());

        return queueName;
    }

    @Override
    public void markSuccessful(String queueName) {
        Envelope envelope = envelopeMap.remove(queueName);
        if (envelope == null) {
            return;
        }

        log.debug("Putting {} at the end of the line...", queueName);
        rabbitTemplate.convertAndSend(queueName);

        try {
            channel.basicAck(envelope.getDeliveryTag(), false);
        } catch (IOException e) {
            log.error("Unable to acknowledge {}", queueName);
        }
    }

    @Override
    public void markUnsuccessful(String queueName) {
        Envelope envelope = envelopeMap.remove(queueName);
        if (envelope == null) {
            return;
        }

        try {
            channel.basicAck(envelope.getDeliveryTag(), false);
        } catch (IOException e) {
            log.error("Unable to acknowledge {}", queueName);
        }
    }
}

ErrorHandler用于处理消息传递过程中的错误,而不是设置侦听器本身。

即将发生的此类异常将在即将发布的1.5版本中发布应用程序事件

它将在今年夏天晚些时候发布; 该功能目前仅在1.5.0.BUILD-SNAPSHOT中可用; 未来几周内将有一个候选发布版。

项目页面显示了如何从快照存储库中获取快照。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM