分布式环境下,保证消息的有序性是一个经典的问题,在分布式环境的很多业务场景中都会遇到该问题。下面让我们从简单到复杂,一步一步分析。
当只有一个生产者一个消费者的时候,这个问题是不存在的。如下所示:
|
|
真正的生产环境下,肯定不会是这么简单的,因为该模型的处理能力真的很有限。一般生产环境的模型应该是如下图所示:
|
|
消息定义: 每个id下有序号分别为1到n的消息。比如xxx_1、xxx_2、xxx_3等分别是id为xxx的序号分别为1 2 3的三条消息。
在该场景下保证消息有序的方案如下:
- 方案一: 保证某个id的消息只会被分发到特定的消费者
- 方案二: 使用分布式锁和带有序列信息的消息
方案一的原理很简单,具体实施的时候需要在队列与消费者之间增加一层消息分发模块,保证某一类消息只会被分发到特定的消费者。该方案的缺点:1.使用某些消息队列服务比如RabbitMQ的时候,实现困难 2.不能充分利用多个消费者的能力,比如某些消费者处于饥饿状态
方案二需要生产者和消费者互相配合来完成。
分布式锁保证某一类消息只能同时被一个消费者消费。只有分布式锁还不足以保证消息的有序消费,比如,生产者生产了xxx_1 xxx_2 xxx_3三条消息,虽然消息是按照如上顺序分发的,但是由于网络等原因导致xxx_2消息被提前消费,这样消息就乱序了。
如果消费者在消费xxx_2时能判断出xxx_1还没有被消费,那么就可以采取一些措施来保证消息的有序消费。比如将xxx_2消息重新放到队列里。具体实施的时候需要生产者做一些配合,比如将消息的格式改为如下xxx_1_0 xxx_2_1 xxx_3_2。这样xxx代表id号 1代表消息序号 0代表前一个消息的序号。
该方案的优点:充分利用每个消费者的能力。缺点:实现复杂,依赖分布式锁
方案二的优化版:根据消息的特点,可以做一些优化。比如有如下两种消息序列,时间从1秒开始计时。第一行是某个id的消息完全有序。第二行是某个id的消息区间内有序。
|
|
第一种方案需要在缓存里一直维护id与序号之间的关系,并且不知道什么时候该删除该对应关系,给缓存增加了压力。
第二种方案可以给缓存设个有效时间。这样既能保证某区间段内消息有序,又能减少缓存的压力。