這節課咱們來學習消息隊列中像隊列、主題、分區等基礎概念。這些基礎的概念,就像咱們學習一門編程語言中的基礎語法同樣,你只有搞清楚它們,才能進行後續的學習。html
若是你研究過超過一種消息隊列產品,你可能已經發現,每種消息隊列都有本身的一套消息模型,像隊列(Queue)、主題(Topic)或是分區(Partition)這些名詞概念,在每一個消息隊列模型中都會涉及一些,含義還不太同樣。前端
爲何出現這種狀況呢?由於沒有標準。曾經,也是有一些國際組織嘗試制定過消息相關的標準,好比早期的 JMS 和 AMQP。但讓人無奈的是,標準的進化跟不上消息隊列的演進速度,這些標準實際上已經被廢棄了。python
那麼,到底什麼是隊列?什麼是主題?主題和隊列又有什麼區別呢?想要完全理解這些,咱們須要從消息隊列的演進提及。數據庫
在互聯網的架構師圈兒中間,流傳着這樣一句不知道出處的名言,我很是認同和喜歡:好的架構不是設計出來的,而是演進出來的。 現代的消息隊列呈現出的模式,同樣是通過以前的十幾年逐步演進而來的。編程
最初的消息隊列,就是一個嚴格意義上的隊列。在計算機領域,「隊列(Queue)」是一種數據結構,有完整而嚴格的定義。在維基百科中,隊列的定義是這樣的:後端
隊列是先進先出(FIFO, First-In-First-Out)的線性表(Linear List)。在具體應用中一般用鏈表或者數組來實現。隊列只容許在後端(稱爲 rear)進行插入操做,在前端(稱爲 front)進行刪除操做。數組
這個定義裏面包含幾個關鍵點,第一個是先進先出,這裏面隱含着的一個要求是,在消息入隊出隊過程當中,須要保證這些消息嚴格有序,按照什麼順序寫進隊列,必須按照一樣的順序從隊列中讀出來。不過,隊列是沒有「讀」這個操做的,「讀」就是出隊,也就是從隊列中「刪除」這條消息。服務器
早期的消息隊列,就是按照「隊列」的數據結構來設計的。咱們一塊兒看下這個圖,生產者(Producer)發消息就是入隊操做,消費者(Consumer)收消息就是出隊也就是刪除操做,服務端存放消息的容器天然就稱爲「隊列」。網絡
這就是最初的一種消息模型:隊列模型。數據結構
若是有多個生產者往同一個隊列裏面發送消息,這個隊列中能夠消費到的消息,就是這些生產者生產的全部消息的合集。消息的順序就是這些生產者發送消息的天然順序。若是有多個消費者接收同一個隊列的消息,這些消費者之間其實是競爭的關係,每一個消費者只能收到隊列中的一部分消息,也就是說任何一條消息只能被其中的一個消費者收到。
若是須要將一份消息數據分發給多個消費者,要求每一個消費者都能收到全量的消息,例如,對於一份訂單數據,風控系統、分析系統、支付系統等都須要接收消息。這個時候,單個隊列就知足不了需求,一個可行的解決方式是,爲每一個消費者建立一個單獨的隊列,讓生產者發送多份。
顯然這是個比較蠢的作法,一樣的一份消息數據被複制到多個隊列中會浪費資源,更重要的是,生產者必須知道有多少個消費者。爲每一個消費者單獨發送一份消息,這實際上違背了消息隊列「解耦」這個設計初衷。
爲了解決這個問題,演化出了另一種消息模型:「發佈 - 訂閱模型(Publish-Subscribe Pattern)」。
在發佈 - 訂閱模型中,消息的發送方稱爲發佈者(Publisher),消息的接收方稱爲訂閱者(Subscriber),服務端存放消息的容器稱爲主題(Topic)。發佈者將消息發送到主題中,訂閱者在接收消息以前須要先「訂閱主題」。「訂閱」在這裏既是一個動做,同時還能夠認爲是主題在消費時的一個邏輯副本,每份訂閱中,訂閱者均可以接收到主題的全部消息。
在消息領域的歷史上很長的一段時間,隊列模式和發佈 - 訂閱模式是並存的,有些消息隊列同時支持這兩種消息模型,好比 ActiveMQ。咱們仔細對比一下這兩種模型,生產者就是發佈者,消費者就是訂閱者,隊列就是主題,並無本質的區別。它們最大的區別其實就是,一份消息數據能不能被消費屢次的問題。
實際上,在這種發佈 - 訂閱模型中,若是隻有一個訂閱者,那它和隊列模型就基本是同樣的了。也就是說,發佈 - 訂閱模型在功能層面上是能夠兼容隊列模型的。
現代的消息隊列產品使用的消息模型大可能是這種發佈 - 訂閱模型,固然也有例外。
這個例外就是 RabbitMQ,它是少數依然堅持使用隊列模型的產品之一。那它是怎麼解決多個消費者的問題呢?你還記得我在上節課中講到 RabbitMQ 的一個特點 Exchange 模塊嗎?在 RabbitMQ 中,Exchange 位於生產者和隊列之間,生產者並不關心將消息發送給哪一個隊列,而是將消息發送給 Exchange,由 Exchange 上配置的策略來決定將消息投遞到哪些隊列中。
同一份消息若是須要被多個消費者來消費,須要配置 Exchange 將消息發送到多個隊列,每一個隊列中都存放一份完整的消息數據,能夠爲一個消費者提供消費服務。這也能夠變相地實現新發布 - 訂閱模型中,「一份消息數據能夠被多個訂閱者來屢次消費」這樣的功能。具體的配置你能夠參考 RabbitMQ 官方教程,其中一個章節專門是講如何實現發佈訂閱的。
講完了 RabbitMQ 的消息模型,咱們再來看看 RocketMQ。RocketMQ 使用的消息模型是標準的發佈 - 訂閱模型,在 RocketMQ 的術語表中,生產者、消費者和主題與我在上面講的發佈 - 訂閱模型中的概念是徹底同樣的。
可是,在 RocketMQ 也有隊列(Queue)這個概念,而且隊列在 RocketMQ 中是一個很是重要的概念,那隊列在 RocketMQ 中的做用是什麼呢?這就要從消息隊列的消費機制提及。
幾乎全部的消息隊列產品都使用一種很是樸素的「請求 - 確認」機制,確保消息不會在傳遞過程當中因爲網絡或服務器故障丟失。具體的作法也很是簡單。在生產端,生產者先將消息發送給服務端,也就是 Broker,服務端在收到消息並將消息寫入主題或者隊列中後,會給生產者發送確認的響應。
若是生產者沒有收到服務端的確認或者收到失敗的響應,則會從新發送消息;在消費端,消費者在收到消息並完成本身的消費業務邏輯(好比,將數據保存到數據庫中)後,也會給服務端發送消費成功的確認,服務端只有收到消費確認後,才認爲一條消息被成功消費,不然它會給消費者從新發送這條消息,直到收到對應的消費成功確認。
這個確認機制很好地保證了消息傳遞過程當中的可靠性,可是,引入這個機制在消費端帶來了一個不小的問題。什麼問題呢?爲了確保消息的有序性,在某一條消息被成功消費以前,下一條消息是不能被消費的,不然就會出現消息空洞,違背了有序性這個原則。
也就是說,每一個主題在任意時刻,至多隻能有一個消費者實例在進行消費,那就無法經過水平擴展消費者的數量來提高消費端整體的消費性能。爲了解決這個問題,RocketMQ 在主題下面增長了隊列的概念。
每一個主題包含多個隊列,經過多個隊列來實現多實例並行生產和消費。須要注意的是,RocketMQ 只在隊列上保證消息的有序性,主題層面是沒法保證消息的嚴格順序的。
RocketMQ 中,訂閱者的概念是經過消費組(Consumer Group)來體現的。每一個消費組都消費主題中一份完整的消息,不一樣消費組之間消費進度彼此不受影響,也就是說,一條消息被 Consumer Group1 消費過,也會再給 Consumer Group2 消費。
消費組中包含多個消費者,同一個組內的消費者是競爭消費的關係,每一個消費者負責消費組內的一部分消息。若是一條消息被消費者 Consumer1 消費了,那同組的其餘消費者就不會再收到這條消息。
在 Topic 的消費過程當中,因爲消息須要被不一樣的組進行屢次消費,因此消費完的消息並不會當即被刪除,這就須要 RocketMQ 爲每一個消費組在每一個隊列上維護一個消費位置(Consumer Offset),這個位置以前的消息都被消費過,以後的消息都沒有被消費過,每成功消費一條消息,消費位置就加一。這個消費位置是很是重要的概念,咱們在使用消息隊列的時候,丟消息的緣由大可能是因爲消費位置處理不當致使的。
RocketMQ 的消息模型中,比較關鍵的概念就是這些了。爲了便於你理解,我畫了下面這張圖:
你能夠對照這張圖再把我剛剛講的這些概念繼續消化一下,加深理解。
咱們再來看看另外一種常見的消息隊列 Kafka,Kafka 的消息模型和 RocketMQ 是徹底同樣的,我剛剛講的全部 RocketMQ 中對應的概念,和生產消費過程當中的確認機制,都徹底適用於 Kafka。惟一的區別是,在 Kafka 中,隊列這個概念的名稱不同,Kafka 中對應的名稱是「分區(Partition)」,含義和功能是沒有任何區別的。
咱們來總結一下本節課學習的內容。首先咱們講了隊列和主題的區別,這兩個概念的背後實際上對應着兩種不一樣的消息模型:隊列模型和發佈 - 訂閱模型。而後你須要理解,這兩種消息模型其實並無本質上的區別,均可以經過一些擴展或者變化來互相替代。
經常使用的消息隊列中,RabbitMQ 採用的是隊列模型,可是它同樣能夠實現發佈 - 訂閱的功能。RocketMQ 和 Kafka 採用的是發佈 - 訂閱模型,而且兩者的消息模型是基本一致的。
最後提醒你一點,我這節課講的消息模型和相關的概念是業務層面的模型,深入理解業務模型有助於你用最佳的姿式去使用消息隊列。
但業務模型不等於就是實現層面的模型。好比說 MySQL 和 Hbase 一樣是支持 SQL 的數據庫,它們的業務模型中,存放數據的單元都是「表」,可是在實現層面,沒有哪一個數據庫是以二維表的方式去存儲數據的,MySQL 使用 B+ 樹來存儲數據,而 HBase 使用的是 KV 的結構來存儲。一樣,像 Kafka 和 RocketMQ 的業務模型基本是同樣的,並非說他們的實現就是同樣的,實際上這兩個消息隊列的實現是徹底不一樣的。
最後給你們留一個思考題。剛剛我在介紹 RocketMQ 的消息模型時講過,在消費的時候,爲了保證消息的不丟失和嚴格順序,每一個隊列只能串行消費,沒法作到併發,不然會出現消費空洞的問題。那若是放寬一下限制,不要求嚴格順序,可否作到單個隊列的並行消費呢?若是能夠,該如何實現?
摘自:極客時間