java【消息隊列】面試題


一、什么是消息隊列?

消息隊列,是分布式系統中重要的組件。

  • 主要解決應用耦合,異步消息,流量削峰等問題。
  • 可實現高性能,高可用,可伸縮和最終一致性架構,是大型分布式系統中不可缺少的中間件。

目前主流的消息隊列有:

  • Kafka
  • RabbitMq
  • RocketMq,老版本是MetaQ
  • ActiveMq,目前用的人是越來越少了。

另外,消息隊列容易和java中的本地MessageQuene搞混,所以消息隊列更多的被稱為消息中間件或者分布式消息隊列等。

二、消息隊列由哪些角色組成?

 

 

 

  • 生產者(Producer):負責生產消息
  • 消費者(Consumer):負責消費消息
  • 消息代理(Message Broker):負責存儲消息和轉發消息兩件事情。其中,轉發消息分為推送和拉去兩種方式。
    • 拉取(Pull),是指Consumer主動從Message Borker獲取消息
    • 推送(Push),是指Message Broker主動將Consumer感興趣的消息推送給Consumer。

三、消息隊列有哪些使用場景?

一般來說,有四大應用場景:

  • 應用解耦
  • 異步處理
  • 流量削峰
  • 消息通訊
  • 日志處理

其中,應用解耦、異步處理是比較核心的。

四、為什么使用消息隊列進行應用解耦?

傳統模式下,如下圖所示:

 

 

 

 

  • 缺點比較明顯,系統間耦合性太強,系統A在代碼中直接調用系統B和系統C的代碼,如果將來D系統接入,系統A還需要修改代碼,過於麻煩!並且萬一系統A、B、C改接口,還要持續跟進。

引入消息隊列,

 

 

  • 將消息寫入消息隊列,需要消息的系統自己從消息隊列中訂閱,從而系統A不需要做任何修改。所以,有了消息隊列之后,從主動調用的方式,變成了消息的訂閱發布(或者說,事件的發布和監聽),從而解耦。舉個實際場景的例子,用戶支付訂單完成后,系統需要給用戶發紅包,增加積分等行為,就可以通過這樣的方式進行解耦。

五、為什么使用消息隊列進行異步處理?

 

 

  • A系統需要串行逐個同步調用系統B、C、D。這其中會有很多問題。
    • 如果每個系統調用執行是200ms,那么這個邏輯就要執行600ms。非常慢
    • 如果任意一個系統調用異常報錯,那個整個邏輯就報錯了
    • 如果任意一個系統調用超時了,那么整個邏輯就超時了。
    • 。。。。。

引入消息隊列后,如下圖所示:

 

 

  • 通過發送3條MQ消息,通過Consumer消費,從而異步,並行調用系統B、C、D
    • 因為發送Mq消息是比較快的,假設每個操作2ms,那個這個邏輯只要執行6ms,非常快。
    • 當然了,可能發送MQ消息會失敗。當然,這個是會存在的。此時可以異步重試,當然,可能異步重試的過程中,JVM進程掛了,此時又需要其他的機制來保證,不過,相比串行逐個同步調用系統B、C、D來說,出錯的幾率會低很多。

六、為什么使用消息隊列進行流量削峰?

 

 

對於大多數系統,一定會有訪問量的波峰和波谷。比較明顯的,就是我們經常使用的美團外賣,又或者被人詬病的小米秒殺。

如果在並發量大的時間,所有請求直接打到數據庫,造成數據庫直接掛掉

 

 

 

通過將請求先轉發到消息隊列匯總,然后,系統A慢慢的按照數據庫能處理的並發量,從消息隊列中逐步拉去消息進行消費。在生產中,這個暫時的高峰期積壓是允許的。

相對來說,消息隊列的性能會比數據庫性能更好,並且,橫向拓展能力更強。

七、為什么使用消息隊列進行消息通信?

