目录
- 队列模式
- 有什么设计的问题?
- 发布订阅模式
- 生产者如何确认消息发往哪个队列?
- 总结
队列模式
我们都知道队列是一种数据结构吗,它的特性是先进先出,就跟我们平时在食堂打饭排队一样,排在前面的同学打完饭了就走了,后面的同学顶上去打饭。
而消息队列,直观来看就是信息排成了队列,被消费了,这个消息就出队了,后续的消息顶上。
一开始的消息队列就是这样设计的,生产者发送的信息被排成队列,然后消费者们竞争消费队列上的信息。
为什么称之为竞争?
按照队列的特性,消费被消费了就等于出队,即从队列上被移除了,那么每条信息只会被一个消费者消费,因此消费者之间是竞争关系。
这个特性很符合初期的需求,一个消息被消费了,自然就应该被移除,不会影响后续的消息被消费。
有什么设计的问题?
刚刚讲的就是队列模式。这样的设计在早期没问题,但是随着互联网演进,系统的复杂性不断提升,随之而来的需求也更加繁琐。
就像一个消息可能有很多消费者都感兴趣,但是他们之间又不是竞争消费的关系,即这些消费者都想消费所有的消息。
这个时候队列模式是不是就不合适了?因为队列的设计是消息被消费了就出队,而出队了如何再被别的消费者消费呢?
很自然我们会想到把消息复制成多份,也就是多队列。
这样一条信息被冗余到多个队列中,每个队列都包含全量的信息,这就满足了这些不是竞争关系的消费者们的需求。
但这样的方式也有问题:对存储不是特别很友好了,因为随着消费者们的增加,队列会越来越多,冗余的消息也会越来越多。
于是逐步推进,出现了发布-订阅模式。
发布订阅模式
生产者发布消息,消费者订阅消息,订阅的依据是什么?就是我们上篇文章提到的Topic(主题)。
生产者发布消息,消费者订阅消息,订阅的依据是啥?就是我上篇文章提到的Topic(主题)。
发布-订阅模式想要实现的功能是:比如我往Topic-LOL这个主题发布消息,那么订阅了这个主题的消费者都能收到这个消息,我往Topic-DOTA这个主题发布消息,那么订阅了DOTA主题的消费者都能收到DOTA相关的消息。
从概念上,发布-订阅模型完美契合一个消息可以被多个消费者同时消费的需求。
那具体是如何实现的呢?
这里就需要引入**消息位置(offset)**的概念,这个既念你可以类比理解为数组的下标。
我们的述求是消息可以被多个消费者消费,那么只需维护每个消费者已经消费到的位置,每当消费者消费一条消息,消费位置就+1,然后消费者根据记录的消息位置去消费对应的数据即可。
就跟遍历数组一样,下标+1来访问后面的数据。这样就能满足不同消费者消费同一条消息,且不影响他们之间的消费进度的需求。如下图:
当小火龙消费完Topic-LOL三条消息后,将位置+1,也就可以顺利访问第四条消息了。
假如后来又来了一个妙蛙种子,我们当然也不需要再复制消息了,只需要多一个妙蛙种子的消息位置记录即可。
这样即使消费者很多,对存储的也没什么影响。初步来看问题得以解决,
但是还记得我们上篇文章提到的消费者组的概念吗?就杰尼龟一个人忙不过来所以组成了自己的小团队,一个消费者单独消费Topc的速度有限,于是乎形成了消费组的概念,消费组内的消费者共同分担消费Topc中信息。
那么问题来了。
同一个消费组内的消费者如何消费消息呢?让他们竞争同一个消费位置吗?那岂不是需要等上一个消费者消费完了,组内其他消费者才能消费下一条消息?
这样效率就很低了。
所以这里还需要引入一个概念,在RokcetMQ中叫队列(这个和数据结构的概念要区分),在Kafka中叫分区。
可以看到,发往一个Toc的消息,实际上不是在一个队列里,而是分布在多个队列中。这样属于一个消费组的消费者们可以专门负责主题里面的一个队列
然后我们消费点位的记录维度就变成了Topc-消费组-队列,比如现在一共有两个主题,分别是 Topic-LOL、Topic-DOTA,每个主题都有两个队列(分区)。
这样一来,我们就完美解决了之前队列模式一个消息只能被一个消费者消费的问题,也实现了消费组之间的消费互不影响,且消费组内多个消费者之间的消费也互不影响。
这个方案非常灵活,加队列同时加消费者的处理方式就是消息堆积的一个解决办法之一(面试常问,别急后续我会专门出文章讲解这一块内容)
至此,我们就清晰了企业级消息队列实现的发布-订阅模式的核心原理:即Topic下分队列(分区),然后维护每个消费组在每个Topic下每个队列的消息位置,以消息位置(offset)来控制消息消费的进度。
消息位置的灵活性不仅仅能区分不同消费组或者说消费者们的消费进度,还能实现重复消费或者跳过部分消息不消费的功能。比如小火龙-1已经消费到Topic-LOL-队列1-20,即第20条消息,但是小火龙-1不小心把之前关于 Topc-LOL的消费得到的结果数据弄丢了,如果按照队列模式那就找不到消息了,因为消息已经出队了没了。而在发布-订阅模式中,我们仅需把这个消息位置变更成Topic-LOL-队列1-0,这样又可以让小火龙-1重新消费,只需要简单地改一条数据就能实现这样功能。
生产者如何确认消息发往哪个队列?
现在再让我们将视线从消费者转到生产者。
一个Topic里面有多个队列,那么生产者怎么知道要发往哪个队列?
一个很简单的办法就是轮询,比如生产者-A,要往Topic-LOL发送信息,那么第一条发给队列-1,第二条发给队列-2,第三条发给队列-3,第四条再发给队列-1如此反复即可。
这样每个队列的信息量会很平均,对应的消费者的工作量也很均衡。
当然也可以指定发往某个队列,比如有关永劫无间的信息都发往队列-1,有关英雄联盟的消息都发往队列-2,有关双人成行的消息都发往队列-3.
有关于这块的内容,我们后续再详谈,现在大概了解即可。
总结
今天我们介绍了消息队列的两个消息模式,分别是:队列模式和发布-订阅模式。
后续我们会介绍常见的MQ中间件的特性,现在打好基础即可。
希望读者着重掌握发布-订阅模式,这对后续进阶的消息负载均衡,顺序消息等很关键。