消息队列学习笔记 - 2
消息队列的模型
在我一开始学习消息队列的时候,有些概念没有搞得很清楚,趁着今天早下班来梳理一下。
在消息队列中,什么是队列,什么是主题?他们之间有什么区别呢?还有它是怎么做的发布 - 订阅模型。
队列
在早期的消息队列中,是用一个“队列(Queue)”来存储消息的。这里包含一个关键点,就是消息是按照到达服务器的顺序进行存储的,也是按照这个顺序来进行消费的,即先来先消费。
如上图,生产者(上游服务)把消息推送到消息队列中去,这样消费者(下游)就能对这些消息进行处理。如果有多个消费者,并发地往同一个队列里面推送消息,那么其实这也还好问题不算太大。只要在消息队列上做一些操作,就能避免并发写带来的问题(比如说队列上加分布式锁,或者仿照GFS保证每次append都是原子的)。
对于消费者并发,其实也可以仿照上述的做法,这样能避免消费者之间的并发问题。但是,这又带来了另外一个问题,这样会导致,一条消息只能被一个消费者消费。然而,很多场景,我们希望一条消息能够被多个系统所共享。
如图,订单系统生成了一条订单消息,而其他三个消费者系统之间并没有依赖关系,几乎可以说是独立的。那么,一条消息按道理来说同时被三个系统消费,是最好的选择。但是这时候,单队列就不能满足这个需求了。
一个比较一般的做法,就是为某个系统都创建一个队列。这时候,一条消息就会复制成多分然后投放到这些队列中去。这显然是一种十分笨拙的方法。
为了解决这个问题,出现了一种模型发布 - 订阅模型。
主题 , 发布 - 订阅模型
为了解决这个问题,演化出了一种模型 — 发布 - 订阅。在该模型里面,发送消息的称为发布者。存储消息的容器称为主题(TOPIC)。消费消息的称为订阅者。
如果消费者想要从消息队列上得到消息,那么在接受消息之前,它需要订阅该主题。我个人理解,订阅是指在队列上,设置一个“专属的offset”。这样,队列中维护着不同订阅者的offset。
每个订阅者,消费完一个消息后,队列上对应的offset就会移动到下一条消息的地方。这就解决了单队列,消息不能被共享的问题。在这个模型中,不同订阅者的offset是独立的,消费完后消息也只是逻辑上出队。实际上并没有出队,也不影响别的队列的offset。
RocketMQ的模型
发布-订阅的缺陷
上面提到的发布 - 订阅模型,虽然解决了消息共享的问题,但在实际生产中仅仅依赖该模型的话,效率仍然是不够高的。因为,主题本质上仍就是单队列。这就让整个系统的并发性能不太行了。
为什么这样说呢?其实是下面这个原因:
- 为了防止消息不丢失,消息队列都有”请求 - 确认”机制
这个机制到来了一个问题。offset,只有收到消息成功消费的确认,该offset才能进行移动。不然会出现,很奇怪的现象。
比如说,发布者发布了两条有顺序关系的消息到主题上。消息1推送给订阅者,但是在网络中丢失了。但是主题并不知道,而且还给订阅者推送消息2。这导致了,订阅者只收到了消息2,但是消息2和消息1是有顺序关系的,因此会导致订阅者的处理逻辑出现错误。
正是这个原因,订阅了“订单消息主题“的支付系统,该系统中只能有一个实例。即使多加了实例,也不能提高系统的效率。这就难以让人接受了。
主题多队列模型
为了在发布 - 订阅模型的基础上提高,消息队列的并发度。能够让一个订阅者上能够部署多个实例,RocketMQ的设计者在一个主题下面配置了多个队列。这些队列的功能都是一样的,都是收发消息。
发布者,发布消息的时候,通过路由规则把消息推送到主题下的某个队列中(通常是hash)。
因为有了多队列,订阅者也可以有多个实例,称为订阅者组。一般来说,订阅者组的实例数量最好是等于主题中队列的数量。
在“主题-单队列”的模型中,因为ack机制,导致了主题上订阅者不能有多个实例。在“主题-多队列”模型中,这个核心问题(ack带来的消息顺序问题)仍旧没有改变,它仅仅是在主题层面上扩充了队列,提高了并发性能。并没有改变,一个队列对应一个实例的要求。
因此,订阅者组中,实例对象太少不能完全利用系统的性能;实例数量太多,必定会有实例被闲置。
在这个模型中,有几个值得注意的点:
(1)一条消息在订阅组中是竞争关系
1 |
|
(2)一条消息在不同订阅者组中是共享的
1 |
|
(3)在主题层面消息是无序的,但是在队列层面消息是有序的
1 |
|
之前学习的时候,一直很困惑主题下的队列怎么跟订阅者组的对应关系,今天梳理一遍之后清晰很多了…好了先这样了,明天还要上班。这么再更一篇6.824的lab或者鸽了很久的tcp…
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!