消息通訊是指:消息隊列一般都內置了高效的通信機制,因此也可以 用在純的消息通訊。

  • IM 聊天。
  • 點對點消息隊列。可能大家會比較懵逼,有基於消息隊列的 RPC 框架實現,例如 rabbitmq-jsonrpc ,雖然現在用的人比較少。
  • 面向物聯網的 MQTT 。阿里在開源的 RocketMQ 基礎上,增加了 MQTT 協議的支持,可見消息隊列 for IoT

八、如何使用消息隊列進行日志處理?

日志處理:是指將消息隊列應用在日志處理中,比如kafka的應用,解決大量日志傳輸的問題。

 

 

 

  • 日志采集客戶端,負責日志數據采集,定時批量寫入kafka隊列。
  • kafka消息隊列,負責日志數據的接收、存儲和轉發
  • 日志處理應用:訂閱並消費kafka隊列中的日志數據

大家最熟悉的就是ELK+Kafka日志方案,如下:

  • kafka:接收用戶日志的消息隊列。
  • Logstash:對接kafka寫入的日志,做日志解析,統一成Json輸出給 Elasticsearch 中。
  • Elasticsearch :實時日志分析服務的核心技術,一個 schemaless ,實時的數據存儲服務,通過 index 組織數據,兼具強大的搜索和統計功能。
  • Kibna:基於Elasticsearch的數據可視化組件,超強的數據可視化能力是眾多公司選擇 ELK stack 的重要原因。

九、消息隊列有什么優缺點?

任何中間件的引入,帶來優點的時候,也同時會帶來缺點。

缺點,主要是如下三點:
  • 系統可用性降低。
系統引入的外部依賴越多,越容易掛掉。本來你就是 A 系統調用 BCD 三個系統的接口就好了,本來 ABCD 四個系統好好的,沒啥問題,你偏加個 MQ 進來,萬一 MQ 掛了咋整,MQ 一掛,整套系統崩潰的,你不就完了?所以,消息隊列一定要做好高可用。
  • 系統復雜度提高。
主要需要多考慮,1)消息怎么不重復消息。2)消息怎么保證不丟失。3)需要消息順序的業務場景,怎么處理。
  • 一致性問題。
A 系統處理完了直接返回成功了,人都以為你這個請求就成功了。但是問題是,要是 B、C。D 三個系統那里,B、D 兩個系統寫庫成功了,結果 C 系統寫庫失敗了,咋整?你這數據就不一致了。 當然,這不僅僅是MQ 的問題,引入 RPC 之后本身就存在這樣的問題。如果我們在使用 MQ 時,一定要達到數據的最終一致性。即,C 系統最終執行完成。
十、消息隊列有幾種消費語義?
一共有三種,分別如下:
  1.消息至多被消費一次(At most once):消息可能會丟失,但絕對不會重傳
  2.消息至少被消費一次(At least once):消息可以重傳,但絕對不會丟失。
  3.消息僅被消費一次(Exactly once):每一條消息只被傳遞一次。
為了支持上面3種消費語義,可以分為3個階段,考慮消息隊列系統中Producer、Message Broker、Consumer需要滿足的條件。
1.消息至多被消費一次。
  該語義是最容易滿足的,特點是整個消息隊列吞吐量大,實現簡單,適合能容忍丟消息。
  • Producer發消息給message Broker階段
    • Producer發消息給message Broker時,不要求Messaage Broker對接收的消息響應確認,Producer也不用關心Message Broker是否收到消息了。
  • Message Broker存儲/轉發階段。
    • 對Message Broker的存儲不要求持久性
    • 轉發消息時,也不關心Consumer是否真的收到了。
  • Consumer消費階段
    • Consumer從Message Broker中獲取到消息后,可以從Message Broker刪除消息。
    • 或者Consumer Broker在消息被Consumer拿去消費時刪除消息,不用關心Consumer最后對消息的消費情況如何。

2.消息至少被消費一次

