訊息佇列面試解析系列(三) 訊息模型辨析

2021-10-08 16:18:18 字數 3352 閱讀 6237

mq都得有訊息模型,就會產生比如佇列(queue)、主題(topic)、分割槽(partition)這些名詞,但是概念上卻不盡相同。

因為沒有標準。曾經,也有一些國際組織嘗試制定訊息的標準,比如jms和amqp。但標準制定跟不上mq演進速度,這些標準名存實亡。

好的架構不是設計出來的,而是演進出來的。

現代mq的表現,也是經過十幾年演進而來。

最初的訊息佇列,就是個嚴格意義的佇列。

佇列作為一種資料結構,先進先出,即訊息入隊出隊過程,需要保證這些訊息嚴格有序,按什麼順序寫進佇列,必須按照同樣的順序從佇列中讀出來。

佇列是沒有「讀」這個操作的,「讀」就是出隊,也就是從佇列中「刪除」這條訊息。

早期mq就是按佇列資料結構設計。

生產者發訊息就是入隊;

消費者收訊息就是出隊,即刪除;

服務端存放訊息的容器自然就稱為「佇列」。

若多生產者往同一佇列發訊息,這個佇列中可以消費到的訊息,就是這些生產者生產的所有訊息的合集。訊息的順序就是這些生產者傳送訊息的自然順序。

若多消費者接收同一佇列的訊息,這些消費者間就是競爭關係,每個消費者只能收到佇列中的一部分訊息,即任一訊息只能被其中乙個消費者收到。

若需將乙份訊息資料分發給多消費者,要求每個消費者都能收到全量訊息。

例如,對乙份訂單資料,風控系統、分析系統、支付系統等都需要接收訊息。這時,單佇列就滿足不了需求,乙個可行的解決方式:為每個消費者建立乙個單獨佇列,讓生產者傳送多份。

這很蠢,同份訊息資料被複製到多佇列會浪費資源,而且生產者必須知道有多少消費者。為每個消費者單獨發乙份訊息,這實際上違背了訊息佇列「解耦」這個設計初衷。

為解決該問題,演化出新的訊息模型:

發布者將訊息傳送到主題,訂閱者在接收訊息前需先「訂閱主題」。

「訂閱」在這既是乙個動作,還可認為是主題在消費時的乙個邏輯副本。

每份訂閱中,訂閱者都可接收到主題的所有訊息。

在訊息領域的很長段時間,佇列模式和發布-訂閱模式並存,有些訊息佇列同時支援這兩種訊息模型,比如activemq。

對比起來,生產者就是發布者,消費者就是訂閱者,佇列就是主題,並無本質區別。

最大區別:乙份訊息資料能否被消費多次。

發布-訂閱模型中,如果只有乙個訂閱者,那它和佇列模型無異。即發布-訂閱模型在功能上可相容佇列模型。

現代mq使用訊息模型大多是發布-訂閱模型,也有例外,比如兔子mq:

少數依然堅持使用佇列模型的產品之一。怎麼解決多消費者問題的?

rabbitmq中,exchange位於生產者和佇列間,生產者並不關心將訊息發給哪個佇列,而將訊息傳送給exchange,由exchange策略決定將訊息投遞到哪些佇列。

同份訊息若需被多消費者消費,需配置exchange將訊息發到多個佇列,每個佇列都存放乙份完整訊息資料,可為乙個消費者提供消費服務。這也可變相實現發布-訂閱模型的「乙份訊息資料可被多訂閱者多次消費」功能。

rocketmq使用標準的發布-訂閱模型,其中的生產者、消費者、主題與前文講的發布-訂閱模型中的概念一致。

rocketmq還有佇列概念,又有何作用?

幾乎所有mq都使用一種非常樸素的「請求-確認」機制,確保訊息不會在傳遞過程中由於網路或伺服器故障而丟失。

具體做法也簡單。

消費者在收到訊息並完成消費業務邏輯(比如將資料儲存到資料庫)後,也會給服務端發消費成功的確認,服務端只有收到消費確認後,才認為一條訊息被成功消費,否則它會給消費者重傳送這條訊息,直到收到對應的消費成功確認

