1. 程式人生 > 其它 >面試-訊息佇列知識點整理

面試-訊息佇列知識點整理

MQ概述

什麼是訊息佇列?

使用佇列來通訊的元件,把要傳輸的訊息放在佇列中。

為什麼要使用訊息佇列(優點)?

  • 系統解耦:解耦訊息生產者和消費者之間的關係
  • 非同步呼叫:使用者呼叫介面時,由於介面之間呼叫導致用時時間比較久,使用者體驗不好。呼叫介面後將訊息放入到MQ後就返回,使用者體驗好,最終一致性由MQ來保證
  • 流量削峰:減少高峰時期對伺服器壓力,先把請求放到MQ中,系統根據實際能處理的併發量來消費請求

解耦的面試技巧:你需要思考一下,在你自己的系統裡面有沒有類似的情況,一個系統或者模組,呼叫了多個系統或者模組,它們互相之間的呼叫非常複雜,並且維護起來很麻煩,但其實這個呼叫是不需要直接同步呼叫介面的,如果用MQ給它非同步化解耦也是可以的,你就需要思考在你的專案裡,是不是可以用MQ給它進行系統的解耦,可以自己組織一下語言回答。

會帶來什麼問題(缺點)?

  • 系統可用性降低。如果MQ掛了,整個系統就不能服務了
  • 系統複雜性提高。訊息丟失、訊息重複消費、訊息重複傳送、訊息順序錯亂等問題
  • 一致性問題。將訊息放到MQ後就返回給使用者成功的資訊,但是其他系統消費訊息時,若某個系統失敗了,導致資料不一致

RabbitMQ

什麼是RabbitMQ?

它是實現了高階訊息佇列協議(AMQP)的訊息中介軟體。

RabbitMQ 元件

  • Broker:一個Broker可以看做一個RabbitMQ伺服器
  • Connection:實際的連線(TCP連線)
  • Channel:虛擬連線。一個Connection上可以有多個Channel(通道),通道沒有數量限制,訊息在通道上傳輸。
  • Message
  • Virtual host:虛擬broker。其內部含有獨立的queue、exchange、binding,以及獨立的許可權系統
  • Exchange:交換機。生產者將訊息傳送到交換機,由交換機將訊息路由到一個或多個佇列中。路由不到時,返回給生產者或直接丟棄(mandatory 為 true,返回訊息給生產者;為 false 丟棄訊息)
  • Queue:儲存訊息的資料結構。可以設定長度,處於ready狀態的訊息會被計數,不統計處於unack的訊息。訊息重新入隊會處於隊頭。多個消費者可以訂閱同一個佇列,佇列中的訊息會平攤給各個消費者處理

RabbitMQ工作模型

  1. 簡單佇列(Simple Queue):生產者將訊息投遞到佇列裡,訊息者從佇列裡取訊息
  2. 工作佇列(Worker Queue):一個生產者,一個佇列,多個消費者,用於訊息消費耗時的場景
  3. 釋出訂閱(fanout)
  4. Direct:訊息根據路由鍵投遞相應的佇列中
  5. Topic:訊息根據路由鍵和繫結鍵的匹配,投遞到相應的佇列中。如果多個鍵匹配成功,且目標佇列是同一個佇列,佇列只會收到一條訊息
  6. Headers:用鍵值對做匹配,匹配方式有all和any,不常使用

其中3-6是交換機的四種工作模式(四種交換機型別)

RabbitMQ消費模式包括哪些?

推送(push)

  1. 通過 channel.basicConsume 將 channel(通道)設定為推模式,訊息可用時自動將訊息推送給消費者
  2. 消費者需要設定緩衝區快取訊息
  3. 實時性好(長連線)

拉取(pull)

  1. 使用 channel.basicGet 拉訊息
  2. 輪詢模型,消費者傳送 get 請求獲取訊息,如果佇列中沒有訊息,則獲得空的回覆
  3. 需要時才去拉取訊息,實時性差,耗費資源(短連線)

Qos(質量服務)

訊息傳送給消費者後,預設是自動確認,如果消費者未能消費成功,則訊息丟失。

通過顯式確認可以保證只有當訊息處理完成並收到Ack後才從佇列中刪除。但是存在的問題是:1)訊息太多全部傳給消費者,可能造成消費者記憶體爆滿;2)訊息處理慢時,想讓別的消費者一起處理,但是這些訊息都被原來的消費者接收了,這些訊息不會再發送給新新增的消費者

Qos可以解決上述問題,需要開啟訊息的顯式確認,設定每次傳輸給消費者的訊息條數為n,消費者處理完n條訊息後再獲取n條訊息進行處理;而新增消費者時,訊息可以立即傳送給新的消費者。

多個消費者監聽一個佇列,訊息如何分發?

  • 輪詢:預設策略
  • 公平分發(QoS):給空閒的消費者傳送更多的訊息(當消費者有x條訊息沒有響應時,不再給該消費者發訊息)

