rabbitmq如何保证消息顺序消费

为什么要保证顺序消费?

要保证mq顺序消费的实际场景比较少见。假设有3个用户发送三个订单,这里每个订单要先后完成insert,update,delete操作,如果我们先执行delete就会出问题。

rabbitmq的队列是先进先出的原则,所以只要是发送给同一个队列的消息,都能保证消费时顺序一致;因此三个用户的三组订单操作要分别发送给三个队列来消费;

//创建三个队列对应的消费者
@Component 
@RabbitListener(queues = {"triple_queue_one"})
public class TripleQueueOneListener {
    @RabbitHandler
    public void handler(Message message){
        System.out.println("TripleQueueOneListener 消费"+message.getMsg());
    }
}
@Component 
@RabbitListener(queues = {"triple_queue_three"})
public class TripleQueueThreeListener {
    @RabbitHandler
    public void handler(Message message){
        System.out.println("TripleQueueThreeListener 消费"+message.getMsg());
    }
}
@Component 
@RabbitListener(queues = {"triple_queue_two"})
public class TripleQueueTwoListener {
    @RabbitHandler
    public void handler(Message message){
        System.out.println("TripleQueueTwoListener 消费"+message.getMsg());
    }
}
//发送消息
@GetMapping("/sendMsgToTripleQueue")
public String sendMsgToTripleQueue() {
    //这是我们将消息发给三个队列,来提高并发处理能力,查看消息处理结果
    for (int i = 1; i <= 3; i++) {
        Message message = new Message();
        message.setId(i);
        for (int j = 1; j <= 3; j++) {
            message.setGroupId(j);
            message.setMsg("订单操作id:" + i + " 用户id:" + j);
            if (j==1){
                rabbitTemplate.convertAndSend("triple_exchange","triple-one", message);
            }
            if (j==2){
                rabbitTemplate.convertAndSend("triple_exchange","triple-two", message);
            }
            if (j==3){
                rabbitTemplate.convertAndSend("triple_exchange","triple-three", message);
            }
        }
    }
    return "消息发送成功";
}
TripleQueueTwoListener 消费订单操作id:1 用户id:2
TripleQueueThreeListener 消费订单操作id:1 用户id:3
TripleQueueOneListener 消费订单操作id:1 用户id:1
TripleQueueTwoListener 消费订单操作id:2 用户id:2
TripleQueueThreeListener 消费订单操作id:2 用户id:3
TripleQueueOneListener 消费订单操作id:2 用户id:1
TripleQueueTwoListener 消费订单操作id:3 用户id:2
TripleQueueThreeListener 消费订单操作id:3 用户id:3
TripleQueueOneListener 消费订单操作id:3 用户id:1

可以看到消费者的执行顺序是不一定的,有时TripleQueueOneListener先执行,有时TripleQueueTwoListener先执行,这就是为什么要限制同一个用户的一组操作要发送给同一个队列的原因,因为在一个队列里就能保证消息的先进先出;

简称:一个队列只有一个消费者

但是在分布式系统中我们一般会对消费者设置集群部署,就是一个队列会有多个消费者实例,那我们如何保证一组信息只会有一个消费者处理呢?

为了方便测试,我创建一个消费者来同时处理3个队列的消息

@Component
@RabbitListener(id = "SingleActiveListener1",
                queues = {"triple_queue_one", "triple_queue_two", "triple_queue_three"})
public class SingleActiveListener {
    @RabbitHandler
    public void handler(Message message) {
        System.out.println("SingleActiveListener1 消费" + message.getMsg());
    }
}

消息消费结果

SingleActiveListener1 消费订单操作id:1 用户id:3
SingleActiveListener1 消费订单操作id:2 用户id:3
SingleActiveListener1 消费订单操作id:3 用户id:3
SingleActiveListener1 消费订单操作id:1 用户id:1
SingleActiveListener1 消费订单操作id:2 用户id:1
SingleActiveListener1 消费订单操作id:3 用户id:1
SingleActiveListener1 消费订单操作id:1 用户id:2
SingleActiveListener1 消费订单操作id:2 用户id:2
SingleActiveListener1 消费订单操作id:3 用户id:2

