「面試題」消息隊列最全解析,說人話的面試解析

「面試題」消息隊列最全解析,說人話的面試解析

其實面試官主要是想看看: 為什么使用消息隊列 其實就是問問你消息隊列都有哪些使用場景,然后你項目里具體是什么場景,說說你在這個場景里用消息隊列是什么? 面試官問你這個問題, 期望的一個回答 是說,你們公司有個什么 業(yè)務(wù)場景 ,這個業(yè)務(wù)場景有個什么技術(shù)挑戰(zhàn),如果不用 MQ 可能會很麻煩,但是你現(xiàn)在用了 MQ 之后帶給了你很多的好處。 先說一下消息隊列常見的使用場景吧,其實場景有很多,但是比較核心的有 3 個: 解耦 、 異步 、 削峰 。

看這么個場景。

A 系統(tǒng)發(fā)送數(shù)據(jù)到 BCD 三個系統(tǒng),通過接口調(diào)用發(fā)送。如果 E 系統(tǒng)也要這個數(shù)據(jù)呢?那如果 C 系統(tǒng)現(xiàn)在不需要了呢?A 系統(tǒng)負(fù)責(zé)人幾乎崩潰…… 在這個場景中,A 系統(tǒng)跟其它各種亂七八糟的系統(tǒng)嚴(yán)重耦合,A 系統(tǒng)產(chǎn)生一條比較關(guān)鍵的數(shù)據(jù),很多系統(tǒng)都需要 A 系統(tǒng)將這個數(shù)據(jù)發(fā)送過來。A 系統(tǒng)要時時刻刻考慮 BCDE 四個系統(tǒng)如果掛了該咋辦?要不要重發(fā),要不要把消息存起來?頭發(fā)都白了??! 如果使用 MQ,A 系統(tǒng)產(chǎn)生一條數(shù)據(jù),發(fā)送到 MQ 里面去,哪個系統(tǒng)需要數(shù)據(jù)自己去 MQ 里面消費。如果新系統(tǒng)需要數(shù)據(jù),直接從 MQ 里消費即可;如果某個系統(tǒng)不需要這條數(shù)據(jù)了,就取消對 MQ 消息的消費即可。

這樣下來,A 系統(tǒng)壓根兒不需要去考慮要給誰發(fā)送數(shù)據(jù),不需要維護(hù)這個代碼,也不需要考慮人家是否調(diào)用成功、失敗超時等情況。 面試技巧 :你需要去考慮一下你負(fù)責(zé)的系統(tǒng)中是否有類似的場景,就是一個系統(tǒng)或者一個模塊,調(diào)用了多個系統(tǒng)或者模塊,互相之間的調(diào)用很復(fù)雜,維護(hù)起來很麻煩。但是其實這個調(diào)用是不需要直接同步調(diào)用接口的,如果用 MQ 給它異步化解耦,也是可以的,你就需要去考慮在你的項目里,是不是可以運用這個 MQ 去進(jìn)行系統(tǒng)的解耦。

在簡歷中體現(xiàn)出來這塊東西,用 MQ 作解耦。 再來看一個場景,A 系統(tǒng)接收一個請求,需要在自己本地寫庫,還需要在 BCD 三個系統(tǒng)寫庫,自己本地寫庫要 3ms,BCD 三個系統(tǒng)分別寫庫要 300ms、450ms、200ms。最終請求總延時是 3 + 300 + 450 + 200 = 953ms,接近 1s,用戶感覺搞個什么東西,慢*了慢*了。

用戶通過瀏覽器發(fā)起請求,等待個 1s,這幾乎是不可接受的。 一般互聯(lián)網(wǎng)類的企業(yè),對于用戶直接的操作,一般要求是每個請求都必須在 200 ms 以內(nèi)完成,對用戶幾乎是無感知的。 如果 使用 MQ ,那么 A 系統(tǒng)連續(xù)發(fā)送 3 條消息到 MQ 隊列中,假如耗時 5ms,A 系統(tǒng)從接受一個請求到返回響應(yīng)給用戶,總時長是 3 + 5 = 8ms,對于用戶而言,其實感覺上就是點個按鈕,8ms 以后就直接返回了,爽!網(wǎng)站做得真好,真快! 每天 0:00 到 12:00,A 系統(tǒng)風(fēng)平浪靜,每秒并發(fā)請求數(shù)量就 50 個。

