消息隊列面經

1 消息隊列的使用場景?

  1. 異步:A系統需要發送請求給B系統處理,由於B系統需要查詢更新數據庫花費時間較長,以至於A系統要等待B系統處理完畢後再發送下個請求,造成A系統資源浪費。使用消息隊列後,A系統生產完消息後直接丟進消息消息隊列,就完成一次請求,繼續處理下個請求。
  2. 解耦:A系統發送個數據到BCD三個系統,接口調用發送,那如果E系統也要這個數據呢?那如果C系統現在不需要了呢?現在A系統又要發送第二種數據了呢?更加崩潰的是,A系統要時時刻刻考慮BCDE四個系統如果掛了咋辦?我要不要重發?我要不要把消息存起來?使用消息隊列就能解決這個問題,A系統只負責生產數據,不需要考慮消息被哪個系統來消費。
  3. 削峯:A系統調用B系統處理數據,每天0點到11點,A系統風平浪靜,每秒併發請求數量就100個。結果每次一到11點~1點,每秒併發請求數量突然會暴增到1萬條。但是B系統最大的處理能力就只能是每秒鐘處理1000個請求啊,那系統就會直接崩掉。引入消息隊列後,把請求數據先存入消息中間件系統中,消費系統慢慢拉取消費,可能會比正常的慢一點,但是不至於打掛服務器。等流量高峯下去了,服務也就沒壓力了。

2 消息隊列帶來的問題?

  1. 系統複雜性提高:本來代碼想怎樣寫就怎樣寫,現在加入一個 MQ 之後,還得考慮如何去維護它。除此之外,還有一大堆問題,例如消息重複消費、消息丟失、消息的順序消費等等,煩死了。
  2. 數據一致性問題:A系統處理完了直接返回成功了,別人以爲你這個請求就成功了。但是問題是,要是BCD三個系統那裏,BD兩個系統寫庫成功了,結果C系統寫庫失敗了,怎麼辦?這數據就不一致了。(解決方法:分佈式事務)
  3. 系統可用性降低:系統引入的外部依賴越多,越容易掛掉,本來就是A系統調用BCD三個系統的接口就好了,ABCD四個系統好好的,沒啥問題,偏偏加個 MQ 進來,萬一 MQ 掛了怎麼辦呢?MQ 掛了,整套系統崩潰了,這不就涼了嗎?

3 消息隊列技術選型?

在這裏插入圖片描述

4 如何保證消息不被重複消費?

我們可以使用接口冪等來處理消息重複消費的問題。

冪等是一個數學與計算機學概念,在編程中一個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同。例如,setTrue() 函數就是一個冪等函數,無論多次執行,其結果都是一樣的,更復雜的操作冪等保證是利用唯一交易號(流水號)來實現的。

那麼是如何保證冪等的呢?一般冪等,需要分場景去考慮,看是強校驗還是弱校驗,比如跟金錢相關的場景就很關鍵,要做強校驗,別不是很重要的場景做弱校驗。

以電商舉例,強校驗就是當有消息過來時拿着訂單號+業務場景這樣的唯一標識去流水錶查,看看有沒有這條流水,有就直接 return 不要走下面的流程了,沒有就執行後面的邏輯。弱校驗就是使用 id +場景唯一標識作爲 Redis 的 key,放到緩存裏面,失效時間看場景,一定時間內的這個消息就去 Redis 判斷,這樣有一定概率會發生消息丟失,但在這樣的場景下也是可以接受的。

5 如何保證消息的順序性?

生產者消費者一般需要保證順序消息的話,可能就是一個業務場景下的,其 id 應該是一致的,例如訂單的創建、支付、發貨、收貨,它們的訂單號總得是相同的吧?

既然如此,我們讓同一個訂單發送到同一個隊列中,再使用同步發送,只有同個訂單的創建消息發送成功,再發送支付消息。這樣,我們保證了發送有序。需要注意的是,消息隊列僅保證順序發送,順序消費由消費者業務保證。

6 如何處理消息丟失的問題?

我們以 RabbitMQ 爲例子。

數據在生產者中丟失

可能數據在從生產者到消息隊列的路上就丟失了,原因可能有很多,比如網絡問題啥的。我們可以使用 RabbitMQ 的事務功能,生產者發送數據之前開啓 RabbitMQ 事務,然後發送消息,如果消息沒有成功被 RabbitMQ 接收到,那麼生產者會收到異常報錯,此時就可以回滾事務,然後重試發送消息;如果收到了消息,那麼可以提交事務。但這有個大問題,使用事務機制之後,吞吐量會降低,造成性能下降比較嚴重。

事實上我們可以使用 confirm 模式。這樣每次寫消息都會分配一個唯一的 id,如果寫入了 RabbitMQ 中,RabbitMQ 會給你回傳一個 ack 消息,告訴你說這個消息 ok 了。如果 RabbitMQ 沒能處理這個消息,會回調你一個 nack 接口,告訴你這個消息接收失敗,你可以重試。而且你可以結合這個機制自己在內存裏維護每個消息 id 的狀態,如果超過一定時間還沒接收到這個消息的回調,那麼你可以重發。

事務機制和 cnofirm 機制最大的區別在於,事務機制是同步的,提交完事務會阻塞,而 confirm 機制是異步的,發送完一條消息之後就可以繼續發送下一條消息,當 RabbitMQ 接收到消息之後會異步回調一個接口通知你這個消息接收到了。一般來說,都選擇使用 cnofirm 機制。

數據在 RabbitMQ 中丟失

持久化 RabbitMQ,即消息寫入之後會持久化到磁盤即可。