適合不能容忍丟消息,允許重復消費的任務。

  • Producer發送消息到Message Broker階段
    • Producer發消息給Message Broker,Message Broker必須響應對消息的確認。
  • Message Broker存儲/轉發階段
    • Message Broker必須提供持久性的保障
    • 轉發消息時,Message Broker需要Consumer通知刪除消息,才能將消息刪除。
  • Consumer消費階段
    • Consumer從Message Broker中獲取消息,必須在消費完成后,Message Broker上的消息才能被刪除。

3.消息僅被消費一次

適合對消費消費情況要求非常高的任務,實現較為復雜。

在這里需要考慮一個問題,就是這里的“僅被消費一次”指的是如下哪種情況:

  • Message Broker上存儲的消息被Consumer僅消費一次。
    • Producer發送消息到Message Broker階段
      • Producer發消息給Message Broker時,不要求Message Broker對接收到的消息響應確認。Producer也不關心Message Broker是否收到消息了。
    • Message Broker存儲/轉發階段
      • Message Broker必須提供持久性保障
      • 並且,每條消息在其消費隊列里有唯一標識(這個唯一標識可以由Producer產生,也可以由Message Broker產生)
    • Consumer消費階段
      • Consumer從Message Broker中獲取到消息后,需要記錄下消費的消費標識,以便在后續消費中防止對某個消息重復消費(比如Consumer獲取到消息,消費完后,還沒有來得及從Messaage Broker刪除消息九掛了,這樣Message Broker如果把消息重新加入待消費隊列的話,那么這條消息就會被重復消費了。)
  • Producer上產生的消息僅被Consumer僅消費一次。
    • Producer發送消息到Message Broker階段
      • Producer發消息到Message Broker時,Message Broker必須響應對消息的確認,並且Producer負責為該消息產生一個標識,以防止Consumer重復消費(因為Producer發消息給Message Broker后,由於網絡問題沒收到Message Broker的響應,可能會重發消息給高Message Broker)。
    • Message Broker存儲/轉發階段
      • Message Broker必須提供持久性保障
      • 並且,每條消息在其消息隊列中有唯一標識(這一個標識需要由Producer產生)
    • Consumer消費階段
      • Consumer僅被消費一次。

雖然3種方式看起來比較復雜,但是我們會發現,是層層遞進,越來越可靠。

實際生產場景下,我們是傾向第三種的第二種情況,每條消息從Producer保證被送達,並且被Consumer僅消費一次。當然,重心還是如何保證Consumer僅消費一次。雖然說,消息產生的唯一標識可以在框架級去做排重,但也是最穩妥的,還是業務層也保證消費的冪等性。

十一、消息隊列有幾種投遞方式?分別有什么缺點?

消息隊列有兩種投遞方式:push推送和pull拉取

push:

  • 優點:就是及時性
  • 缺點:就是受限於消費者的消費能力,可能造成消息的堆積,Broker會不斷的給消費者發送不能處理的消息。

pull:

  • 優點:就是主動權掌握在消費者,可以根據自己的消費速度進行消息拉取。
  • 缺點:就是消費方不知道什么時候可以獲取最新的消息,會有消息延遲和忙等。

目前的消息隊列,基於push+pull模式結合的方式,Broker僅僅告訴Consumer有新的消息,具體的消息拉取,還需要Consumer自己主動拉取。

十二、如何保證消費者的消息的冪等性?

如果要到達消費者的消息消息的冪等性,就需要消息僅被消費一次,且每一條消息從Producer保證被送達,並且被Consumer僅消費一次。

對於Producer來說:

  • 可能因為網絡問題,Producer重試多次發送消息,實際第一次就發送成功,那么就會產生多條相同的消息。

對於Consumer來說:

  • 可能因為Broker的消息進度丟失,導致消息重復投遞給Consumer。
  • Consumer消費成功,但是因為JVM異常崩潰,導致消息的消費進度未及時同步給Consumer。

如何解決?

所以,上述的種種情況,都可能導致消費者會獲取到重復的消息,那么我們的死來就無法是解決不發送、投遞重復的消息,而是消費者在消費時,如何保證冪等性。