結(jié)果每次一到 12:00 ~ 13:00 ,每秒并發(fā)請求數(shù)量突然會暴增到 5k+ 條。但是系統(tǒng)是直接基于 MySQL 的,大量的請求涌入 MySQL,每秒鐘對 MySQL 執(zhí)行約 5k 條 SQL。 一般的 MySQL,扛到每秒 2k 個請求就差不多了,如果每秒請求到 5k 的話,可能就直接把 MySQL 給打*了,導(dǎo)致系統(tǒng)崩潰,用戶也就沒法再使用系統(tǒng)了。 但是高峰期一過,到了下午的時候,就成了低峰期,可能也就 1w 的用戶同時在網(wǎng)站上操作,每秒中的請求數(shù)量可能也就 50 個請求,對整個系統(tǒng)幾乎沒有任何的壓力。

如果使用 MQ,每秒 5k 個請求寫入 MQ,A 系統(tǒng)每秒鐘最多處理 2k 個請求,因為 MySQL 每秒鐘最多處理 2k 個。A 系統(tǒng)從 MQ 中慢慢拉取請求,每秒鐘就拉取 2k 個請求,不要超過自己每秒能處理的**請求數(shù)量就 ok,這樣下來,哪怕是高峰期的時候,A 系統(tǒng)也**不會掛掉。而 MQ 每秒鐘 5k 個請求進(jìn)來,就 2k 個請求出去,結(jié)果就導(dǎo)致在中午高峰期(1 個小時),可能有幾十萬甚至幾百萬的請求積壓在 MQ 中。 這個短暫的高峰期積壓是 ok 的,因為高峰期過了之后,每秒鐘就 50 個請求進(jìn) MQ,但是 A 系統(tǒng)依然會按照每秒 2k 個請求的速度在處理。

所以說,只要高峰期一過,A 系統(tǒng)就會快速將積壓的消息給解決掉。 優(yōu)點上面已經(jīng)說了,就是 在特殊場景下有其對應(yīng)的好處 , 解耦 、 異步 、 削峰 。 缺點有以下幾個: 所以消息隊列實際是一種非常復(fù)雜的架構(gòu),你引入它有很多好處,但是也得針對它帶來的壞處做各種額外的技術(shù)方案和架構(gòu)來規(guī)避掉,做好之后,你會發(fā)現(xiàn),媽呀,系統(tǒng)復(fù)雜度提升了一個數(shù)量級,也許是復(fù)雜了 10 倍。但是關(guān)鍵時刻,用,還是得用的。

綜上,各種對比之后,有如下建議: 一般的業(yè)務(wù)系統(tǒng)要引入 MQ,最早大家都用 ActiveMQ,但是現(xiàn)在確實大家用的不多了,沒經(jīng)過大規(guī)模吞吐量場景的驗證,社區(qū)也不是很活躍,所以大家還是算了吧,我個人不推薦用這個了; 后來大家開始用 RabbitMQ,但是確實 erlang 語言阻止了大量的 Java 工程師去深入研究和掌控它,對公司而言,幾乎處于不可控的狀態(tài),但是確實人家是開源的,比較穩(wěn)定的支持,活躍度也高; 不過現(xiàn)在確實越來越多的公司會去用 RocketMQ,確實很不錯,畢竟是阿里出品,但社區(qū)可能有突然黃掉的風(fēng)險(目前 RocketMQ 已捐給 Apache,但 GitHub 上的活躍度其實不算高)對自己公司技術(shù)實力有**自信的,推薦用 RocketMQ,否則回去老老實實用 RabbitMQ 吧,人家有活躍的開源社區(qū),**不會黃。 所以 中小型公司 ,技術(shù)實力較為一般,技術(shù)挑戰(zhàn)不是特別高,用 RabbitMQ 是不錯的選擇; 大型公司 ,基礎(chǔ)架構(gòu)研發(fā)實力較強,用 RocketMQ 是很好的選擇。 如果是 大數(shù)據(jù)領(lǐng)域 的實時計算、日志采集等場景,用 Kafka 是業(yè)內(nèi)標(biāo)準(zhǔn)的,**沒問題,社區(qū)活躍度很高,**不會黃,何況幾乎是全世界這個領(lǐng)域的事實性規(guī)范。

