Java Spring和rabbitmq消息顺序

Java Spring和rabbitmq消息顺序,java,rabbitmq,spring-amqp,spring-rabbit,Java,Rabbitmq,Spring Amqp,Spring Rabbit,我正在开发一个应用程序,它接收按会话id分组的rest消息(会话1可以由2条消息组成,会话2可以由10条消息组成),并将它们发送到数据库。给定会话的消息内部具有相同的会话id 对于给定的会话,第一条消息应该先发送到数据库,然后再发送第二条,以此类推。在会话中,顺序非常重要 会话的顺序并不重要,我们可以混合它们的消息,例如,我们可以按此顺序将消息发送到数据库: 会话A消息1 会话B消息1 会话A消息2 会话C消息1 会话B消息2 会话A消息3 会话C消息2 我创建了10个rabbitmq队列。

我正在开发一个应用程序,它接收按会话id分组的rest消息(会话1可以由2条消息组成,会话2可以由10条消息组成),并将它们发送到数据库。给定会话的消息内部具有相同的会话id

对于给定的会话,第一条消息应该先发送到数据库,然后再发送第二条,以此类推。在会话中,顺序非常重要

会话的顺序并不重要,我们可以混合它们的消息,例如,我们可以按此顺序将消息发送到数据库:

  • 会话A消息1
  • 会话B消息1
  • 会话A消息2
  • 会话C消息1
  • 会话B消息2
  • 会话A消息3
  • 会话C消息2
我创建了10个rabbitmq队列。应用程序根据会话id选择队列:来自给定会话的所有消息都在同一队列中

每个队列有1个消费者,因此保证同一队列中的顺序

出于性能原因(以及流量增长),我们必须将队列数量设置得更高(节点创建100个队列)或部署应用程序的其他实例(10个节点,每个队列上有1个使用者,因此每个队列有10个使用者)

将队列数设置得更高并不困难,但我的做法有点难看,而且代码重复(见下文)。我需要更好的建议(当天我们需要1000人排队)

如果我们部署10个节点而不是1个节点,那么每个队列将有10个使用者,并且队列中消息的顺序将无法保证(因此会话a中的消息2可以在会话a中的消息1之前发送到数据库)

首选的解决方案是10节点方案,因为我们可以使其成为动态的,并且可以在需要时启动/停止docker中的节点

以下是我使用的依赖项:

    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-amqp</artifactId>
        <version>1.6.3.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-rabbit</artifactId>
        <version>1.6.3.RELEASE</version>
    </dependency>
目前,我用10个类创建了10个队列。以下是一个队列示例:

@Component
@RabbitListener(containerFactory = "myRabbitListenerContainerFactory", bindings = @QueueBinding(value = @Queue(value = "queue2", durable = "true"), exchange = @Exchange(type = "topic", value = "exchange2", durable = "true"), key = "key2"))
public class QueueGroup2Listener {
    @RabbitHandler
    public void processOrder(RequestMessage received) throws DataAccessResourceFailureException {
        process(received);
    }
}
我没有找到比在注释中使用不同的值(从1到10)创建10次这个类更好的方法

问题是: 如何在队列中添加使用者并保证给定会话中消息的顺序?我的意思是有10个消费者在排队。使用者A使用会话A中的消息1,因此其他使用者不应使用会话A中的其他消息

问题是: 如何使队列创建优于每个队列1个类

非常感谢

更新

这个问题的答案对我很有帮助 :我可以为每个会话创建一个队列(在本例中,下一个问题是rabbitmq可以同时管理多少个队列?)

在Gary的回答后更新

感谢回复,我尝试了以下方法,但应用程序启动时间非常长:

@Bean
public QueueMessageListener listener() {
    return new QueueMessageListener();
}


@Bean(name="exchange")
public Exchange exchange() {
    TopicExchange exchange = new TopicExchange(EXCHANGE, true, false);
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory());
    MessageListenerAdapter adapter = new MessageListenerAdapter(listener(), "processOrder");
    container.setMessageListener(adapter);
    admin().declareExchange(exchange);
    createQueues(exchange, QUEUE, numberOfQueues, BINDING_KEY, container, null, true);
    container.start();  // very very very long 
    return exchange;
}