確認機制保證訊息傳遞過程中的可靠性,但該機制在消費端帶來不小問題。

為確保訊息的有序性,在某條訊息被成功消費前,下條訊息是不能被消費的,否則就會出現訊息空洞,違背有序性原則。

即每個主題在任意時刻,至多只能有乙個消費者例項在進行消費,那就沒法通過水平擴充套件消費者數量提公升消費端總體的消費效能。為解決問題,rocketmq在主題下增加佇列概念。

每個主題包含多個佇列,通過多佇列實現多例項並行生產和消費。注意rocketmq只在佇列保證訊息有序性,主題層面無法保證訊息的嚴格順序。

rocketmq中,訂閱者的概念是通過消費組(consumer group)體現。

每個消費組都消費主題中乙份完整訊息,不同消費組間消費進度彼此不受影響,即一條訊息被consumer group1消費過,也會再給consumer group2消費。

角色完全相同的消費者被分組在一起,稱為消費組。通過它,在訊息消費方面實現負載平衡和容錯非常容易。

consumer group的consumer例項必須具有完全相同的主題訂閱。

消費組包含多個消費者,同組的消費者是競爭消費關係,每個消費者負責消費組內的一部分訊息。若一條訊息被consumer1消費,那同組的其他消費者就不會再收到該訊息。

在topic的消費過程,由於訊息需要被不同組進行多次消費,所以消費完的訊息並不會立即被刪,這需要rocketmq為每個消費組在每個佇列維護乙個消費位置(consumer offset),該位置之前的訊息都被消費過,之後訊息都未被消費過,每成功消費一條訊息,消費位置加一。

這個消費位置是非常重要的概念,丟訊息的原因大多是由於消費位置處理不當導致。

在消費時,為保證訊息的不丟失和嚴格順序,每個佇列只能序列消費,無法做到併發,否則會出現消費空洞的問題。那如果放寬一下限制,不要求嚴格順序,能否做到單個佇列的並行消費呢?如果可以,該如何實現?

todo

rocketmq的完全一致,上節所有rocketmq中對應的概念,和生產消費過程中的確認機制,都完全適用於kafka。

唯一區別:

kafka中,佇列概念的名稱不一樣,kafka中對應的名稱分割槽(partition),本質毫無差異。

佇列和主題的區別,這倆概念的背後實際對應兩種不同的訊息模型:佇列模型和發布-訂閱模型。這兩種訊息模型其實並無本質區別,都可通過一些擴充套件或者變化來互相替代。

本文訊息模型相關概念都是業務層面模型,但業務模型不等於實現層面模型。

比如mysql和hbase都是支援sql的資料庫,它們的業務模型中,存放資料的單元都是「表」。

但在實現層面,沒有哪個資料庫是以二維表方式儲存資料,mysql使用b+樹,hbase使用kv結構。

同理,像kafka和rocketmq的業務模型基本一樣,並非指實現就一樣,實際倆實現完全不同。

參考

訊息佇列(三)RocketMQ如何儲存訊息

rocket的訊息是有consume queue和commit log組成。consume queue consume queue是訊息的邏輯佇列,相當於字典目錄,用來指定訊息在物理檔案 commit log 上的位置,我們可以在配置中指定consumequeue和commitlog儲存的目錄。每乙...

Redis系列(五) 訊息佇列

訊息佇列已經成為現在網際網路服務端的標配元件,現在比較常用的訊息中介軟體有rabbitmq kafka rocketmq activemq。說出來你可能不信,redis作為乙個快取中介軟體,居然也提供了訊息佇列的功能。redis提供的訊息佇列功能是發布 訂閱模型,它引入了channel的概念,即訂閱...

訊息佇列面試場景

面試官 你好。候選人 你好。面試官在你的簡歷上面看到了,呦,有個亮點,你在專案裡用過mq,比如說你用過activemq 面試官 你在系統裡用過訊息佇列嗎?面試官在隨和的語氣中展開了面試 候選人 用過的 此時感覺沒啥 面試官 那你說一下你們在專案裡是怎麼用訊息佇列的?候選人 巴拉巴拉,我們啥啥系統傳送...