使用MQ的好處,為什么使用MQ

舉個例子,如果訂單系統(tǒng)最多能處理一萬次訂單,這個處理能力應(yīng)付正常時段的下單時綽綽有余,正常時段我們下單一秒后就能返回結(jié)果。但是在高峰期,如果有兩萬次下單操作系統(tǒng)是處理不了的,只能限制訂單超過一萬后不允許用戶下單。

使用消息隊列做緩沖,我們可以取消這個限制,把一秒內(nèi)下的訂單分散成一段時間來處理,這時有些用戶可能在下單十幾秒后才能收到下單成功的操作,但是比不能下單的體驗要好。

以電商應(yīng)用為例,應(yīng)用中有訂單系統(tǒng)、庫存系統(tǒng)、物流系統(tǒng)、支付系統(tǒng)。用戶創(chuàng)建訂單后,如果耦合調(diào)用庫存系統(tǒng)、物流系統(tǒng)、支付系統(tǒng),任何一個子系統(tǒng)出了故障,都會造成下單操作異常。當(dāng)轉(zhuǎn)變成基于消息隊列的方式后,系統(tǒng)間調(diào)用的問題會減少很多,比如物流系統(tǒng)因為發(fā)生故障,需要幾分鐘來修復(fù)。在這幾分鐘的時間里,物流系統(tǒng)要處理的內(nèi)存被緩存在消息隊列中,用戶的下單操作可以正常完成。

當(dāng)物流系統(tǒng)恢復(fù)后,繼續(xù)處理訂單信息即可,中單用戶感受不到物流系統(tǒng)的故障,提升系統(tǒng)的可用性。有些服務(wù)間調(diào)用是異步的,例如 A 調(diào)用 B,B 需要花費很長時間執(zhí)行,但是 A 需要知道 B 什么時候可以執(zhí)行完,以前一般有兩種方式,A 過一段時間去調(diào)用 B 的查詢 api 查詢?;蛘?A 提供一個 callback api,B 執(zhí)行完之后調(diào)用 api 通知 A 服務(wù)。

這兩種方式都不是很優(yōu)雅,使用消息總線,可以很方便解決這個問題,A 調(diào)用 B 服務(wù)后,只需要監(jiān)聽 B 處理完成的消息,當(dāng) B 處理完成后,會發(fā)送一條消息給 MQ,MQ 會將此消息轉(zhuǎn)發(fā)給 A 服務(wù)。這樣 A 服務(wù)既不用循環(huán)調(diào)用 B 的查詢 api,也不用提供 callback api。同樣 B 服務(wù)也不用做這些操作。

A 服務(wù)還能及時的得到異步處理成功的消息。

消息隊列為什么用redis實現(xiàn)

redis只是提供一個高性能的、原子操作的分布式隊列實現(xiàn)。具體的業(yè)務(wù)還是得需要你自己定制。

你的需求實際上是一個變形的生產(chǎn)者-消費者實現(xiàn)。

對于此類需求,主要是將請求和實際的處理過程解耦,一般都是采取異步的方式來通知請求方,這跟用不用redis其實沒有多大的關(guān)系。一般的實現(xiàn)方法是你需要將用戶的請求封裝成一個Task,然后將這個Task再push到redis隊列,然后后端的worker.php完全可以多進(jìn)程、多線程的并發(fā)處理Task并將處理結(jié)果回調(diào)給請求方。

消息隊列原理及選型

消息隊列(Message Queue)是一種進(jìn)程間通信或同一進(jìn)程的不同線程間的通信方式。 Broker(消息服務(wù)器) Broker的概念來自與Apache ActiveMQ,通俗的講就是MQ的服務(wù)器。

Producer(生產(chǎn)者) 業(yè)務(wù)的發(fā)起方,負(fù)責(zé)生產(chǎn)消息傳輸給broker Consumer(消費者) 業(yè)務(wù)的處理方,負(fù)責(zé)從broker獲取消息并進(jìn)行業(yè)務(wù)邏輯處理 Topic(主題) 發(fā)布訂閱模式下的消息統(tǒng)一匯集地,不同生產(chǎn)者向topic發(fā)送消息,由MQ服務(wù)器分發(fā)到不同的訂閱 者,實現(xiàn)消息的廣播 Queue(隊列) PTP模式下,特定生產(chǎn)者向特定queue發(fā)送消息,消費者訂閱特定的queue完成指定消息的接收。