消費者實現冪等性,有兩種方式:

1.框架層統一封裝

2.業務層自己實現。

  • 1.框架層統一封裝

首先需要有一個消息排重的唯一標識,該編號由Producer生成,例如uuid,或者其他唯一編號的算法。

然后就需要一個排重的存儲器,例如說:

  • 使用關系數據庫,增加一個排重表,使用消息編號作為唯一主鍵。
  • 使用 KV 數據庫,KEY 存儲消息編號,VALUE 任一。此處,暫時不考慮 KV 數據庫持久化的問題
那么,我們要什么時候插入這條排重記錄呢?
  • 在消息消費執行業務邏輯之前,插入這條排重記錄。但是,此時會有可能 JVM 異常崩潰。那么 JVM 重啟后,這條消息就無法被消費了。因為,已經存在這條排重記錄。
  • 在消息消費執行業務邏輯之后,插入這條排重記錄。
    • 如果業務邏輯執行失敗,顯然,我們不能插入這條排重記錄,因為我們后續要消費重試。
    • 如果業務邏輯執行成功,此時,我們可以插入這條排重記錄。但是,萬一插入這條排重記錄失敗呢?那
      么,需要讓插入記錄和業務邏輯在同一個事務當中,此時,我們只能使用數據庫。
  • 業務層自己實現
方式很多,這個和 HTTP 請求實現冪等是一樣的邏輯:
  • 先查詢數據庫,判斷數據是否已經被更新過。如果是,則直接返回消費完成,否則執行消費。
  • 更新數據庫時,帶上數據的狀態。如果更新失敗,則直接返回消費完成,否則執行消費。
如果胖友的系統的並發量非常大,可以使用 Zookeeper 或者 Redis 實現分布式鎖,避免並發帶來的問題。當然,
引入一個組件,也會帶來另外的復雜性:
1. 系統的並發能力下降。
2. Zookeeper 和 Redis 在獲取分布式鎖時,發現它們已經掛掉,此時到底要不要繼續執行下去呢?嘿嘿。
選擇
可用有影響,所以最好還是由業務層自己實現處理消息重復的問題。
當然,這兩種方式不是沖突的。可以提供不同類型的消息,根據配置,使用哪種方式。例如說:
默認情況下,開啟【框架層統一封裝】的功能。
可以通過配置,關閉【框架層統一封裝】的功能。
十三、如何保證生產者的發送消息的可靠性?
https://www.cnblogs.com/qingmuchuanqi48/p/11124103.html
十四、如何保證消息的順序性?
https://www.cnblogs.com/qingmuchuanqi48/p/11124112.html
十五、如何解決消息積壓的問題?
//todo
十六、如何解決消息過期的問題?
//todo
十七、消息隊列如何實現高可用?
//todo
十八、Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么優缺點?
 

 

 

ActiveMq

  一般的業務系統要引入Mq,最早大家都用ActiveMQ,但是現在確實大家用的不多了,沒經過大規模吞吐量場景的驗證,社區也不是很活躍

RabbitMQ

  后來大家開始用RabbitMq,但是確實Erlang語言阻止了大量的java工程師去深入研究和掌控它,對公司幾乎處於不可控的狀態,但是確實人家是開源的,比較穩定的支持,社區活躍度也高,另外,因為Spring Cloud在消息隊列上的支持,對RabbitMq是比較不錯的。所以在選型上又更加被推崇。