設置 RabbitMQ 的持久化有兩個步驟,一是創建 queue 時將其設置爲持久化的,這樣 RabbitMQ 會持久化 queue 的元數據,但是不會持久化 queue 裏的數據。二是將消息設置爲持久化的,這樣 RabbitMQ 就會將消息持久化到磁盤上去。

持久化可以跟生產者的 confirm 機制配合,只有消息被持久化到磁盤之後,纔會通知生產者 ack,所以哪怕是在持久化到磁盤之前,RabbitMQ 掛了,數據丟了,生產者收不到 ack,你也是可以自己重發的。

數據在消費者中丟失

使用 RabbitMQ 提供的 ack 機制,即關閉 RabbitMQ 的自動 ack,然後在代碼中手動 ack。這樣,如果消息丟失,就不會 ack,RabbitMQ 就會把這個消息分配給其他消費者去處理。

7 如何保證消息隊列的高可用?

我們以 RabbitMQ 爲例子講解一下消息隊列的高可用性是如何實現的。RabbitMQ 具有三種模式:

單機模式

在本地用來做 demo 的,正常來說生產環境沒人敢用吧

普通集羣模式

在多臺機器上啓動多個 RabbitMQ 實例,每臺機器啓動一個。但是創建的 queue,只會放在一個 RabbitMQ 實例上,但是每個實例都同步 queue 的元數據。消費的時候,如果連接到了另外一個 RabbitMQ 實例,那麼那個實例會從 queue 所在實例上拉取數據過來。

因爲這導致要麼消費者每次隨機連接一個實例然後拉取數據,要麼固定連接那個 queue 所在實例消費數據,前者有數據拉取的開銷,後者導致單實例性能瓶頸。而且如果那個放 queue 的實例宕機了,會導致接下來其他實例就無法從那個實例拉取。如果開啓了消息持久化,讓 RabbitMQ 落地存儲消息的話,消息不一定會丟,得等這個實例恢復了,然後纔可以繼續從這個 queue 拉取數據。

這方案主要是用來提高吞吐量的,就是說讓集羣中多個節點來服務某個 queue 的讀寫操作。

鏡像集羣模式

這種模式是所謂的 RabbitMQ 的高可用模式。在這種模式下,創建的 queue 無論是元數據還是 queue 裏的消息都會存在於多個實例上,然後每次寫消息到 queue 時,都會自動把消息到多個實例的 queue 裏進行消息同步。

這種模式優點在於即使有機器宕機,其他機器也可以繼續使用。但其缺點也非常明顯,其一是性能開銷大,消息同步所有機器,導致網絡帶寬壓力和消耗很大。其二是該模式缺少擴展性,如果某個 queue 負載很重,即使加機器,新增的機器也包含了這個 queue 的所有數據,並沒有辦法線性擴展 queue。.

8 如何解決消息隊列的延時以及過期失效問題?消息隊列滿了以後該怎麼處理?有幾百萬消息持續積壓幾小時呢?

大量消息在 MQ 裏積壓了幾個小時了還沒解決?

一般這個時候,只能臨時緊急擴容了,具體操作步驟和思路如下:

  1. 先修復 consumer 的問題,確保其恢復消費速度,然後將現有 consumer 都停掉。
  2. 新建一個 topic,partition 是原來的 10 倍,臨時建立好原先 10 倍的 queue 數量。
  3. 然後寫一個臨時的分發數據的 consumer 程序,這個程序部署上去消費積壓的數據,消費之後不做耗時的處理,直接均勻輪詢寫入臨時建立好的 10 倍數量的 queue。
  4. 接着臨時徵用 10 倍的機器來部署 consumer,每一批 consumer 消費一個臨時 queue 的數據。這種做法相當於是臨時將 queue 資源和 consumer 資源擴大 10 倍,以正常的 10 倍速度來消費數據。
  5. 等快速消費完積壓數據之後,得恢復原先部署的架構,重新用原先的 consumer 機器來消費消息。

MQ 中的消息過期失效了?

假設用的是 RabbitMQ,RabbtiMQ 是可以設置過期時間的,也就是 TTL。如果消息在 queue 中積壓超過一定的時間就會被 RabbitMQ 給清理掉,這個數據就沒了。這就不是說數據會大量積壓在 MQ 裏,而是大量的數據會直接搞丟。

這個情況下,就不是說要增加 consumer 消費積壓的消息,因爲實際上沒啥積壓,而是丟了大量的消息。我們可以採取一個方案,就是批量重導,就是大量積壓的時候,就直接丟棄數據了,然後等過了高峯期以後,再開始寫程序,將丟失的那批數據,寫個臨時程序,一點一點的查出來,然後重新灌入 MQ 裏面去,把白天丟的數據給他補回來。

假設 1 萬個訂單積壓在 MQ 裏面,沒有處理,其中 1000 個訂單都丟了,你只能手動寫程序把那 1000 個訂單給查出來,手動發到 MQ 裏去再補一次。

MQ 都快寫滿了?

如果消息積壓在 MQ 裏,你很長時間都沒有處理掉,此時導致 MQ 都快寫滿了,咋辦?這個還有別的辦法嗎?沒有,誰讓你第一個方案執行的太慢了,你臨時寫程序,接入數據來消費,消費一個丟棄一個,都不要了,快速消費掉所有的消息。然後走第二個方案,等過了高峯期再補數據吧。

參考:《吊打面試官》系列-消息隊列基礎
消息隊列
消息中間件面試題:如何解決消息隊列的延時以及過期失效問題?消息隊列滿了以後該怎麼處理?有幾百萬消息持續積壓幾小時呢?

發佈了123 篇原創文章 · 獲贊 226 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章