Message(消息體) 根據(jù)不同通信協(xié)議定義的固定格式進(jìn)行編碼的數(shù)據(jù)包,來封裝業(yè)務(wù)數(shù)據(jù),實現(xiàn)消息的傳輸 點對點模型用于消息生產(chǎn)者和消息消費者之間點到點的通信。 點對點模式包含三個角色: 每個消息都被發(fā)送到一個特定的隊列,接收者從隊列中獲取消息。隊列保留著消息,可以放在內(nèi)存 中也可以持久化,直到他們被消費或超時。 特點: 發(fā)布訂閱模型包含三個角色: 多個發(fā)布者將消息發(fā)送到Topic,系統(tǒng)將這些消息傳遞給多個訂閱者。

特點: AMQP即Advanced Message Queuing Protocol,是應(yīng)用層協(xié)議的一個開放標(biāo)準(zhǔn),為面向消息的中間件設(shè)計。消息中間件主要用于組件之間的解耦,消息的發(fā)送者無需知道消息使用者的存在,反之亦然。AMQP 的主要特征是面向消息、隊列、路由(包括點對點和發(fā)布/訂閱)、可靠性、安全。

優(yōu)點:可靠、通用 MQTT(Message Queuing Telemetry Transport,消息隊列遙測傳輸)是IBM開發(fā)的一個即時通訊協(xié)議,有可能成為物聯(lián)網(wǎng)的重要組成部分。該協(xié)議支持所有平臺,幾乎可以把所有聯(lián)網(wǎng)物品和外部連接起來,被用來當(dāng)做百科傳感器和致動器(比如通過Twitter讓房屋聯(lián)網(wǎng))的通信協(xié)議。 優(yōu)點:格式簡潔、占用帶寬小、移動端通信、PUSH、嵌入式系統(tǒng) STOMP(Streaming Text Orientated Message Protocol)是流文本定向消息協(xié)議,是一種為MOM(Message Oriented Middleware,面向消息的中間件)設(shè)計的簡單文本協(xié)議。

STOMP提供一個可互操作的連接格式,允許客戶端與任意STOMP消息**(Broker)進(jìn)行交互。 優(yōu)點:命令模式(非topic\\queue模式) XMPP(可擴(kuò)展消息處理現(xiàn)場協(xié)議,Extensible Messaging and Presence Protocol)是基于可擴(kuò)展標(biāo)記語言(XML)的協(xié)議,多用于即時消息(IM)以及**現(xiàn)場探測。適用于服務(wù)器之間的準(zhǔn)即時操作。

核心是基于XML流傳輸,這個協(xié)議可能最終允許因特網(wǎng)用戶向因特網(wǎng)上的其他任何人發(fā)送即時消息,即使其操作系統(tǒng)和瀏覽器不同。 優(yōu)點:通用公開、兼容性強、可擴(kuò)展、安全性高,但XML編碼格式占用帶寬大 RabbitMQ 是實現(xiàn) AMQP(高級消息隊列協(xié)議)的消息中間件的一種,最初起源于金融系統(tǒng),用于在分布式系統(tǒng)中存儲轉(zhuǎn)發(fā)消息,在易用性、擴(kuò)展性、高可用性等方面表現(xiàn)不俗。 RabbitMQ 主要是為了實現(xiàn)系統(tǒng)之間的雙向解耦而實現(xiàn)的。當(dāng)生產(chǎn)者大量產(chǎn)生數(shù)據(jù)時,消費者無法快速消費,那么需要一個中間層。

保存這個數(shù)據(jù)。 RabbitMQ 是一個開源的 AMQP 實現(xiàn),服務(wù)器端用Erlang語言編寫,支持多種客戶端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP 等,支持 AJAX。用于在分布式系統(tǒng)中存儲轉(zhuǎn)發(fā)消息,在易用性、擴(kuò)展性、高可用性等方面表現(xiàn)不俗。 Channel(通道) 道是兩個管理器之間的一種單向點對點的的通信連接,如果需要雙向交流,可以建立一對通道。