RocketMQ
不過現在確實越來越多的公司,會去用 RocketMQ,確實很不錯(阿里出品)。但社區可能有突然黃掉的風險,對自己公司技術實力有絕對自信的,推薦用 RocketMQ,否則回去老老實實用 RabbitMQ 吧,人家有活躍的開源社區,絕對不會黃。 目前已經加入 Apache ,所以社區層面有相應的保證,並且是使用 Java 語言進行實現,對於Java 工程師更容易去深入研究和掌控它。目前,也是比較推薦去選擇的。並且,如果使用阿里雲,可以直接使用其雲服務。當然,現在比較被社區詬病的是,官方暫未提供比較好的中文文檔,國內外也缺乏比較好的 RocketMQ 書籍,所以是比較大的痛點。
總結
所以中小型公司,技術實力較為一般,技術挑戰不是特別高,用 RabbitMQ 是不錯的選擇
大型公司,基礎架構研發實力較強,用 RocketMQ 是很好的選擇。當然,中小型公司使用 RocketMQ 也是沒什么問題的選擇,特別是以 Java 為主語言的公司。如果是大數據領域的實時計算、日志采集等場景,用 Kafka 是業內標准的,絕對沒問題,社區活躍度很高,絕對不會黃,何況幾乎是全世界這個領域的事實性規范。另外,目前國內也是有非常多的公司,將 Kafka 應用在業務系統中,例如唯品會、陸金所、美團等等。目前,的團隊使用 RocketMQ 作為消息隊列,因為有 RocketMQ 5 年左右使用經驗,並且目前線上環境是使用阿里雲,適合我們團隊。
十九、消息隊列的一般存儲方式有哪些?
當前業界幾款主流的MQ消息隊列采用的存儲方式主要有以下三種方式。
1. 分布式KV存儲
這類 MQ 一般會采用諸如 LevelDB 、RocksDB 和 Redis 來作為消息持久化的方式。由於分布式緩存的讀寫能力要優於 DB ,所以在對消息的讀寫能力要求都不是比較高的情況下,采用這種方式倒也不失為一種可以替代的設計方案。
消息存儲於分布式 KV 需要解決的問題在於如何保證 MQ 整體的可靠性。
 2. 文件系統
目前業界較為常用的幾款產品(RocketMQ / Kafka / RabbitMQ)均采用的是消息刷盤至所部署虛擬機/物理機的文件系統來做持久化(刷盤一般可以分為異步刷盤和同步刷盤兩種模式)。
消息刷盤為消息存儲提供了一種高效率、高可靠性和高性能的數據持久化方式。除非部署 MQ 機器本身或是本地磁
盤掛了,否則一般是不會出現無法持久化的故障問題。
🦅 3. 關系型數據庫 DB
Apache下開源的另外一款MQ—ActiveMQ(默認采用的KahaDB做消息存儲)可選用 JDBC 的方式來做消息持久化,通過簡單的 XML 配置信息即可實現JDBC消息存儲。由於,普通關系型數據庫(如 MySQL )在單表數據量達到千萬級別的情況下,其 IO 讀寫性能往往會出現瓶頸。
因此,如果要選型或者自研一款性能強勁、吞吐量大、消息堆積能力突出的 MQ 消息隊列,那么並不推薦采用關系型數據庫作為消息持久化的方案。在可靠性方面,該種方案非常依賴 DB ,如果一旦 DB 出現故障,則 MQ 的消息就無法落盤存儲會導致線上故障
總結:
因此,綜合上所述從存儲效率來說,文件系統 > 分布式 KV 存儲 > 關系型數據庫 DB ,直接操作文件系統肯定是最快和最高效的,而關系型數據庫 TPS 一般相比於分布式 KV 系統會更低一些(簡略地說,關系型數據庫本身也是一個需要讀寫文件 Server ,這時 MQ 作為 Client與其建立連接並發送待持久化的消息數據,同時又需要依賴 DB 的事務等,這一系列操作都比較消耗性能),所以如果追求高效的IO讀寫,那么選擇操作文件系統會更加合適一些。但是如果從易於實現和快速集成來看,文件系統 > 分布式 KV 存儲 > 關系型數據庫 DB,但是性能會下降很多。另外,從消息中間件的本身定義來考慮,應該盡量減少對於外部第三方中間件的依賴。一般來說依賴的外部系統越多,也會使得本身的設計越復雜,所以個人的理解是采用文件系統作為消息存儲的方式,更貼近消息中間件本身的
定義。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM