youyichannel

志于道,据于德,依于仁,游于艺!

0%

MQ 的两个基础模式

「RocketMQ 整体设计架构」

下面介绍消息队列的两个不同的实现模式

  • 队列模式
  • 订阅-发布模式

队列模式

队列是一种数据结构,它的特性是先进先出。而消息队列直观来看就是消息排成了队列,被消费了,这个消息就出队了,后续的消息会顶上去。

一开始的消息队列就是这样设计的,生产者发送的消息被排成队列,然后消费者们竞争消费队列上的消息。

为什么称之为竞争?

因为按照队列的特性,消息被消费了等于出队,即从队列上被移除了,那么每条消息只会被一个消费者消费,因此消费者之间是竞争关系。

这个特性很符合互联网初期的需求,一条消息被消费了,自然就应该被移除,不应该堵在队头影响后续的消费消费。这个模式就是队列模式

这样的设计在早期没有问题,但是随着互联网的演进,系统的复杂性不断提升,随之而来的需求也更加繁琐。就像一条消息可能有很多消费者都感兴趣,但是他们之间又不是竞争消费的关系,即这些消费者都想要消费所有的消息。

那这个时候队列模式就不合适了,因为队列的设计是消息被消费了就出队,而出队了如何再被别的消费者消费呢?我们自然会想到把消息复制多份,也就是采用「多队列」的方式,将一条消息冗余到多个队列中,每个队列都包含全量的信息,这就满足了这些不是竞争关系的消费者们的需求。

但是这样的方式对于存储就不是很友好了,因为随着消费者的增加,队列会越来越多,冗余的消息也就越来越多。

如何解决?我们来看发布-订阅模式

发布-订阅模式

这个模式,很明显是生产者发布消息,消费者订阅消息,订阅的依据就是「Topic」。

发布-订阅模式想要实现的功能是:生产者向某个Topic发布消息,订阅这个Topic的消费者都可以接收到这条消息。从这个方面来看,发布-订阅模式完美契合一条消息可以被多个消费者同时消费的诉求。

如何实现呢?

这里就需要引入消息位置 offset的概念。我们的需求是消息可以被多个消费者消费,那么只需要维护每个消费者已经消费到的位置即可,每当消费者消费一条消息,消费位置就+1,然后消费者根据记录的消息位置消费对应的消息即可。这样就能够满足不同消费者消费同一条消息,且不影响他们之间的消费进度的需求。

如图,当消费者yy消费完 Topic-COC 第三条消息后,将位置+1记录下来就能够顺利访问第四条消息。

假设现在又来了一个消费者 dd,我们也不需要复制消息,只需要多记录一个dd的消息位置即可。

这样一来,即使消费者再多,对存储也没有什么影响。

问题:同一个消费者组内消费者如何消费消息呢?

难道让他们竞争同一个消费位置吗?那这样岂不是要等上一个消费者消费完了,组内其他消费者才能够消费下一条消息?这样效率就很低下了。

这就需要引入另一个概念了,队列(类比Kafka中的分区)

发往一个 Topic 的消息实际上不是在一个队列中,而是分布在多个队列中,这样属于一个消费者组的消费者可以专门负责主题中的一个队列。

然后,消费点位的记录维度就变成了「Topic-消费者组-队列」

这样一来就完美的解决了之前队列模式一条消息只能被一个消费者消费的问题,也实现了消费者组之间的消费互不影响,且消费者内多个消费者之间的消费也互不影响。

这个解决方案非常灵活,可以通过增加队列或者增加消费者的方式来调整消息消费的状态。

除此之外,还能实现重复消费或者跳过部分消息不消费的功能,就是通过修改消费位置就可以实现这个功能。