大家好呀,金三銀四即將來臨,整理了十道十分經典的消息隊列面試題,看完肯定對面試有幫助的,大家一起加油哈~
你可以把消息隊列理解為一個使用隊列來通信的組件。它的本質,就是個轉發(fā)器,包含發(fā)消息、存消息、消費消息的過程。最簡單的消息隊列模型如下:
我們通常說的消息隊列,簡稱MQ(Message Queue),它其實就指消息中間件,當前業(yè)界比較流行的開源消息中間件包括:RabbitMQ、RocketMQ、Kafka
。
有時候面試官會換個角度問你,為什么使用消息隊列。你可以回答以下這幾點:
舉個常見業(yè)務場景:下單扣庫存,用戶下單后,訂單系統(tǒng)去通知庫存系統(tǒng)扣減。傳統(tǒng)的做法就是訂單系統(tǒng)直接調用庫存系統(tǒng):
如何解決這個問題呢?可以引入消息隊列
流量削峰也是消息隊列的常用場景。我們做秒殺實現(xiàn)的時候,需要避免流量暴漲,打垮應用系統(tǒng)的風險??梢栽趹们懊婕尤胂㈥犃?。
假設秒殺系統(tǒng)每秒最多可以處理2k
個請求,每秒卻有5k
的請求過來,可以引入消息隊列,秒殺系統(tǒng)每秒從消息隊列拉2k請求處理得了。
有些伙伴擔心這樣會出現(xiàn)消息積壓的問題,
我們經常會遇到這樣的業(yè)務場景:用戶注冊成功后,給它發(fā)個短信和發(fā)個郵件。
如果注冊信息入庫是30ms,發(fā)短信、郵件也是30ms,三個動作串行執(zhí)行的話,會比較耗時,響應90ms:
如果采用并行執(zhí)行的方式,可以減少響應時間。注冊信息入庫后,同時異步發(fā)短信和郵件。如何實現(xiàn)異步呢,用消息隊列即可,就是說,注冊信息入庫成功后,寫入到消息隊列(這個一般比較快,如只需要3ms),然后異步讀取發(fā)郵件和短信。
消息隊列內置了高效的通信機制,可用于消息通訊。如實現(xiàn)點對點消息隊列、聊天室等。
我們公司基于MQ,自研了遠程調用框架。
一個消息從生產者產生,到被消費者消費,主要經過這3個過程:
因此如何保證MQ不丟失消息,可以從這三個階段闡述:
生產端如何保證不丟消息呢?確保生產的消息能到達存儲端。
如果是RocketMQ消息中間件,Producer生產者提供了三種發(fā)送消息的方式,分別是:
生產者要想發(fā)消息時保證消息不丟失,可以:
如何保證存儲端的消息不丟失呢?確保消息持久化到磁盤。大家很容易想到就是刷盤機制。
刷盤機制分同步刷盤和異步刷盤:
Broker一般是集群部署的,有master主節(jié)點和slave從節(jié)點。消息到Broker存儲端,只有主節(jié)點和從節(jié)點都寫入成功,才反饋成功的ack給生產者。這就是同步復制,它保證了消息不丟失,但是降低了系統(tǒng)的吞吐量。與之對應的就是異步復制,只要消息寫入主節(jié)點成功,就返回成功的ack,它速度快,但是會有性能問題。
消費者執(zhí)行完業(yè)務邏輯,再反饋會Broker說消費成功,這樣才可以保證消費階段不丟消息。
消息的有序性,就是指可以按照消息的發(fā)送順序來消費。有些業(yè)務對消息的順序是有要求的,比如先下單再付款,最后再完成訂單,這樣等。假設生產者先后產生了兩條消息,分別是下單消息(M1),付款消息(M2),M1比M2先產生,如何保證M1比M2先被消費呢。
為了保證消息的順序性,可以將M1、M2發(fā)送到同一個Server上,當M1發(fā)送完收到ack后,M2再發(fā)送。如圖:
這樣還是可能會有問題,因為從MQ服務器到消費端,可能存在網絡延遲,雖然M1先發(fā)送,但是它比M2晚到。
那還能怎么辦才能保證消息的順序性呢?將M1和M2發(fā)往同一個消費者,且發(fā)送M1后,等到消費端ACK成功后,才發(fā)送M2就得了。
消息隊列保證順序性整體思路就是這樣啦。比如Kafka的全局有序消息,就是這種思想的體現(xiàn): 就是生產者發(fā)消息時,1個Topic
只能對應1個Partition
,一個 Consumer
,內部單線程消費。
但是這樣吞吐量太低,一般保證消息局部有序即可。在發(fā)消息的時候指定Partition Key
,Kafka對其進行Hash計算,根據(jù)計算結果決定放入哪個Partition
。這樣Partition Key相同的消息會放在同一個Partition。然后多消費者單線程消費指定的Partition。
消息隊列是可能發(fā)生重復消費的。
如何冪等處理重復消息呢?
我之前寫過一篇冪等設計的文章,大家有興趣可以看下哈:聊聊冪等設計
冪等處理重復消息,簡單來說,就是搞個本地表,帶唯一業(yè)務標記的,利用主鍵或者唯一性索引,每次處理業(yè)務,先校驗一下就好啦。又或者用redis緩存下業(yè)務標記,每次看下是否處理過了。
消息積壓是因為生產者的生產速度,大于消費者的消費速度。遇到消息積壓問題時,我們需要先排查,是不是有bug產生了。
如果不是bug,我們可以優(yōu)化一下消費的邏輯,比如之前是一條一條消息消費處理的話,我們可以確認是不是可以優(yōu)為批量處理消息。如果還是慢,我們可以考慮水平擴容,增加Topic的隊列數(shù),和消費組機器的數(shù)量,提升整體消費能力。
如果是bug導致幾百萬消息持續(xù)積壓幾小時。有如何處理呢?需要解決bug,臨時緊急擴容,大概思路如下:
先修復consumer消費者的問題,以確保其恢復消費速度,然后將現(xiàn)有consumer 都停掉。 新建一個 topic,partition 是原來的 10 倍,臨時建立好原先10倍的queue 數(shù)量。 然后寫一個臨時的分發(fā)數(shù)據(jù)的 consumer 程序,這個程序部署上去消費積壓的數(shù)據(jù),消費之后不做耗時的處理,直接均勻輪詢寫入臨時建立好的 10 倍數(shù)量的 queue。 接著臨時征用 10 倍的機器來部署 consumer,每一批 consumer 消費一個臨時 queue 的數(shù)據(jù)。這種做法相當于是臨時將 queue 資源和 consumer 資源擴大 10 倍,以正常的 10 倍速度來消費數(shù)據(jù)。 等快速消費完積壓數(shù)據(jù)之后,得恢復原先部署的架構,重新用原先的 consumer 機器來消費消息。
先可以對比下它們優(yōu)缺點:
Kafka | RocketMQ | RabbitMQ | |
---|---|---|---|
單機吞吐量 | 17.3w/s | 11.6w/s | 2.6w/s(消息做持久化) |
開發(fā)語言 | Scala/Java | Java | Erlang |
主要維護者 | Apache | Alibaba | Mozilla/Spring |
訂閱形式 | 基于topic,按照topic進行正則匹配的發(fā)布訂閱模式 | 基于topic/messageTag,按照消息類型、屬性進行正則匹配的發(fā)布訂閱模式 | 提供了4種:direct, topic ,Headers和fanout。fanout就是廣播模式 |
持久化 | 支持大量堆積 | 支持大量堆積 | 支持少量堆積 |
順序消息 | 支持 | 支持 | 不支持 |
集群方式 | 天然的Leader-Slave,無狀態(tài)集群,每臺服務器既是Master也是Slave | 常用 多對’Master-Slave’ 模式,開源版本需手動切換Slave變成Master | 支持簡單集群,'復制’模式,對高級集群模式支持不好。 |
性能穩(wěn)定性 | 較差 | 一般 | 好 |
消息中間件如何保證高可用呢?單機是沒有高可用可言的,高可用都是對集群來說的,一起看下kafka的高可用吧。
Kafka 的基礎集群架構,由多個broker
組成,每個broker
都是一個節(jié)點。當你創(chuàng)建一個topic
時,它可以劃分為多個partition
,而每個partition
放一部分數(shù)據(jù),分別存在于不同的 broker 上。也就是說,一個 topic 的數(shù)據(jù),是分散放在多個機器上的,每個機器就放一部分數(shù)據(jù)。
有些伙伴可能有疑問,每個partition
放一部分數(shù)據(jù),如果對應的broker掛了,那這部分數(shù)據(jù)是不是就丟失了?那還談什么高可用呢?
Kafka 0.8 之后,提供了復制品副本機制來保證高可用,即每個 partition 的數(shù)據(jù)都會同步到其它機器上,形成多個副本。然后所有的副本會選舉一個 leader 出來,讓leader去跟生產和消費者打交道,其他副本都是follower。寫數(shù)據(jù)時,leader 負責把數(shù)據(jù)同步給所有的follower,讀消息時, 直接讀 leader 上的數(shù)據(jù)即可。如何保證高可用的?就是假設某個 broker 宕機,這個broker上的partition 在其他機器上都有副本的。如果掛的是leader的broker呢?其他follower會重新選一個leader出來。
一條普通的MQ消息,從產生到被消費,大概流程如下:
我們舉個下訂單的例子吧。訂單系統(tǒng)創(chuàng)建完訂單后,再發(fā)送消息給下游系統(tǒng)。如果訂單創(chuàng)建成功,然后消息沒有成功發(fā)送出去,下游系統(tǒng)就無法感知這個事情,出導致數(shù)據(jù)不一致。
如何保證數(shù)據(jù)一致性呢?可以使用事務消息。一起來看下事務消息是如何實現(xiàn)的吧。
這個問題面試官主要考察三個方面的知識點:
遇到這種設計題,大部分人會很蒙圈,因為平時沒有思考過類似的問題。大多數(shù)人平時埋頭增刪改啥,不去思考框架背后的一些原理。有很多類似的問題,比如讓你來設計一個 Dubbo 框架,或者讓你來設計一個MyBatis 框架,你會怎么思考呢?
回答這類問題,并不要求你研究過那技術的源碼,你知道那個技術框架的基本結構、工作原理即可。設計一個消息隊列,我們可以從這幾個角度去思考:
阿里RocketMQ如何解決消息的順序&重復兩大硬傷?: https://dbaplus.cn/news-21-1123-1.html
[2]消息中間件面試題:如何解決消息隊列的延時以及過期失效問題?: https://jsbintask.cn/2019/01/28/interview/interview-middleware-manymessage/
[3]消息隊列設計精要: https://zhuanlan.zhihu.com/p/21649950
[4]MQ消息最終一致性解決方案: https://juejin.cn/post/6844903951448408071
微信8.0將好友放開到了一萬,小伙伴可以加我大號了,先到先得,再滿就真沒了