RabbitMQ如何保證訊息的可靠性?

  • 生產者到佇列:事務機制和Confirm機制,注意:事務機制和Confirm機制是互斥的,兩者不能共存,會導致RabbitMQ報錯。

  • 佇列自身:持久化、叢集、普通模式、映象模式。

  • 佇列到消費者:basicAck機制、死信佇列、訊息補償機制

生產者如何可靠地將訊息投遞到佇列中?

confirm機制

  1. 生產者生產的訊息帶有唯一 ID
  2. 訊息被投遞到目標佇列後,傳送Ack訊息(包含訊息的唯一ID)給生產者
  3. 有可能因為網路問題導致Ack訊息無法傳送到生產者,那麼生產者在等待超時後,會重傳訊息;或者RabbitMQ內部錯誤導致訊息丟失,則傳送nack訊息
  4. 生產者收到Ack訊息後,認為訊息已經投遞成功

佇列自身不弄丟訊息

佇列開啟持久化,訊息的diliveryMode = 2

佇列如何將訊息可靠投遞到消費者?

手動確認

  1. 佇列將訊息push給消費者(或消費者來pull訊息)
  2. 消費者得到訊息並做完業務邏輯
  3. 消費者傳送Ack訊息給佇列 ,通知佇列刪除該訊息(佇列會一直等待直到得到ack訊息,佇列通過消費者的連線是否中斷來確認是否需要重新發送訊息,只要連線不中斷,消費者有足夠長的時間來處理訊息,保證資料的最終一致性)
  4. 佇列將已消費的訊息刪除

RabbitMQ如何保證訊息的可靠性?如何避免訊息重複投遞或重複消費?

重複投遞的原因:等待超時後,需要重試。

避免重複投遞:訊息生產時,生產者傳送的訊息攜帶一個Message ID(全域性唯一ID),作為去重和冪等的依據,避免重複的訊息進入佇列

重複消費的原因:消費者接收訊息後,在確認之前斷開了連線或者取消訂閱,訊息會被重新分發給下一個訂閱的消費者。

避免重複消費:訊息消費時,要求訊息體中必須要有一個全域性唯一ID,作為去重和冪等的依據,避免同一條訊息被重複消費

RabbitMQ訊息的狀態

  • alpha:訊息內容(包括訊息體、屬性和headers) 和訊息索引都儲存在記憶體中
  • beta:訊息內容儲存在磁碟中,訊息索引儲存在記憶體中
  • gamma:訊息內容儲存在磁碟中,訊息索引在磁碟和記憶體中都有
  • delta:訊息內容和索引都在磁碟中

高階特性

死信

訊息成為死信訊息的原因

  • 訊息被拒(Basic.Reject/Basic.Nack)且不重新入隊
  • 訊息TTL過期
  • 佇列滿了,無法再新增

死信訊息的headers

欄位名 含義
x-first-death-exchange 第一次成為死信前交換機的名稱
x-first-death-reason 第一次成為死信的原因。rejected:訊息被拒,且 default-requeue-rejected 引數為false。expired:訊息過期。maxlen: 佇列內訊息數量超過佇列最大容量
x-first-death-queue 第一次成為死信前所在佇列名稱
x-death 歷次被投入死信交換機的資訊列表,同一個訊息每次進入一個死信交換機,這個陣列的資訊就會被更新

https://cloud.tencent.com/developer/article/1463065

如何使用?

為佇列配置死信交換機,再為死信交換機配置死信佇列,訊息成為死信資訊後,會由死信交換機投遞到死信佇列中。死信交換機、死信佇列和普通的交換機、佇列沒有區別。例子如下:

@Bean("queueA")
public Queue queueA(){
    Map<String, Object> args = new HashMap<>(4);
    // x-dead-letter-exchange    這裡聲明當前佇列繫結的死信交換機
    args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
    // x-dead-letter-routing-key  這裡聲明當前佇列的死信路由key。如果不設定的話,保留原來的路由key
    args.put("x-dead-letter-routing-key", DEAD_LETTER_QUEUEA_ROUTING_KEY);
    // 繫結
    return QueueBuilder.durable(QUEUEA_NAME).withArguments(args).build();
}

延遲佇列

儲存延遲訊息,訊息被髮送後不想讓消費者立即拿到訊息,要等待特定時間後才能拿到訊息。

