消息队列学习笔记 - 2

消息队列的模型

​ 在我一开始学习消息队列的时候,有些概念没有搞得很清楚,趁着今天早下班来梳理一下。

​ 在消息队列中,什么是队列,什么是主题?他们之间有什么区别呢?还有它是怎么做的发布 - 订阅模型

队列

​ 在早期的消息队列中,是用一个“队列(Queue)”来存储消息的。这里包含一个关键点,就是消息是按照到达服务器的顺序进行存储的,也是按照这个顺序来进行消费的,即先来先消费

image-20211125221456438

​ 如上图,生产者(上游服务)把消息推送到消息队列中去,这样消费者(下游)就能对这些消息进行处理。如果有多个消费者,并发地往同一个队列里面推送消息,那么其实这也还好问题不算太大。只要在消息队列上做一些操作,就能避免并发写带来的问题(比如说队列上加分布式锁,或者仿照GFS保证每次append都是原子的)。

​ 对于消费者并发,其实也可以仿照上述的做法,这样能避免消费者之间的并发问题。但是,这又带来了另外一个问题,这样会导致,一条消息只能被一个消费者消费。然而,很多场景,我们希望一条消息能够被多个系统所共享。

image-20211125222902977

​ 如图,订单系统生成了一条订单消息,而其他三个消费者系统之间并没有依赖关系,几乎可以说是独立的。那么,一条消息按道理来说同时被三个系统消费,是最好的选择。但是这时候,单队列就不能满足这个需求了。

​ 一个比较一般的做法,就是为某个系统都创建一个队列。这时候,一条消息就会复制成多分然后投放到这些队列中去。这显然是一种十分笨拙的方法。

​ 为了解决这个问题,出现了一种模型发布 - 订阅模型


主题 , 发布 - 订阅模型

​ 为了解决这个问题,演化出了一种模型 — 发布 - 订阅。在该模型里面,发送消息的称为发布者。存储消息的容器称为主题(TOPIC)。消费消息的称为订阅者

​ 如果消费者想要从消息队列上得到消息,那么在接受消息之前,它需要订阅该主题。我个人理解,订阅是指在队列上,设置一个“专属的offset”。这样,队列中维护着不同订阅者的offset。

​ 每个订阅者,消费完一个消息后,队列上对应的offset就会移动到下一条消息的地方。这就解决了单队列,消息不能被共享的问题。在这个模型中,不同订阅者的offset是独立的,消费完后消息也只是逻辑上出队。实际上并没有出队,也不影响别的队列的offset。


RocketMQ的模型

发布-订阅的缺陷

​ 上面提到的发布 - 订阅模型,虽然解决了消息共享的问题,但在实际生产中仅仅依赖该模型的话,效率仍然是不够高的。因为,主题本质上仍就是单队列。这就让整个系统的并发性能不太行了。

​ 为什么这样说呢?其实是下面这个原因:

  • 为了防止消息不丢失,消息队列都有”请求 - 确认”机制

​ 这个机制到来了一个问题。offset,只有收到消息成功消费的确认,该offset才能进行移动。不然会出现,很奇怪的现象。

​ 比如说,发布者发布了两条有顺序关系的消息到主题上。消息1推送给订阅者,但是在网络中丢失了。但是主题并不知道,而且还给订阅者推送消息2。这导致了,订阅者只收到了消息2,但是消息2和消息1是有顺序关系的,因此会导致订阅者的处理逻辑出现错误。

​ 正是这个原因,订阅了“订单消息主题“的支付系统,该系统中只能有一个实例。即使多加了实例,也不能提高系统的效率。这就难以让人接受了。


主题多队列模型

​ 为了在发布 - 订阅模型的基础上提高,消息队列的并发度。能够让一个订阅者上能够部署多个实例,RocketMQ的设计者在一个主题下面配置了多个队列。这些队列的功能都是一样的,都是收发消息。

​ 发布者,发布消息的时候,通过路由规则把消息推送到主题下的某个队列中(通常是hash)。

image-20211125234415299

​ 因为有了多队列,订阅者也可以有多个实例,称为订阅者组。一般来说,订阅者组的实例数量最好是等于主题中队列的数量。

​ 在“主题-单队列”的模型中,因为ack机制,导致了主题上订阅者不能有多个实例。在“主题-多队列”模型中,这个核心问题(ack带来的消息顺序问题)仍旧没有改变,它仅仅是在主题层面上扩充了队列,提高了并发性能。并没有改变,一个队列对应一个实例的要求。

​ 因此,订阅者组中,实例对象太少不能完全利用系统的性能;实例数量太多,必定会有实例被闲置。

image-20211125235549490

​ 在这个模型中,有几个值得注意的点:

(1)一条消息在订阅组中是竞争关系

1
2
3
结合上面提到的,发布者的路由。一条消息,会路由到主题中的某一个队列,不会出现在多个队列中。
而主题下的队列,因为ack机制,对于一个订阅者组只能绑定其中一个实例。
那么,很显然,这就导致了,一条消息传递到订阅者组中只能被其中一个实例进行消费了。

(2)一条消息在不同订阅者组中是共享的

1
2
3
消息被订阅者组1消费完之后,还可以被订阅者组2消费...


(3)在主题层面消息是无序的,但是在队列层面消息是有序的

1
2
3
(1)可以把有顺序关系hash到同一个队列中,因此队列角度来看消息是有序的。
(2)那显然,主题层面上是无序的。
(3)但是在主题层面来说,消息是“因果序”而非"全序"

​ 之前学习的时候,一直很困惑主题下的队列怎么跟订阅者组的对应关系,今天梳理一遍之后清晰很多了…好了先这样了,明天还要上班。这么再更一篇6.824的lab或者鸽了很久的tcp…


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!