Exchange(消息交換機(jī)) Exchange類似于數(shù)據(jù)通信**中的交換機(jī),提供消息路由策略。 RabbitMq中,producer不是通過信道直接將消息發(fā)送給queue,而是先發(fā)送給Exchange。一個Exchange可以和多個Queue進(jìn)行綁定,producer在傳遞消息的時候,會傳遞一個ROUTING_KEY,Exchange會根據(jù)這個ROUTING_KEY按照特定的路由算法,將消息路由給指定的queue。和Queue一樣,Exchange也可設(shè)置為持久化,臨時或者自動刪除。

Exchange有4種類型:direct(默認(rèn)),fanout, topic, 和headers。 不同類型的Exchange轉(zhuǎn)發(fā)消息的策略有所區(qū)別: Binding(綁定) 所謂綁定就是將一個特定的 Exchange 和一個特定的 Queue 綁定起來。Exchange 和Queue的綁定可以是多對多的關(guān)系。

Routing Key(路由關(guān)鍵字) exchange根據(jù)這個關(guān)鍵字進(jìn)行消息投遞。 vhost(虛擬主機(jī)) 在RabbitMq server上可以創(chuàng)建多個虛擬的message broker,又叫做virtual hosts (vhosts)。每一個vhost本質(zhì)上是一個mini-rabbitmq server,分別管理各自的exchange,和bindings。

vhost相當(dāng)于物理的server,可以為不同app提供邊界隔離,使得應(yīng)用安全的運行在不同的vhost實例上,相互之間不會干擾。producer和consumer連接rabbit server需要指定一個vhost。 假設(shè)P1和C1注冊了相同的Broker,Exchange和Queue。P1發(fā)送的消息最終會被C1消費。

基本的通信流程大概如下所示: Consumer收到消息時需要顯式的向rabbit broker發(fā)送basic。ack消息或者consumer訂閱消息時設(shè)置auto_ack參數(shù)為true。 在通信過程中,隊列對ACK的處理有以下幾種情況: 即消息的Ackownledge確認(rèn)機(jī)制,為了保證消息不丟失,消息隊列提供了消息Acknowledge機(jī)制,即ACK機(jī)制,當(dāng)Consumer確認(rèn)消息已經(jīng)被消費處理,發(fā)送一個ACK給消息隊列,此時消息隊列便可以刪除這個消息了。

如果Consumer宕機(jī)/關(guān)閉,沒有發(fā)送ACK,消息隊列將認(rèn)為這個消息沒有被處理,會將這個消息重新發(fā)送給其他的Consumer重新消費處理。 消息的收發(fā)處理支持事務(wù),例如:在任務(wù)中心場景中,一次處理可能涉及多個消息的接收、處理,這應(yīng)該處于同一個事務(wù)范圍內(nèi),如果一個消息處理失敗,事務(wù)回滾,消息重新回到隊列中。 消息的持久化,對于一些關(guān)鍵的核心業(yè)務(wù)來說是非常重要的,啟用消息持久化后,消息隊列宕機(jī)重啟后,消息可以從持久化存儲恢復(fù),消息不丟失,可以繼續(xù)消費處理。 fanout 模式 模式特點: direct 模式 任何發(fā)送到Direct Exchange的消息都會被轉(zhuǎn)發(fā)到routing_key中指定的Queue。

如果一個exchange 聲明為direct,并且bind中指定了routing_key,那么發(fā)送消息時需要同時指明該exchange和routing_key。 簡而言之就是:生產(chǎn)者生成消息發(fā)送給Exchange, Exchange根據(jù)Exchange類型和basic_publish中的routing_key進(jìn)行消息發(fā)送 消費者:訂閱Exchange并根據(jù)Exchange類型和binding key(bindings 中的routing key) ,如果生產(chǎn)者和訂閱者的routing_key相同,Exchange就會路由到那個隊列。 topic 模式 前面講到direct類型的Exchange路由規(guī)則是完全匹配binding key與routing key,但這種嚴(yán)格的匹配方式在很多情況下不能滿足實際業(yè)務(wù)需求。

topic類型的Exchange在匹配規(guī)則上進(jìn)行了擴(kuò)展,它與direct類型的Exchage相似,也是將消息路由到binding key與routing key相匹配的Queue中,但這里的匹配規(guī)則有些不同。 它約定: 以上圖中的配置為例,routingKey=”quick.orange.rabbit”的消息會同時路由到Q1與Q2,routingKey=”lazy.orange.fox”的消息會?。