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