0 Github
1 面试题
如何保证消息的顺序性?
2 考点分析
在MQ消息队列相关面试中,消息顺序性几乎是高频必考题,也是面试官非常关注的核心问题之一。它主要考察两点:第一,你是否真正理解消息有序消费的概念;第二,当业务场景需要保证消息顺序时,你是否具备可落地的解决方案。因为在真实的生产环境中,消息顺序错乱是非常常见且影响很大的问题,并不是单纯的理论题。
3 详解
3.0 案例
先看一个非常典型的业务场景:MySQL binlog 同步系统,每天需要同步上亿条数据。
在MySQL中,针对同一条数据执行增删改操作时,往往会依次产生3条binlog。此时,这3条binlog发送到MQ后,消费端必须按照原始顺序依次消费和执行,对吧?
否则,原本正确的执行顺序是:增加->修改->删除,结果消费时一旦顺序错乱,就可能变成:删除->修改->增加。这样整个数据状态就会被彻底打乱。
数据同步到下游系统后,原本最后一步应该是删除,结果因为消息顺序颠倒,数据反而被保留下来,最终导致同步结果错误。这类问题在实际业务中影响非常严重,绝不能忽视。
3.1 顺序错乱的场景
3.1.1 rabbitmq
先看RabbitMQ的典型场景:一个队列对应多个消费者。当消息进入队列后,多个消费者会并发拉取并处理消息,这种情况下消息消费顺序天然就无法严格保证,很容易出现顺序错乱。
3.1.2 kafka
再看Kafka。假设是一个Topic、一个Partition、一个Consumer,表面上看消息顺序似乎没有问题。但真正的风险点在Consumer内部:如果消费者使用多线程并发处理消息,那么线程执行速度不同,最终处理顺序就可能发生变化,因此同样会导致消息乱序。
3.2 保证消息的顺序性
3.2.1 rabbitmq
针对RabbitMQ,常见的解决思路主要有两种。
方案一:拆分队列。让一个队列只对应一个消费者,这样消息按顺序进入队列,也能按顺序被消费。缺点是需要额外拆分和维护更多队列,系统管理成本会更高一些。
方案二:一个队列只配一个消费者。然后在消费者内部通过内存队列进行排队,再将已经排好顺序的消息分发给底层不同的Worker处理。这样可以确保每个Worker处理自己对应的那部分消息时保持有序,从而实现整体上的顺序消费。
3.2.2 kafka
Kafka中保证消息顺序性的方案也很经典:一个Topic、一个Partition、一个Consumer,同时要求Consumer内部使用单线程拉取和顺序消费消息。然后在Consumer内部维护N个内存队列,再由N个线程分别处理各自对应的内存队列。这样既能保证同一条业务链路上的消息有序,又能兼顾一定的并发处理能力。