可以发现当所有队列只有一个消费者时就可以严格保证消息的先进先出

此时我们再创建一个消费者来模拟分布式系统中创建多个消费者实例来提供并发处理能力

@Component
@RabbitListener(id = "SingleActiveListener2",
                queues = {"triple_queue_one", "triple_queue_two", "triple_queue_three"})
public class SingleActiveListener2 {
    @RabbitHandler
    public void handler(Message message) {
        System.out.println("SingleActiveListener2 消费" + message.getMsg());
    }
}

此时每个队列都会有两个消费者实例,来看看消息消费结果

SingleActiveListener1 消费订单操作id:1 用户id:1
SingleActiveListener2 消费订单操作id:2 用户id:1
SingleActiveListener2 消费订单操作id:2 用户id:2
SingleActiveListener1 消费订单操作id:1 用户id:2
SingleActiveListener2 消费订单操作id:2 用户id:3
SingleActiveListener1 消费订单操作id:1 用户id:3
SingleActiveListener1 消费订单操作id:3 用户id:2
SingleActiveListener1 消费订单操作id:3 用户id:3
SingleActiveListener1 消费订单操作id:3 用户id:1

可以看到用户id为2的操作发生了乱序的问题,id=2的订单操作发生在了id=1之前。那么如何解决呢?

这时候就要用到我们的单活模式队列来保证每个队列存在多个消费者实例,但是只会有一个实例起作用

我们只需要在创建队列时指定队列属性即可

@Configuration
public class TripleQueueConfig {
    @Bean
    public DirectExchange directExchange(){
        DirectExchange directExchange = new DirectExchange("triple_exchange");
        return directExchange;
    }

    @Bean
    public Queue tripleQueueOne(){
        Queue queue = creatQueue("triple_queue_one");
        return queue;
    }

    @Bean
    public Queue tripleQueueTwo(){
        Queue queue = creatQueue("triple_queue_two");
        return queue;
    }
    @Bean
    public Queue tripleQueueThree(){
        Queue queue = creatQueue("triple_queue_three");
        return queue;
    }

    @Bean
    public Binding bindingOne(){
        return BindingBuilder.bind(tripleQueueOne()).to(directExchange()).with("triple-one");
    }

    @Bean
    public Binding bindingTwo(){
        return BindingBuilder.bind(tripleQueueTwo()).to(directExchange()).with("triple-two");
    }
    @Bean
    public Binding bindingThree(){
        return BindingBuilder.bind(tripleQueueThree()).to(directExchange()).with("triple-three");
    }

    private Queue creatQueue(String name) {
        // 创建一个 单活模式 队列
        HashMap<String, Object> args = new HashMap<>();
        args.put("x-single-active-consumer", true);
        return new Queue(name, true, false, false, args);
    }

}

此时可以看到mq将一个消费者标记为了single active,表示只有这一个消费者起作用,另一个标记为了waiting

查看消费结果:

SingleActiveListener1 消费订单操作id:1 用户id:3
SingleActiveListener1 消费订单操作id:2 用户id:3
SingleActiveListener1 消费订单操作id:3 用户id:3
SingleActiveListener1 消费订单操作id:1 用户id:2
SingleActiveListener1 消费订单操作id:2 用户id:2
SingleActiveListener1 消费订单操作id:3 用户id:2
SingleActiveListener1 消费订单操作id:1 用户id:1
SingleActiveListener1 消费订单操作id:2 用户id:1
SingleActiveListener1 消费订单操作id:3 用户id:1

可以看到此时就和只配置一个消费者的情况一样了

那么问题来了

我为了提高并发处理能力创建了多个消费者,但是呢我为了保证消息消费顺序我又只能让一个消费者起作用

emmmm.......

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值