面試大殺器:為什么一定要用MQ中間件?
這篇文章我們會(huì)把消息中間件這塊高頻的面試問題來給大家說一下,也會(huì)涵蓋一些 MQ 中間件常見的技術(shù)問題。
假如說面試官看你簡(jiǎn)歷里寫了 MQ 中間件的使用經(jīng)驗(yàn),很可能會(huì)有如下的問題:
- 你們公司生產(chǎn)環(huán)境用的是什么消息中間件?
- 為什么要在系統(tǒng)架構(gòu)中引入消息中間件?
- 引入消息中間件之后會(huì)有什么好處以及壞處?
好,下面我們一個(gè)個(gè)的來分析!
你們公司生產(chǎn)環(huán)境用的是什么消息中間件?
這個(gè)首先你可以說下你們公司選用的是什么消息中間件,比如用的是 RabbitMQ,然后可以初步給一些你對(duì)不同 MQ 中間件技術(shù)的選型分析。
舉個(gè)例子:比如說 ActiveMQ 是老牌的消息中間件,國(guó)內(nèi)很多公司過去運(yùn)用的還是非常廣泛的,功能很強(qiáng)大。
但是問題在于沒法確認(rèn) ActiveMQ 可以支撐互聯(lián)網(wǎng)公司的高并發(fā)、高負(fù)載以及高吞吐的復(fù)雜場(chǎng)景,在國(guó)內(nèi)互聯(lián)網(wǎng)公司落地較少。而且使用較多的是一些傳統(tǒng)企業(yè),用 ActiveMQ 做異步調(diào)用和系統(tǒng)解耦。
然后你可以說說 RabbitMQ,他的好處在于可以支撐高并發(fā)、高吞吐、性能很高,同時(shí)有非常完善便捷的后臺(tái)管理界面可以使用。
另外,他還支持集群化、高可用部署架構(gòu)、消息高可靠支持,功能較為完善。
而且經(jīng)過調(diào)研,國(guó)內(nèi)各大互聯(lián)網(wǎng)公司落地大規(guī)模 RabbitMQ 集群支撐自身業(yè)務(wù)的 case 較多,國(guó)內(nèi)各種中小型互聯(lián)網(wǎng)公司使用 RabbitMQ 的實(shí)踐也比較多。
除此之外,RabbitMQ 的開源社區(qū)很活躍,較高頻率的迭代版本,來修復(fù)發(fā)現(xiàn)的 Bug 以及進(jìn)行各種優(yōu)化,因此綜合考慮過后,公司采取了 RabbitMQ。
但是 RabbitMQ 也有一點(diǎn)缺陷,就是他自身是基于 Erlang 語(yǔ)言開發(fā)的,所以導(dǎo)致較為難以分析里面的源碼,也較難進(jìn)行深層次的源碼定制和改造,畢竟需要較為扎實(shí)的 Erlang 語(yǔ)言功底才可以。
然后可以聊聊 RocketMQ,是阿里開源的,經(jīng)過阿里的生產(chǎn)環(huán)境的超高并發(fā)、高吞吐的考驗(yàn),性能卓越,同時(shí)還支持分布式事務(wù)等特殊場(chǎng)景。
而且 RocketMQ 是基于 Java 語(yǔ)言開發(fā)的,適合深入閱讀源碼,有需要可以站在源碼層面解決線上生產(chǎn)問題,包括源碼的二次開發(fā)和改造。
另外就是 Kafka。Kafka 提供的消息中間件的功能明顯較少一些,相對(duì)上述幾款 MQ 中間件要少很多。
但是 Kafka 的優(yōu)勢(shì)在于專為超高吞吐量的實(shí)時(shí)日志采集、實(shí)時(shí)數(shù)據(jù)同步、實(shí)時(shí)數(shù)據(jù)計(jì)算等場(chǎng)景來設(shè)計(jì)。
因此 Kafka 在大數(shù)據(jù)領(lǐng)域中配合實(shí)時(shí)計(jì)算技術(shù)(比如 Spark Streaming、Storm、Flink)使用的較多。但是在傳統(tǒng)的 MQ 中間件使用場(chǎng)景中較少采用。
PS:如果大家對(duì)上述一些 MQ 技術(shù)還沒在自己電腦部署過,沒寫幾個(gè) helloworld 體驗(yàn)一下的話,建議先上各個(gè)技術(shù)的官網(wǎng)找到 helloworld demo,自己跑一遍玩玩。
為什么在你們系統(tǒng)架構(gòu)中要引入消息中間件?
回答這個(gè)問題,其實(shí)就是讓你先說說消息中間件的常見使用場(chǎng)景。然后結(jié)合你們自身系統(tǒng)對(duì)應(yīng)的使用場(chǎng)景,說一下在你們系統(tǒng)中引入消息中間件解決了什么問題。
系統(tǒng)解耦
假設(shè)你有個(gè)系統(tǒng) A,這個(gè)系統(tǒng) A 會(huì)產(chǎn)出一個(gè)核心數(shù)據(jù),現(xiàn)在下游有系統(tǒng) B 和系統(tǒng) C 需要這個(gè)數(shù)據(jù)。那簡(jiǎn)單,系統(tǒng) A 就是直接調(diào)用系統(tǒng) B 和系統(tǒng) C 的接口發(fā)送數(shù)據(jù)給他們就好了。
整個(gè)過程,如下圖所示:
但是現(xiàn)在要是來了系統(tǒng) D、系統(tǒng) E、系統(tǒng) F、系統(tǒng) G,等等,十來個(gè)其他系統(tǒng)慢慢的都需要這份核心數(shù)據(jù)呢?如下圖所示:
大家可別以為這是開玩笑,一個(gè)大規(guī)模系統(tǒng),往往會(huì)拆分為幾十個(gè)甚至上百個(gè)子系統(tǒng),每個(gè)子系統(tǒng)又對(duì)應(yīng) N 多個(gè)服務(wù),這些系統(tǒng)與系統(tǒng)之間有著錯(cuò)綜復(fù)雜的關(guān)系網(wǎng)絡(luò)。
如果某個(gè)系統(tǒng)產(chǎn)出一份核心數(shù)據(jù),可能下游無數(shù)的其他系統(tǒng)都需要這份數(shù)據(jù)來實(shí)現(xiàn)各種業(yè)務(wù)邏輯。
此時(shí)如果你要是采取上面那種模式來設(shè)計(jì)系統(tǒng)架構(gòu),那么絕對(duì)你負(fù)責(zé)系統(tǒng) A 的同學(xué)要被煩死了。
先是來一個(gè)人找他要求發(fā)送數(shù)據(jù)給一個(gè)新的系統(tǒng) H,系統(tǒng) A 的同學(xué)要修改代碼,然后在那個(gè)代碼里加入調(diào)用新系統(tǒng) H 的流程。
一會(huì)那個(gè)系統(tǒng) B 是個(gè)陳舊老系統(tǒng)要下線了,告訴系統(tǒng) A 的同學(xué):別給我發(fā)送數(shù)據(jù)了,接著系統(tǒng) A 再次修改代碼不再給這個(gè)系統(tǒng) B。
然后如果要是某個(gè)下游系統(tǒng)突然宕機(jī)了呢?系統(tǒng) A 的調(diào)用代碼里是不是會(huì)拋異常?
那系統(tǒng)A的同學(xué)會(huì)收到報(bào)警說異常了,結(jié)果他還要去 care 是下游哪個(gè)系統(tǒng)宕機(jī)了。
所以在實(shí)際的系統(tǒng)架構(gòu)設(shè)計(jì)中,如果全部采取這種系統(tǒng)耦合的方式,在某些場(chǎng)景下絕對(duì)是不合適的,系統(tǒng)耦合度太嚴(yán)重。
并且互相耦合起來并不是核心鏈路的調(diào)用,而是一些非核心的場(chǎng)景(比如上述的數(shù)據(jù)消費(fèi))導(dǎo)致了系統(tǒng)耦合,這樣會(huì)嚴(yán)重的影響上下游系統(tǒng)的開發(fā)和維護(hù)效率。
因此在上述系統(tǒng)架構(gòu)中,就可以采用 MQ 中間件來實(shí)現(xiàn)系統(tǒng)解耦。系統(tǒng) A 就把自己的一份核心數(shù)據(jù)發(fā)到 MQ 里,下游哪個(gè)系統(tǒng)感興趣自己去消費(fèi)即可,不需要了就取消數(shù)據(jù)的消費(fèi),如下圖所示:
異步調(diào)用
假設(shè)你有一個(gè)系統(tǒng)調(diào)用鏈路,是系統(tǒng) A 調(diào)用系統(tǒng) B,一般耗時(shí) 20ms;系統(tǒng) B 調(diào)用系統(tǒng) C,一般耗時(shí) 200ms;系統(tǒng) C 調(diào)用系統(tǒng) D,一般耗時(shí) 2s,如下圖所示:
現(xiàn)在***的問題就是:用戶一個(gè)請(qǐng)求過來巨慢無比,因?yàn)樽咄暌粋€(gè)鏈路,需要耗費(fèi) 20ms + 200ms + 2000ms(2s) = 2220ms,也就是 2 秒多的時(shí)間。
但是實(shí)際上,鏈路中的系統(tǒng) A 調(diào)用系統(tǒng) B,系統(tǒng) B 調(diào)用系統(tǒng) C,這兩個(gè)步驟起來也就 220ms。
就因?yàn)橐肓讼到y(tǒng) C 調(diào)用系統(tǒng) D 這個(gè)步驟,導(dǎo)致最終鏈路執(zhí)行時(shí)間是 2 秒多,直接將鏈路調(diào)用性能降低了 10 倍,這就是導(dǎo)致鏈路執(zhí)行過慢的罪魁禍?zhǔn)住?/p>
那此時(shí)我們可以思考一下,是不是可以將系統(tǒng) D 從鏈路中抽離出去做成異步調(diào)用呢?其實(shí)很多的業(yè)務(wù)場(chǎng)景是可以允許異步調(diào)用的。
舉個(gè)例子,你平時(shí)點(diǎn)個(gè)外賣,咔嚓一下子下訂單然后付款了,此時(shí)賬戶扣款、創(chuàng)建訂單、通知商家給你準(zhǔn)備菜品。
接著,是不是需要找個(gè)騎手給你送餐?那這個(gè)找騎手的過程,是需要一套復(fù)雜算法來實(shí)現(xiàn)調(diào)度的,比較耗時(shí)。
但是其實(shí)稍微晚個(gè)幾十秒完成騎手的調(diào)度都是 ok 的,因?yàn)閷?shí)際并不需要在你支付的一瞬間立馬給你找好騎手,也沒那個(gè)必要。
那么我們是不是就可以把找騎手給你送餐的這個(gè)步驟從鏈路中抽離出去,做成異步化的,哪怕延遲個(gè)幾十秒,但是只要在一定時(shí)間范圍內(nèi)給你找到一個(gè)騎手去送餐就可以了。
這樣是不是就可以讓你下訂單點(diǎn)外賣的速度變得超快?支付成功之后,直接創(chuàng)建好訂單、賬戶扣款、通知商家立馬給你準(zhǔn)備做菜就 ok 了,這個(gè)過程可能就幾百毫秒。
然后后臺(tái)異步化的耗費(fèi)可能幾十秒通過調(diào)度算法給你找到一個(gè)騎手去送餐,但是這個(gè)步驟不影響我們快速下訂單。
當(dāng)然我們不是說那些大家熟悉的外賣平臺(tái)的技術(shù)架構(gòu)就一定是這么實(shí)現(xiàn)的,只不過是用一個(gè)生活中常見的例子給大家舉例說明而已。
所以上面的鏈路也是同理,如果業(yè)務(wù)流程支持異步化的話,是不是就可以考慮把系統(tǒng) C 對(duì)系統(tǒng) D 的調(diào)用抽離出去做成異步化的,不要放在鏈路中同步依次調(diào)用。
這樣,實(shí)現(xiàn)思路就是系統(tǒng) A→系統(tǒng) B→系統(tǒng) C,直接就耗費(fèi) 220ms 后直接成功了。
然后系統(tǒng) C 就是發(fā)送個(gè)消息到 MQ 中間件里,由系統(tǒng) D 消費(fèi)到消息之后慢慢的異步來執(zhí)行這個(gè)耗時(shí) 2s 的業(yè)務(wù)處理。通過這種方式直接將核心鏈路的執(zhí)行性能提升了 10 倍。
整個(gè)過程,如下圖所示:
流量削峰
假設(shè)你有一個(gè)系統(tǒng),平時(shí)正常的時(shí)候每秒可能就幾百個(gè)請(qǐng)求,系統(tǒng)部署在 8 核 16G 的機(jī)器的上,正常處理都是 ok 的,每秒幾百請(qǐng)求是可以輕松抗住的。
但是如下圖所示,在高峰期一下子來了每秒鐘幾千請(qǐng)求,瞬時(shí)出現(xiàn)了流量高峰,此時(shí)你的選擇是要搞 10 臺(tái)機(jī)器,抗住每秒幾千請(qǐng)求的瞬時(shí)高峰嗎?
那如果瞬時(shí)高峰每天就那么半個(gè)小時(shí),接著直接就降低為了每秒就幾百請(qǐng)求,如果你線上部署了很多臺(tái)機(jī)器,那么每臺(tái)機(jī)器就處理每秒幾十個(gè)請(qǐng)求就可以了,這不是有點(diǎn)浪費(fèi)機(jī)器資源嗎?
大部分時(shí)候,每秒幾百請(qǐng)求,一臺(tái)機(jī)器就足夠了,但是為了抗那每天瞬時(shí)的高峰,硬是部署了 10 臺(tái)機(jī)器,每天就那半個(gè)小時(shí)有用,別的時(shí)候都是浪費(fèi)資源的。
但是如果你就部署一臺(tái)機(jī)器,那會(huì)導(dǎo)致瞬時(shí)高峰時(shí),一下子壓垮你的系統(tǒng),因?yàn)榻^對(duì)無法抗住每秒幾千的請(qǐng)求高峰。
此時(shí)我們就可以用 MQ 中間件來進(jìn)行流量削峰。所有機(jī)器前面部署一層 MQ,平時(shí)每秒幾百請(qǐng)求大家都可以輕松接收消息。
一旦到了瞬時(shí)高峰期,一下涌入每秒幾千的請(qǐng)求,就可以積壓在 MQ 里面,然后那一臺(tái)機(jī)器慢慢的處理和消費(fèi)。
等高峰期過了,再消費(fèi)一段時(shí)間,MQ 里積壓的數(shù)據(jù)就消費(fèi)完畢了。
這個(gè)就是很典型的一個(gè) MQ 的用法,用有限的機(jī)器資源承載高并發(fā)請(qǐng)求。
如果業(yè)務(wù)場(chǎng)景允許異步削峰,高峰期積壓一些請(qǐng)求在 MQ 里,然后高峰期過了,后臺(tái)系統(tǒng)在一定時(shí)間內(nèi)消費(fèi)完畢不再積壓的話,那就很適合用這種技術(shù)方案。
引入消息中間件之后會(huì)有什么好處以及壞處?
如果你在系統(tǒng)架構(gòu)里引入了消息中間件之后,會(huì)有哪些缺點(diǎn)?
系統(tǒng)可用性降低
首先是你的系統(tǒng)整體可用性絕對(duì)會(huì)降低,給你舉個(gè)例子,我們就拿之前的一幅圖來說明。
比如說一個(gè)核心鏈路里面,系統(tǒng) A→系統(tǒng) B→系統(tǒng) C,然后系統(tǒng) C 是通過 MQ 異步調(diào)用系統(tǒng) D 的。
看起來很好,你用這個(gè) MQ 異步化的手段解決了一個(gè)核心鏈路執(zhí)行性能過差的問題。
但是你有沒有考慮另外一個(gè)問題,就是萬(wàn)一你依賴的那個(gè) MQ 中間件突然掛掉了怎么辦?
這個(gè)還真的不是異想天開,MQ、Redis、MySQL 這些組件都有可能會(huì)掛掉。一旦你的 MQ 掛了,就導(dǎo)致你的系統(tǒng)的核心業(yè)務(wù)流程中斷了。
本來你要是不引入 MQ 中間件,那其實(shí)就是一些系統(tǒng)之間的調(diào)用,但是現(xiàn)在你引入了 MQ,就導(dǎo)致你多了一個(gè)依賴。一旦多了一個(gè)依賴,就會(huì)導(dǎo)致你的可用性降低。
因此,一旦引入了 MQ 中間件,你就必須去考慮這個(gè) MQ 是如何部署的,如何保證高可用性。
甚至在復(fù)雜的高可用的場(chǎng)景下,你還要考慮如果 MQ 一旦掛了以后,你的系統(tǒng)有沒有備用兜底的技術(shù)方案,可以保證系統(tǒng)繼續(xù)運(yùn)行下去。
系統(tǒng)穩(wěn)定性降低
還是上面那張圖,大家再來看一下:
不知道大家有沒有發(fā)現(xiàn)一個(gè)問題,這個(gè)鏈路除了 MQ 中間件掛掉這個(gè)可能存在的隱患之外,可能還有一些其他的技術(shù)問題。
比如說,莫名其妙的,系統(tǒng) C 發(fā)了一個(gè)消息到 MQ,結(jié)果那個(gè)消息因?yàn)榫W(wǎng)絡(luò)故障等問題,就丟失了。這就導(dǎo)致系統(tǒng) D 沒有收到那條消息。
這可就慘了,這樣會(huì)導(dǎo)致系統(tǒng) D 沒完成自己該做的任務(wù),此時(shí)可能整個(gè)系統(tǒng)會(huì)出現(xiàn)業(yè)務(wù)錯(cuò)亂,數(shù)據(jù)丟失,嚴(yán)重的 Bug,用戶體驗(yàn)很差等各種問題。
這還只是其中之一,萬(wàn)一說系統(tǒng) C 給 MQ 發(fā)送消息,不小心一抽風(fēng)重復(fù)發(fā)了一條一模一樣的,導(dǎo)致消息重復(fù)了,這個(gè)時(shí)候該怎么辦?
可能會(huì)導(dǎo)致系統(tǒng) D 一下子把一條數(shù)據(jù)插入了兩次,導(dǎo)致數(shù)據(jù)錯(cuò)誤,臟數(shù)據(jù)的產(chǎn)生,***一樣會(huì)導(dǎo)致各種問題。
或者說如果系統(tǒng) D 突然宕機(jī)了幾個(gè)小時(shí),導(dǎo)致無法消費(fèi)消息,結(jié)果大量的消息在 MQ 中間件里積壓了很久,這個(gè)時(shí)候怎么辦?
即使系統(tǒng) D 恢復(fù)了,也需要慢慢的消費(fèi)數(shù)據(jù)來進(jìn)行處理。所以這就是引入 MQ 中間件的第二個(gè)大問題,系統(tǒng)穩(wěn)定性可能會(huì)下降,故障會(huì)增多,各種各樣亂七八糟的問題都可能產(chǎn)生。
而且一旦產(chǎn)生了一個(gè)問題,就會(huì)導(dǎo)致系統(tǒng)整體出問題。你就需要為了解決各種 MQ 引發(fā)的技術(shù)問題,采取很多的技術(shù)方案。
關(guān)于這個(gè),我們后面會(huì)用專門的文章聊聊 MQ 中間件的這些問題的解決方案,包括:
- 消息高可靠傳遞(0 丟失)
- 消息冪等性傳遞(絕對(duì)不重復(fù))
- 百萬(wàn)消息積壓的線上故障處理
分布式一致性問題
引入消息中間件,還有分布式一致性的問題。舉個(gè)例子,比如說系統(tǒng) C 現(xiàn)在處理自己本地?cái)?shù)據(jù)庫(kù)成功了,然后發(fā)送了一個(gè)消息給 MQ,系統(tǒng) D 也確實(shí)是消費(fèi)到了。
但是結(jié)果不幸的是,系統(tǒng) D 操作自己本地?cái)?shù)據(jù)庫(kù)失敗了,那這個(gè)時(shí)候咋辦?
系統(tǒng) C 成功了,系統(tǒng) D 失敗了,會(huì)導(dǎo)致系統(tǒng)整體數(shù)據(jù)不一致了啊。所以此時(shí)又需要使用可靠消息最終一致性的分布式事務(wù)方案來保障。
關(guān)于這個(gè),可以參考之前的一篇文章:最終一致性分布式事務(wù)如何保障實(shí)際生產(chǎn)中 99.99% 高可用?
我們?cè)诶锩嬖敿?xì)闡述了系統(tǒng)之間異步調(diào)用場(chǎng)景下,如何采用分布式事務(wù)方案保證其數(shù)據(jù)一致性。
總結(jié)
最后,我們來做一個(gè)簡(jiǎn)單的小結(jié)。在面試中要答好這個(gè)問題,首先一定要熟悉 MQ 這個(gè)技術(shù)的優(yōu)缺點(diǎn)。了解清楚把他引入系統(tǒng)是為了解決哪些問題的,但是他自身又會(huì)帶來哪些問題。
此外,對(duì)于引入 MQ 以后,是否對(duì)他自身可能引發(fā)的問題有一些方案的設(shè)計(jì),來保證你的系統(tǒng)高可用、高可靠的運(yùn)行,保證數(shù)據(jù)的一致性。這個(gè)也要做好相應(yīng)的準(zhǔn)備。
中華石杉:十余年 BAT 架構(gòu)經(jīng)驗(yàn),一線互聯(lián)網(wǎng)公司技術(shù)總監(jiān)。帶領(lǐng)上百人團(tuán)隊(duì)開發(fā)過多個(gè)億級(jí)流量高并發(fā)系統(tǒng)。現(xiàn)將多年工作中積累下的研究手稿、經(jīng)驗(yàn)總結(jié)整理成文,傾囊相授。微信公眾號(hào):石杉的架構(gòu)筆記(ID:shishan100)。