前言
金三銀四即將來臨,整理了十道十分經(jīng)典的消息隊列面試題,看完肯定對面試有幫助的,大家一起加油哈~
1. 什么是消息隊列
你可以把消息隊列理解為一個使用隊列來通信的組件。它的本質(zhì),就是個轉(zhuǎn)發(fā)器,包含發(fā)消息、存消息、消費(fèi)消息的過程。最簡單的消息隊列模型如下:
我們通常說的消息隊列,簡稱MQ(Message Queue),它其實(shí)就指消息中間件,當(dāng)前業(yè)界比較流行的開源消息中間件包括:RabbitMQ、RocketMQ、Kafka。
2. 消息隊列有哪些使用場景。
有時候面試官會換個角度問你,為什么使用消息隊列。你可以回答以下這幾點(diǎn):
應(yīng)用解耦
流量削峰
異步處理
消息通訊
遠(yuǎn)程調(diào)用
2.1 應(yīng)用解耦
舉個常見業(yè)務(wù)場景:下單扣庫存,用戶下單后,訂單系統(tǒng)去通知庫存系統(tǒng)扣減。傳統(tǒng)的做法就是訂單系統(tǒng)直接調(diào)用庫存系統(tǒng):
如果庫存系統(tǒng)無法訪問,下單就會失敗,訂單和庫存系統(tǒng)存在耦合關(guān)系
如果業(yè)務(wù)又接入一個營銷積分服務(wù),那訂單下游系統(tǒng)要擴(kuò)充,如果未來接入越來越多的下游系統(tǒng),那訂單系統(tǒng)代碼需要經(jīng)常修改
如何解決這個問題呢?可以引入消息隊列
訂單系統(tǒng):用戶下單后,消息寫入到消息隊列,返回下單成功
庫存系統(tǒng):訂閱下單消息,獲取下單信息,進(jìn)行庫存扣減操作。
2.2 流量削峰
流量削峰也是消息隊列的常用場景。我們做秒殺實(shí)現(xiàn)的時候,需要避免流量暴漲,打垮應(yīng)用系統(tǒng)的風(fēng)險??梢栽趹?yīng)用前面加入消息隊列。
假設(shè)秒殺系統(tǒng)每秒最多可以處理2k個請求,每秒?yún)s有5k的請求過來,可以引入消息隊列,秒殺系統(tǒng)每秒從消息隊列拉2k請求處理得了。
有些伙伴擔(dān)心這樣會出現(xiàn)消息積壓的問題,
首先秒殺活動不會每時每刻都那么多請求過來,高峰期過去后,積壓的請求可以慢慢處理;
其次,如果消息隊列長度超過最大數(shù)量,可以直接拋棄用戶請求或跳轉(zhuǎn)到錯誤頁面;
2.3 異步處理
我們經(jīng)常會遇到這樣的業(yè)務(wù)場景:用戶注冊成功后,給它發(fā)個短信和發(fā)個郵件。
如果注冊信息入庫是30ms,發(fā)短信、郵件也是30ms,三個動作串行執(zhí)行的話,會比較耗時,響應(yīng)90ms:
如果采用并行執(zhí)行的方式,可以減少響應(yīng)時間。注冊信息入庫后,同時異步發(fā)短信和郵件。如何實(shí)現(xiàn)異步呢,用消息隊列即可,就是說,注冊信息入庫成功后,寫入到消息隊列(這個一般比較快,如只需要3ms),然后異步讀取發(fā)郵件和短信。
2.4 消息通訊
消息隊列內(nèi)置了高效的通信機(jī)制,可用于消息通訊。如實(shí)現(xiàn)點(diǎn)對點(diǎn)消息隊列、聊天室等。
2.5 遠(yuǎn)程調(diào)用
我們公司基于MQ,自研了遠(yuǎn)程調(diào)用框架。
3. 消息隊列如何解決消息丟失問題?
一個消息從生產(chǎn)者產(chǎn)生,到被消費(fèi)者消費(fèi),主要經(jīng)過這3個過程:
因此如何保證MQ不丟失消息,可以從這三個階段闡述:
生產(chǎn)者保證不丟消息
存儲端不丟消息
消費(fèi)者不丟消息
3.1 生產(chǎn)者保證不丟消息
生產(chǎn)端如何保證不丟消息呢?確保生產(chǎn)的消息能到達(dá)存儲端。
如果是RocketMQ消息中間件,Producer生產(chǎn)者提供了三種發(fā)送消息的方式,分別是:
同步發(fā)送
異步發(fā)送
單向發(fā)送
生產(chǎn)者要想發(fā)消息時保證消息不丟失,可以:
采用同步方式發(fā)送,send消息方法返回成功狀態(tài),就表示消息正常到達(dá)了存儲端Broker。
如果send消息異常或者返回非成功狀態(tài),可以重試。
可以使用事務(wù)消息,RocketMQ的事務(wù)消息機(jī)制就是為了保證零丟失來設(shè)計的
3.2 存儲端不丟消息
如何保證存儲端的消息不丟失呢?確保消息持久化到磁盤。大家很容易想到就是刷盤機(jī)制。
刷盤機(jī)制分同步刷盤和異步刷盤:
生產(chǎn)者消息發(fā)過來時,只有持久化到磁盤,RocketMQ的存儲端Broker才返回一個成功的ACK響應(yīng),這就是同步刷盤。它保證消息不丟失,但是影響了性能。
異步刷盤的話,只要消息寫入PageCache緩存,就返回一個成功的ACK響應(yīng)。這樣提高了MQ的性能,但是如果這時候機(jī)器斷電了,就會丟失消息。
Broker一般是集群部署的,有master主節(jié)點(diǎn)和slave從節(jié)點(diǎn)。消息到Broker存儲端,只有主節(jié)點(diǎn)和從節(jié)點(diǎn)都寫入成功,才反饋成功的ack給生產(chǎn)者。這就是同步復(fù)制,它保證了消息不丟失,但是降低了系統(tǒng)的吞吐量。與之對應(yīng)的就是異步復(fù)制,只要消息寫入主節(jié)點(diǎn)成功,就返回成功的ack,它速度快,但是會有性能問題。
3.3 消費(fèi)階段不丟消息
消費(fèi)者執(zhí)行完業(yè)務(wù)邏輯,再反饋會Broker說消費(fèi)成功,這樣才可以保證消費(fèi)階段不丟消息。
4. 消息隊列如何保證消息的順序性。
消息的有序性,就是指可以按照消息的發(fā)送順序來消費(fèi)。有些業(yè)務(wù)對消息的順序是有要求的,比如先下單再付款,最后再完成訂單,這樣等。假設(shè)生產(chǎn)者先后產(chǎn)生了兩條消息,分別是下單消息(M1),付款消息(M2),M1比M2先產(chǎn)生,如何保證M1比M2先被消費(fèi)呢。
為了保證消息的順序性,可以將M1、M2發(fā)送到同一個Server上,當(dāng)M1發(fā)送完收到ack后,M2再發(fā)送。如圖:
這樣還是可能會有問題,因?yàn)閺腗Q服務(wù)器到消費(fèi)端,可能存在網(wǎng)絡(luò)延遲,雖然M1先發(fā)送,但是它比M2晚到。
那還能怎么辦才能保證消息的順序性呢?將M1和M2發(fā)往同一個消費(fèi)者,且發(fā)送M1后,等到消費(fèi)端ACK成功后,才發(fā)送M2就得了。
消息隊列保證順序性整體思路就是這樣啦。比如Kafka的全局有序消息,就是這種思想的體現(xiàn): 就是生產(chǎn)者發(fā)消息時,1個Topic只能對應(yīng)1個Partition,一個 Consumer,內(nèi)部單線程消費(fèi)。
但是這樣吞吐量太低,一般保證消息局部有序即可。在發(fā)消息的時候指定Partition Key,Kafka對其進(jìn)行Hash計算,根據(jù)計算結(jié)果決定放入哪個Partition。這樣Partition Key相同的消息會放在同一個Partition。然后多消費(fèi)者單線程消費(fèi)指定的Partition。
5.消息隊列有可能發(fā)生重復(fù)消費(fèi),如何避免,如何做到冪等?
消息隊列是可能發(fā)生重復(fù)消費(fèi)的。
生產(chǎn)端為了保證消息的可靠性,它可能往MQ服務(wù)器重復(fù)發(fā)送消息,直到拿到成功的ACK。
再然后就是消費(fèi)端,消費(fèi)端消費(fèi)消息一般是這個流程:拉取消息、業(yè)務(wù)邏輯處理、提交消費(fèi)位移。假設(shè)業(yè)務(wù)邏輯處理完,事務(wù)提交了,但是需要更新消費(fèi)位移時,消費(fèi)者卻掛了,這時候另一個消費(fèi)者就會拉到重復(fù)消息了。
如何冪等處理重復(fù)消息呢?
我之前寫過一篇冪等設(shè)計的文章,大家有興趣可以看下哈:聊聊冪等設(shè)計
冪等處理重復(fù)消息,簡單來說,就是搞個本地表,帶唯一業(yè)務(wù)標(biāo)記的,利用主鍵或者唯一性索引,每次處理業(yè)務(wù),先校驗(yàn)一下就好啦。又或者用redis緩存下業(yè)務(wù)標(biāo)記,每次看下是否處理過了。
6. 如何處理消息隊列的消息積壓問題
消息積壓是因?yàn)樯a(chǎn)者的生產(chǎn)速度,大于消費(fèi)者的消費(fèi)速度。遇到消息積壓問題時,我們需要先排查,是不是有bug產(chǎn)生了。
如果不是bug,我們可以優(yōu)化一下消費(fèi)的邏輯,比如之前是一條一條消息消費(fèi)處理的話,我們可以確認(rèn)是不是可以優(yōu)為批量處理消息。如果還是慢,我們可以考慮水平擴(kuò)容,增加Topic的隊列數(shù),和消費(fèi)組機(jī)器的數(shù)量,提升整體消費(fèi)能力。
如果是bug導(dǎo)致幾百萬消息持續(xù)積壓幾小時。有如何處理呢?需要解決bug,臨時緊急擴(kuò)容,大概思路如下:
先修復(fù)consumer消費(fèi)者的問題,以確保其恢復(fù)消費(fèi)速度,然后將現(xiàn)有consumer 都停掉。
新建一個 topic,partition 是原來的 10 倍,臨時建立好原先10倍的queue 數(shù)量。
然后寫一個臨時的分發(fā)數(shù)據(jù)的 consumer 程序,這個程序部署上去消費(fèi)積壓的數(shù)據(jù),消費(fèi)之后不做耗時的處理,直接均勻輪詢寫入臨時建立好的 10 倍數(shù)量的 queue。
接著臨時征用 10 倍的機(jī)器來部署 consumer,每一批 consumer 消費(fèi)一個臨時 queue 的數(shù)據(jù)。這種做法相當(dāng)于是臨時將 queue 資源和 consumer 資源擴(kuò)大 10 倍,以正常的 10 倍速度來消費(fèi)數(shù)據(jù)。
等快速消費(fèi)完積壓數(shù)據(jù)之后,得恢復(fù)原先部署的架構(gòu),重新用原先的 consumer 機(jī)器來消費(fèi)消息。
7. 消息隊列技術(shù)選型,Kafka還是RocketMQ,還是RabbitMQ
先可以對比下它們優(yōu)缺點(diǎn):
Kafka | RocketMQ | RabbitMQ | |
---|---|---|---|
單機(jī)吞吐量 | 17.3w/s | 11.6w/s | 2.6w/s(消息做持久化) |
開發(fā)語言 | Scala/Java | Java | Erlang |
主要維護(hù)者 | Apache | Alibaba | Mozilla/Spring |
訂閱形式 | 基于topic,按照topic進(jìn)行正則匹配的發(fā)布訂閱模式 | 基于topic/messageTag,按照消息類型、屬性進(jìn)行正則匹配的發(fā)布訂閱模式 | 提供了4種:direct, topic ,Headers和fanout。fanout就是廣播模式 |
持久化 | 支持大量堆積 | 支持大量堆積 | 支持少量堆積 |
順序消息 | 支持 | 支持 | 不支持 |
集群方式 | 天然的Leader-Slave,無狀態(tài)集群,每臺服務(wù)器既是Master也是Slave | 常用 多對’Master-Slave’ 模式,開源版本需手動切換Slave變成Master | 支持簡單集群,'復(fù)制’模式,對高級集群模式支持不好。 |
性能穩(wěn)定性 | 較差 | 一般 | 好 |
RabbitMQ是開源的,比較穩(wěn)定的支持,活躍度也高,但是不是Java語言開發(fā)的。
很多公司用RocketMQ,比較成熟,是阿里出品的。
如果是大數(shù)據(jù)領(lǐng)域的實(shí)時計算、日志采集等場景,用 Kafka 是業(yè)內(nèi)標(biāo)準(zhǔn)的。
8. 消息中間件如何做到高可用
消息中間件如何保證高可用呢?單機(jī)是沒有高可用可言的,高可用都是對集群來說的,一起看下kafka的高可用吧。
Kafka 的基礎(chǔ)集群架構(gòu),由多個broker組成,每個broker都是一個節(jié)點(diǎn)。當(dāng)你創(chuàng)建一個topic時,它可以劃分為多個partition,而每個partition放一部分?jǐn)?shù)據(jù),分別存在于不同的 broker 上。也就是說,一個 topic 的數(shù)據(jù),是分散放在多個機(jī)器上的,每個機(jī)器就放一部分?jǐn)?shù)據(jù)。
有些伙伴可能有疑問,每個partition放一部分?jǐn)?shù)據(jù),如果對應(yīng)的broker掛了,那這部分?jǐn)?shù)據(jù)是不是就丟失了?那還談什么高可用呢?
Kafka 0.8 之后,提供了復(fù)制品副本機(jī)制來保證高可用,即每個 partition 的數(shù)據(jù)都會同步到其它機(jī)器上,形成多個副本。然后所有的副本會選舉一個 leader 出來,讓leader去跟生產(chǎn)和消費(fèi)者打交道,其他副本都是follower。寫數(shù)據(jù)時,leader 負(fù)責(zé)把數(shù)據(jù)同步給所有的follower,讀消息時,直接讀 leader 上的數(shù)據(jù)即可。如何保證高可用的?就是假設(shè)某個 broker 宕機(jī),這個broker上的partition 在其他機(jī)器上都有副本的。如果掛的是leader的broker呢?其他follower會重新選一個leader出來。
9. 如何保證數(shù)據(jù)一致性,事務(wù)消息如何實(shí)現(xiàn)
一條普通的MQ消息,從產(chǎn)生到被消費(fèi),大概流程如下:
生產(chǎn)者產(chǎn)生消息,發(fā)送帶MQ服務(wù)器
MQ收到消息后,將消息持久化到存儲系統(tǒng)。
MQ服務(wù)器返回ACk到生產(chǎn)者。
MQ服務(wù)器把消息push給消費(fèi)者
消費(fèi)者消費(fèi)完消息,響應(yīng)ACK
MQ服務(wù)器收到ACK,認(rèn)為消息消費(fèi)成功,即在存儲中刪除消息。
我們舉個下訂單的例子吧。訂單系統(tǒng)創(chuàng)建完訂單后,再發(fā)送消息給下游系統(tǒng)。如果訂單創(chuàng)建成功,然后消息沒有成功發(fā)送出去,下游系統(tǒng)就無法感知這個事情,出導(dǎo)致數(shù)據(jù)不一致。
如何保證數(shù)據(jù)一致性呢?可以使用事務(wù)消息。一起來看下事務(wù)消息是如何實(shí)現(xiàn)的吧。
生產(chǎn)者產(chǎn)生消息,發(fā)送一條半事務(wù)消息到MQ服務(wù)器
MQ收到消息后,將消息持久化到存儲系統(tǒng),這條消息的狀態(tài)是待發(fā)送狀態(tài)。
MQ服務(wù)器返回ACK確認(rèn)到生產(chǎn)者,此時MQ不會觸發(fā)消息推送事件
生產(chǎn)者執(zhí)行本地事務(wù)
如果本地事務(wù)執(zhí)行成功,即commit執(zhí)行結(jié)果到MQ服務(wù)器;如果執(zhí)行失敗,發(fā)送rollback。
如果是正常的commit,MQ服務(wù)器更新消息狀態(tài)為可發(fā)送;如果是rollback,即刪除消息。
如果消息狀態(tài)更新為可發(fā)送,則MQ服務(wù)器會push消息給消費(fèi)者。消費(fèi)者消費(fèi)完就回ACK。
如果MQ服務(wù)器長時間沒有收到生產(chǎn)者的commit或者rollback,它會反查生產(chǎn)者,然后根據(jù)查詢到的結(jié)果執(zhí)行最終狀態(tài)。
10. 讓你寫一個消息隊列,該如何進(jìn)行架構(gòu)設(shè)計?
這個問題面試官主要考察三個方面的知識點(diǎn):
你有沒有對消息隊列的架構(gòu)原理比較了解
考察你的個人設(shè)計能力
考察編程思想,如什么高可用、可擴(kuò)展性、冪等等等。
遇到這種設(shè)計題,大部分人會很蒙圈,因?yàn)槠綍r沒有思考過類似的問題。大多數(shù)人平時埋頭增刪改啥,不去思考框架背后的一些原理。有很多類似的問題,比如讓你來設(shè)計一個 Dubbo 框架,或者讓你來設(shè)計一個MyBatis 框架,你會怎么思考呢?
回答這類問題,并不要求你研究過那技術(shù)的源碼,你知道那個技術(shù)框架的基本結(jié)構(gòu)、工作原理即可。設(shè)計一個消息隊列,我們可以從這幾個角度去思考:
首先是消息隊列的整體流程,producer發(fā)送消息給broker,broker存儲好,broker再發(fā)送給consumer消費(fèi),consumer回復(fù)消費(fèi)確認(rèn)等。
producer發(fā)送消息給broker,broker發(fā)消息給consumer消費(fèi),那就需要兩次RPC了,RPC如何設(shè)計呢?可以參考開源框架Dubbo,你可以說說服務(wù)發(fā)現(xiàn)、序列化協(xié)議等等
broker考慮如何持久化呢,是放文件系統(tǒng)還是數(shù)據(jù)庫呢,會不會消息堆積呢,消息堆積如何處理呢。
消費(fèi)關(guān)系如何保存呢?點(diǎn)對點(diǎn)還是廣播方式呢?廣播關(guān)系又是如何維護(hù)呢?zk還是config server
消息可靠性如何保證呢?如果消息重復(fù)了,如何冪等處理呢?
消息隊列的高可用如何設(shè)計呢?可以參考Kafka的高可用保障機(jī)制。多副本 -> leader & follower -> broker 掛了重新選舉 leader 即可對外服務(wù)。
消息事務(wù)特性,與本地業(yè)務(wù)同個事務(wù),本地消息落庫;消息投遞到服務(wù)端,本地才刪除;定時任務(wù)掃描本地消息庫,補(bǔ)償發(fā)送。
MQ得伸縮性和可擴(kuò)展性,如果消息積壓或者資源不夠時,如何支持快速擴(kuò)容,提高吞吐?可以參照一下 Kafka 的設(shè)計理念,broker -> topic -> partition,每個 partition 放一個機(jī)器,就存一部分?jǐn)?shù)據(jù)。如果現(xiàn)在資源不夠了,簡單啊,給 topic 增加 partition,然后做數(shù)據(jù)遷移,增加機(jī)器,不就可以存放更多數(shù)據(jù),提供更高的吞吐量了?
審核編輯 :李倩
-
開源
+關(guān)注
關(guān)注
3文章
3358瀏覽量
42523 -
消息隊列
+關(guān)注
關(guān)注
0文章
33瀏覽量
2997
原文標(biāo)題:消息隊列經(jīng)典十連問
文章出處:【微信號:DBDevs,微信公眾號:數(shù)據(jù)分析與開發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論