有哪些排队机制可以实现循环队列?

时间:2016-10-07 09:40:54

标签: java amqp

我有多个任务生成器可以将工作添加到队列中。我也有多个消费者以该队列为食。由于这些队列是FIFO,因此它们按照添加的顺序出列。

在我的方案中,任务从HTTP请求添加到队列中。每个任务都与一个帐户相关联,并且没有速率限制。因此,可以让来自一个帐户的任务充斥消息队列。

为了解决这个问题,我一直在寻找一个队列实现,它允许我以循环方式处理来自多个帐户的排队任务,以实现公平。

我目前使用Redis并使用一些Lua脚本来模拟循环队列,但是想知道是否有任何现有的排队拓扑可以实现这一目标?

5 个答案:

答案 0 :(得分:4)

我通常这样做:

  • 不是将任务直接放入工作队列,而是为每个帐户创建单独的任务队列。每个请求都将一个任务放入其帐户队列,当帐户队列从空变为非空时,将帐户队列放入全局工作队列

  • 当工作准备好进行更多工作时,工作人员会从工作队列中获取帐户队列。当一个工作人员占用一个帐户队列时,它会取出第一个任务,而该工作人员会立即将该帐户队列放回工作队列的末尾(如果它不为空)。然后工人执行任务。

使用此系统,每个帐户队列最多只能在工作队列中一次,所有具有相关工作的帐户在工作队列中均等显示

这很容易实现,但是你必须要小心检测何时必须将一个帐户队列放入工作队列,因为可能有两个线程同时做出这个决定,你不要我希望帐户队列进入两次。

我这样简单:

  • 在每个帐户队列中都有一个原子布尔值,用于跟踪它是否在工作队列中。在将帐户队列出列后,工作人员立即将此设置为false。如果有人发现帐户队列非空,他们可以尝试将此布尔值CAS设置为true,如果成功则将帐户队列放入工作队列。

  • 当帐户队列为空时,很可能会进入工作队列。确保这是无害的 - 如果工作人员未能从帐户队列中获取任务,则应该忘记它并从工作队列中获取新的帐户队列。

答案 1 :(得分:2)

使用RabbitMQ Direct ExchangeSpring AMQP,您可以实现排队拓扑,该拓扑为连接到单个交换的每个帐户保留一个队列。使用帐户名称作为路由密钥并将单个消费者绑定到多个队列,将消息发送到交换机,消费者将收到消息round robin (see "Direct exchanges and load balance")。< / p>

这个设置的问题是,你最终可能会有相当多的队列(每个帐户一个),至少在我的实现中(在下面作为简单的Spring Boot应用程序附加),你将不得不“重启”每次有新帐户进入时都会使用,因为这意味着您有一个新的队列来连接使用者。不知道,这种尺度/表现是否很好。检查this post for the maximum number of queues in RabbitMQ以及这是否会对您产生影响。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = RoundRobin.RoundRobinQueueConfiguration.class)
public class RoundRobin {

    private static final String EXCHANGE = "round-robin-exchange";

    private final List<String> tasks = Arrays.asList(   // account(a):task(t) where t holds the expected order of consumption
            "a1:t1", "a2:t2", "a3:t3",                  // make sure, a queue for every account (a) exists
            "a1:t4", "a1:t7", "a1:t9", "a1:t10",        // add "many" tasks (t) for account 1
            "a2:t5", "a2:t8", "a3:t6");                 // add further tasks for other accounts, such that a1 has to "wait"

    private final List<String> declaredQueues = new ArrayList<>();

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RabbitAdmin rabbitAdmin;

    @Autowired
    private DirectExchange directExchange;

    @Autowired
    private SimpleMessageListenerContainer listenerContainer;

    @Test
    public void enqueuedTasksAreProcessedRoundRobin() {
        tasks.forEach(task -> {
            String[] accountAndTask = task.split(":");
            declareQueue(accountAndTask[0]);
            rabbitTemplate.convertAndSend(accountAndTask[0], accountAndTask[1] + " from account " + accountAndTask[0]);
        });
    }