private void createQueues(Exchange exchange, String queuePrefix, int numberOfQueues, String bindingPrefix,
        SimpleMessageListenerContainer container, Map<String, Object> args) {
    int length = 1;
    if(numberOfQueues > 1) {
        length = (int)(Math.log10(numberOfQueues - 1) + 1);
    }
    for (int i = 0; i < numberOfQueues; i++) {
        Queue queue = new Queue(queuePrefix + String.format("%0" + length + "d", i), true, false, false, args);
        container.addQueues(queue);
        admin().declareQueue(queue);
        Binding binding = BindingBuilder.bind(queue).to(exchange).with(bindingPrefix + i).noargs();
        admin().declareBinding(binding);
    }
}
@Bean
公共队列消息侦听器(){
返回新的QueueMessageListener();
}
@Bean(name=“exchange”)
公共交换{
TopicExchange exchange=新TopicExchange(exchange,true,false);
SimpleMessageListenerContainer容器=新的SimpleMessageListenerContainer(connectionFactory());
MessageListenerAdapter=newMessageListenerAdapter(listener(),“processOrder”);
setMessageListener(适配器);
管理员()申报交换(交换);
createQueues(交换、队列、numberOfQueues、绑定密钥、容器、null、true);
container.start();//非常长
换汇;
}
私有void createQueues(Exchange交换、字符串queuePrefix、int numberOfQueues、字符串bindingPrefix、,
SimpleMessageListenerContainer容器,映射参数){
整数长度=1;
如果(队列数>1){
长度=(int)(数学log10(numberOfQueues-1)+1);
}
对于(int i=0;i

如果我不调用start函数,则不会创建使用者。

您可以通过编程方式启动SimpleMessageListenerContainer,而不是使用声明性范例

您还可以使用
RabbitAdmin
以编程方式声明队列、绑定等

由于Spring AMQP缓存通道,因此无法保证在同一通道上发生两次发送(导致订单丢失的可能性很小);为了确保顺序,您需要在即将发布的2.0版本中使用新的
rabbitmplate.invoke()
方法。它将在同一通道上的调用范围内执行发送,从而保证顺序


如果您的发送代码是单线程的,这不是问题,因为在这种情况下始终使用相同的通道。

您可以通过编程方式启动
SimpleMessageListenerContainer
s,而不是使用声明性范例

您还可以使用
RabbitAdmin
以编程方式声明队列、绑定等

由于Spring AMQP缓存通道,因此无法保证在同一通道上发生两次发送(导致订单丢失的可能性很小);为了确保顺序,您需要在即将发布的2.0版本中使用新的
rabbitmplate.invoke()
方法。它将在同一通道上的调用范围内执行发送,从而保证顺序

如果您的发送代码是单线程的,这不是一个问题,因为在这种情况下将始终使用相同的通道

@Bean
public QueueMessageListener listener() {
    return new QueueMessageListener();
}


@Bean(name="exchange")
public Exchange exchange() {
    TopicExchange exchange = new TopicExchange(EXCHANGE, true, false);
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory());
    MessageListenerAdapter adapter = new MessageListenerAdapter(listener(), "processOrder");
    container.setMessageListener(adapter);
    admin().declareExchange(exchange);
    createQueues(exchange, QUEUE, numberOfQueues, BINDING_KEY, container, null, true);
    container.start();  // very very very long 
    return exchange;
}

private void createQueues(Exchange exchange, String queuePrefix, int numberOfQueues, String bindingPrefix,
        SimpleMessageListenerContainer container, Map<String, Object> args) {
    int length = 1;
    if(numberOfQueues > 1) {
        length = (int)(Math.log10(numberOfQueues - 1) + 1);
    }
    for (int i = 0; i < numberOfQueues; i++) {
        Queue queue = new Queue(queuePrefix + String.format("%0" + length + "d", i), true, false, false, args);
        container.addQueues(queue);
        admin().declareQueue(queue);
        Binding binding = BindingBuilder.bind(queue).to(exchange).with(bindingPrefix + i).noargs();
        admin().declareBinding(binding);
    }
}