一、消息隊列的演進
1、初始階段
最初的消息隊列,就是一個嚴格意義上的隊列。隊列是一種數據結構,先進先出,在消息入隊出隊過程中,保證這些消息嚴格有序。早期的消息隊列就是按照“隊列”的數據結構設計的。
隊列模型:

生產者(Producer)發消息就是入隊操作,消費者(Consumer)收消息就是出隊也就是刪除操作,服務端存放消息的容器自然就稱為“隊列”。
- 如果有多個生產者往同一個隊列里面發送消息,這個隊列中可以消費到的消息,就是這些生產者生產的所有消息的合集。消息的順序就是這些生產者發送消息的自然順序。
- 如果有多個消費者接收同一個隊列的消息,這些消費者之間實際上是競爭的關系,每個消費者只能收到隊列中的一部分消息,也就是說任何一條消息只能被其中的一個消費者收到。
2、發布 - 訂閱模型階段
如果需要將一份消息數據分發給多個消費者,要求每個消費者都能收到全量的消息,例如,對於一份訂單數據,風控系統、分析系統、支付系統等都需要接收消息。
這個時候,單個隊列就滿足不了需求,一個可行的解決方式是,為每個消費者創建一個單獨的隊列,讓生產者發送多份。但是同樣的一份消息數據被復制到多個隊列中會浪費資源,更重要的是,生產者必須知道有多少個消費者。為每個消費者單獨發送一份消息,這實際上違背了消息隊列“解耦”這個設計初衷。
為了解決這個問題,演化出了另外一種消息模型:發布 - 訂閱模型(Publish-Subscribe Pattern)

消息的發送方稱為發布者(Publisher),消息的接收方稱為訂閱者(Subscriber),服務端存放消息的容器稱為主題(Topic)。
- 發布者將消息發送到主題中,訂閱者在接收消息之前需要先“訂閱主題”。
- 每份訂閱中,訂閱者都可以接收到主題的所有消息。
3、總結:
- 在很長的一段時間,隊列模式和發布 - 訂閱模式是並存的。
- 有些消息隊列同時支持這兩種消息模型,比如 ActiveMQ。
- 對比這兩種模型,生產者就是發布者,消費者就是訂閱者,隊列就是主題,並沒有本質的區別。它們最大的區別是:一份消息數據能不能被消費多次的問題。
- 實際上,在這種發布 - 訂閱模型中,如果只有一個訂閱者,那它和隊列模型就基本是一樣的了。也就是說,發布 - 訂閱模型在功能層面上是可以兼容隊列模型的。
二、RabbitMQ 的消息模型
少數依然堅持使用隊列模型的產品之一。
RabbitMQ 使用 Exchange 模塊解決多個消費者的問題。Exchange 位於生產者和隊列之間,生產者並不關心將消息發送給哪個隊列,而是將消息發送給 Exchange,由 Exchange 上配置的策略來決定將消息投遞到哪些隊列中。

- 同一份消息如果需要被多個消費者來消費,需要配置 Exchange 將消息發送到多個隊列,每個隊列中都存放一份完整的消息數據,可以為一個消費者提供消費服務。
三、RocketMQ 的消息模型
RocketMQ 使用的消息模型是標准的發布 - 訂閱模型。在 RocketMQ 也有隊列(Queue)這個概念。
消息隊列的消費機制:
幾乎所有的消息隊列產品都使用一種非常朴素的“請求 - 確認”機制,確保消息不會在傳遞過程中由於網絡或服務器故障丟失。
在生產端,生產者先將消息發送給服務端,也就是 Broker,服務端在收到消息並將消息寫入主題或者隊列中后,會給生產者發送確認的響應。如果生產者沒有收到服務端的確認或者收到失敗的響應,則會重新發送消息。
在消費端,消費者在收到消息並完成自己的消費業務邏輯(比如,將數據保存到數據庫中)后,也會給服務端發送消費成功的確認,服務端只有收到消費確認后,才認為一條消息被成功消費,否則它會給消費者重新發送這條消息,直到收到對應的消費成功確認。
這個確認機制很好地保證了消息傳遞過程中的可靠性,但是,引入這個機制在消費端帶來了一個問題:為了確保消息的有序性,在某一條消息被成功消費之前,下一條消息是不能被消費的,也就是說,每個主題在任意時刻,至多只能有一個消費者實例在進行消費,那就沒法通過水平擴展消費者的數量來提升消費端總體的消費性能。
為了解決這個問題,RocketMQ 在主題下面增加了隊列的概念:

- 每個主題包含多個隊列,通過多個隊列來實現多實例並行生產和消費。需要注意的是,RocketMQ 只在隊列上保證消息的有序性,主題層面是無法保證消息的嚴格順序的。
- 生產者會往所有隊列發消息,但不是“同一條消息每個隊列都發一次”,每條消息只會往某個隊列里面發送一次。
- 一個消費組,每個隊列上只能串行消費,多個隊列加一起就是並行消費了,並行度就是隊列數量,隊列數量越多並行度越大,所以水平擴展可以提升消費性能。
- 每隊列每消費組維護一個消費位置(offset),記錄這個消費組在這個隊列上消費到哪兒了。
- 訂閱者是通過消費組(Consumer Group)來體現的。每個消費組都消費主題中一份完整的消息,不同消費組之間消費進度彼此不受影響,也就是說,一條消息被 Consumer Group1 消費過,也會再給 Consumer Group2 消費。
- 消費組中包含多個消費者,同一個組內的消費者是競爭消費的關系,每個消費者負責消費組內的一部分消息。如果一條消息被消費者 Consumer1 消費了,那同組的其他消費者就不會再收到這條消息。
- 由於消息需要被不同的組進行多次消費,所以消費完的消息並不會立即被刪除,這就需要 RocketMQ 為每個消費組在每個隊列上維護一個消費位置(Consumer Offset),這個位置之前的消息都被消費過,之后的消息都沒有被消費過,每成功消費一條消息,消費位置就加一。我們在使用消息隊列的時候,丟消息的原因大多是由於消費位置處理不當導致的。
四、Kafka 的消息模型
Kafka 的消息模型和 RocketMQ 是完全一樣的,唯一的區別是,在 Kafka 中,隊列這個概念的名稱不一樣,Kafka 中對應的名稱是“分區(Partition)”,含義和功能是沒有任何區別的。
五、總結
- 常用的消息隊列中,RabbitMQ 采用的是隊列模型,但是它一樣可以實現發布 - 訂閱的功能。RocketMQ 和 Kafka 采用的是發布 - 訂閱模型,並且二者的消息模型是基本一致的。