    private void declareQueue(String routingKey) {
        if (!declaredQueues.contains(routingKey)) {
            Queue queue = new Queue(routingKey);
            rabbitAdmin.declareQueue(queue);
            rabbitAdmin.declareBinding(BindingBuilder.bind(queue).to(directExchange).with(routingKey));
            listenerContainer.stop();
            listenerContainer.addQueues(queue);
            listenerContainer.start();
            declaredQueues.add(routingKey);
        }
    }

    @Configuration
    public static class RoundRobinQueueConfiguration {

        @Bean
        public ConnectionFactory connectionFactory() {
            return new CachingConnectionFactory("localhost");
        }

        @Bean
        public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
            RabbitTemplate template = new RabbitTemplate(connectionFactory);
            template.setExchange(EXCHANGE);
            return template;
        }

        @Bean
        public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
            return new RabbitAdmin(connectionFactory);
        }

        @Bean
        public DirectExchange directExchange(RabbitAdmin rabbitAdmin) {
            DirectExchange directExchange = new DirectExchange(EXCHANGE);
            rabbitAdmin.declareExchange(directExchange);
            return directExchange;
        }

        @Bean
        public SimpleMessageListenerContainer simpleMessageListenerContainer(ConnectionFactory connectionFactory, RabbitAdmin rabbitAdmin) {
            Queue queue = new Queue("dummy-queue"); // we need a queue to get the container started...
            rabbitAdmin.declareQueue(queue);
            SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
            container.setMessageListener(new RoundRobinMessageListener());
            container.setQueues(new Queue("dummy-queue"));
            container.start();
            return container;
        }

    }

    public static class RoundRobinMessageListener implements MessageListener {

        @Override
        public void onMessage(Message message) {
            System.out.println("Consumed message " + (new String(message.getBody())));
        }

    }

}

在这个例子中任务的数量是任意的 - 但是我想“添加预期的”顺序以查看输出是否符合我们的预期。

测试的输出是:

Consumed message t1 from account a1
Consumed message t2 from account a2
Consumed message t3 from account a3
Consumed message t4 from account a1
Consumed message t5 from account a2
Consumed message t6 from account a3
Consumed message t7 from account a1
Consumed message t8 from account a2
Consumed message t9 from account a1
Consumed message t10 from account a1

我猜你想要的是......

答案 2 :(得分:1)

我知道版本5.1中的WebSphere(非常旧)提供了这样的Queue,单个Queue可以提供子队列服务,即在您的情况下,您将为每个客户端创建一个子队列,然后基本上可以在圆形中询问-robin like like每个子队列用于下一个任务。但我不知道细节,一般不会推荐WebSphere(从经验谈起)。但我想以编程方式可以维护一个队列列表或一个队列队列,其中较低级别的每个队列代表来自特定客户端的任务队列。然后,您可以使用自己的逻辑从正确的队列中按票价顺序执行任务。当然,您必须管理您的队列,即清理空队列,并在收到新任务时检查此客户端是否已有专用队列,并相应地将您的任务添加到新队列或现有队列。

答案 3 :(得分:1)

任何打包的解决方案都会带来很多额外的开销。我相信这是pattern you want to focus on。例如,RabbitMQ有一个路由队列解决方案。还有ActiveMQ supports this pattern

我会自己写,但这并不难。

答案 4 :(得分:0)

我想提出Guava Multimap的用法:

import java.util.LinkedHashSet;

public class QueueTest {
    public static void main(String[] args) {
        TreeMultimap<String, String> multimap = TreeMultimap.create();
        multimap.put("c1", "TaskC11");
        multimap.put("c1", "TaskC12");
        multimap.put("c1", "TaskC13");
        multimap.put("c2", "TaskC21");
        multimap.put("c3", "TaskC31");

        while (multimap.size() > 0) {
            for (String customer : new LinkedHashSet<>(multimap.keySet())) {
                String taskToProcess = multimap.get(customer).pollFirst();
                System.out.println(taskToProcess);
            }
        }
    }
}

结果:

TaskC11
TaskC21
TaskC31
TaskC12
TaskC13

您还可以为每个客户添加自定义比较器以进行优先级管理。