實現方式

  • 3.6.x之前,死信佇列+TTL過期時間可以實現延遲佇列。訊息先進入一個佇列,該佇列沒有消費者,那麼訊息過期後進入死信佇列

    這種方式存在的缺陷:如果隊頭訊息沒有過期而隊中的某個訊息過期了,隊中的該訊息只有走到隊頭才會進入死信佇列。解決的方法是多級佇列。

    如果所有訊息的延遲時間是相同的話,可以設定佇列的訊息過期時間(x-expires

  • 3.6.x開始,延遲佇列外掛

應用場景

  • x天自動確認收貨、自動預設評價。簽收商品後物流系統在x天的延遲後傳送訊息給支付系統,通知系統打款給商家。x天后通知評價系統進行預設評價
  • 12306購票支付確認頁面,如果30分鐘沒有付款將自動取消訂單。在下訂單的那一刻購票系統會發送一個延時訊息給訂單系統。如果在30分鐘內完成了訂單,則可以通過邏輯程式碼忽略訊息

優先順序佇列

優先順序高的佇列會先被消費(設定 x-max-priority 引數)。當消費速度大於生成速度且 Broker 沒有堆積的情況下,沒有意義。

叢集

普通叢集

每臺機器上啟動一個RabbitMQ例項,而建立的queue只放在一個例項上,其他例項同步queue的元資料。消費時如果連線到了另一個例項上,則例項會從queue所在的例項上拉取資料

多個例項服務一個queue

映象叢集

RabbitMQ的元資料和queue裡的訊息都會存在於多個例項上,每次訊息進入佇列,會自動把訊息同步到多個例項的佇列中

分為一個master和多個slave。所有的操作最終都會到master上操作

  1. 生產者可任意選擇一個節點連線,如果該節點不是master,則轉發給master。master向slave傳送訊息,收到半數以上回復後本地提交,再讓slave提交
  2. 消費者可任意選擇一個節點連線,如果該節點不是master,則轉發給master。消費者消費後進行 ack 確認,master收到ack後刪除,並讓slave刪除。
  3. 如果master掉線,自動選出一個節點(slave中訊息佇列最長的節點)作為新的master

https://blog.csdn.net/jing956899449/article/details/107064395

場景題

如何保證訊息的順序性?

  • 只有一個消費者,可以保證順序
  • 多個佇列,每個佇列對應一個消費者,同一個使用者的操作hash到同一個佇列上
  • 每個訊息有一個全域性ID,同時去關聯一個parentMsgId,在前一條訊息未消費時不處理下一條訊息

訊息積壓在訊息佇列中會導致什麼結果?產生的原因是?如何解決?

原因:消費者消費速度慢,或者出現了問題

導致的結果:1)磁碟空間滿了;2)海量訊息堆積,消費者需要很長時間消費

解決辦法

  • 磁碟空間滿的情況:在其他機器上建立臨時的訊息佇列,再寫一個臨時的消費者,把積壓的訊息放到臨時佇列裡去
  • 海量訊息堆積的情況:修復消費者問題,停掉現有的消費者,臨時建立10倍的訊息佇列,再用一個臨時的消費者將訊息分發到臨時訊息佇列中,臨時徵用10倍的機器部署消費者。等積壓訊息消費完成後,再恢復成之前的架構

Kafka和RabbitMQ的對比

保證訊息可靠性

  • Kafka

    1)使用acks=all,Leader收到訊息後,等待所有ISR列表的Follower返回後再發送ack給生產者(分割槽副本數和ISR最少副本數配置成大於1)

    2)生產者使用同步傳送訊息(預設是非同步,多個請求先放在緩衝區,再合併傳送)
    3)消費者使用手動提交offset,從而保證訊息至少消費一次

  • RabbitMQ:RabbitMQ如何保證訊息的可靠性?

RabbitMQ的優缺點

優點

  • 延遲很低(微秒級)
  • 可靠性:使用一些機制來保證可靠性, 如持久化、傳輸確認及釋出確認等。
  • 靈活的路由:對於典型的路由功能,RabbitMQ己經提供了一些內建的交換器來實現。針對更復雜的路由功能,可以將多個交換器繫結在一起,也可以通過外掛機制來實現自己的交換器。
  • 管理介面 : RabbitMQ提供了一個易用的使用者介面,使得使用者可以監控和管理訊息、叢集中的節點等。
  • 高可用性:佇列可以在叢集中的機器上設定映象,使得在部分節點出現問題的情況下佇列仍然可用。
  • 多種協議:RabbitMQ除了原生支援AMQP協議,還支援STOMP、MQTT等多種訊息中介軟體協議。
  • 支援多種語言:如Java、Python、Ruby、PHP、C#、JavaScript等。
  • 外掛機制 : RabbitMQ提供了許多外掛,以實現從多方面進行擴充套件,也可以自定義外掛。

缺點

  • erlang開發,難以維護
  • 相比較於其他訊息中介軟體,吞吐量較低(萬級)

Kafka的優點和缺點

優點:

  • 吞吐量十萬級
  • 可用性非常高(一個數據多個副本,少數機器宕機,不會丟失資料,不會導致不可用)
  • 訊息可以做到0丟失

缺點:

  • topic增多會導致吞吐量大幅下降(幾百個topic),如果要支援大規模topic,需要更多的機器資源
  • 依賴ZooKeeper進行元資料管理(額外的複雜性)