消息隊列面試題
題目來自於中華石杉,解決方案根據本身的思路來總結而得。
題目主要以下:
java
1. 爲何要引入消息隊列?
消息隊列的引入能夠解決3個核心問題:面試
- 解耦
在一個項目中,若是一個模塊A產生的一個關鍵數據,須要調用其餘模塊接口服務;而須要調用的接口不少,又不肯定以後是否還須要將數據傳給其餘模塊的接口時。這時可使用消息隊列,使用了消息隊列以後,模塊A不須要在對接各個模塊,而是直接對接消息隊列。這樣一來。當其餘的模塊須要這個數據時,也不用再修改A數據,而是去到MQ中訂閱這個Topic。使得模塊A與其餘模塊之間耦合度下降。
- 異步
在一個項目中,若是模塊A的請求處理須要20ms,而模塊A又依賴了模塊B,模塊C ,模塊D 。在A請求處理結束後,A須要調用
模塊B,C,D的對應請求處理,這時B請求處理須要100ms,C 須要200ms,D 須要400ms。這樣一來,整體一個請求的總時長爲
20 + 100 + 200 + 400 = 720 ms 遠遠大於模塊A的請求處理時間。另外一方面,模塊B 模塊C 模塊D之間 並無順序關係。
這時能夠引入消息隊列 ,模塊A在請求處理結束後,將本身的數據發送給消息隊列MQ,由B,C ,D去消息隊列獲取數據,自行處理
模塊A在處理完成後直接返回給客戶端處理結果,而不須要等待B,C,D處理結束,如此一來,一個請求處理的就只須要計算
模塊A的處理時間=20ms,大大提升了用戶體驗。
- 削峯
若是一個系統只能一秒鐘處理5000個請求(MySQL通常只能2000QPS),而在特殊時期就只要1個小時的時間段內,請求量暴漲,一秒鐘來了1W個請求。從而系統會之間宕機。但這種狀況可能在平時不會發生,不須要升級相應的服務器配置。
問題在於在1小時的時間段,如何把請求從10000QPS 降低到5000QPS 使得系統可以正常運轉而不發生宕機。
這時候能夠引入消息隊列,假設模塊A負責處理請求,模塊B負責將數據持久化到數據庫。模塊A在接受到請求時,把請求交給消息隊列,由消息隊列來緩解對應的請求壓力,相似於Buffer創建一個緩衝區,模塊B根據本身的請求處理速度去到消息隊列中去消費數據。這樣一來就解決了對應特定時間段的削峯問題。
- 要結合實際項目來講明:
體現上述的三個點。
2. MQ有什麼缺點?
任何技術都是一把雙刃劍,在引入消息隊列的同時一定也會伴隨着相應的問題,正如《人月神話》中所說,沒有銀彈。
消息隊列的引入會帶來3個核心的問題:spring
- 系統可靠性下降
在引入MQ時,MQ做爲了中間層,這就使得模塊與對應的MQ是緊耦合的關係。一旦MQ宕機,下游服務即便正常運行,但整個系統卻沒法使用。這個問題的解決方式是 MQ的高可用,使得MQ高可用,不會那麼容易宕機達到5個9。
-
系統複雜度提升
引入MQ,須要考慮的問題變複雜,隨之而來的問題是數據庫
- 消息丟了怎麼辦
- 重複消費消息問題
- 消息的順序問題
這幾個問題都有對應的解決方案。
- 處理結果的最終一致性問題
引入MQ會致使處理結果的最終一致性問題,由於模塊A與其餘模塊之間解耦,從而模塊A不知道其餘模塊的處理結果
這就致使模塊A覺得處理結果OK,但實際上可能模塊B處理結果失敗,這也是異步化所帶來的最終一致性問題。
3. 你都瞭解過哪些MQ? 他們之間有什麼區別嗎?
這個問題能夠延伸爲技術選型問題,關於這個問題能夠認爲憑什麼你選擇了這個MQ,而沒有選擇其餘MQ。對應能夠擴展爲spring-security 與shiro 都是安全框架都實現了Oauth2協議,並且shiro是輕量級的,爲何你選擇了Spring-Security這個安全框架這個問題比較考察技術廣度。segmentfault
首先我瞭解過的MQ有:activeMQ,RabbitMQ,RocketMQ,Kafka緩存
|
activeMQ |
RabbitMQ |
RocketMQ |
Kafka |
併發量 |
萬級 |
萬級 |
十萬級 |
萬級 |
處理時長 |
毫秒 |
微妙 |
毫秒 |
毫秒 |
開發語言 |
Java |
ErLang |
Java |
Java Scala |
功能完備 |
完備 |
完備 且提供了插件與管理界面 |
完備 |
完備 |
經常使用場景 |
小型項目demo |
* |
* |
大數據領域日誌處理、實時計算 |
社區活躍度 |
較低 |
高 |
高 |
高 |
activeMQ社區活躍度較低,不建議使用。
RabbitMQ社區活躍度高,更新版本頻繁,但使用開發語言爲ErLang , 當有定製化需求沒法進行擴展
RocketMQ 阿里出品,但存在着後續項目不更新狀況,這就使得企業自行維護相應的功能或者定製化功能
Kafka 大數據領域 主要用來進行實時計算,日誌採集的消息隊列,功能相比於其餘MQ少,可是kafka是大數據領域公認的消息隊列
若是對功能有要求,小公司能夠選擇RabbitMQ, 有技術團隊的大公司可使用RocketMQ,大數據生態爲了與其餘組件配合因此使用Kafka安全
4. 如何保證MQ的高可用
- RabbitMQ的HA
RabbitMQ的解決方式爲=>集羣模式 + 鏡像
普通集羣模式:
queue建立以後,若是沒有其它policy(策略),則queue就會按照普通模式集羣。對於Queue來講,消息實體只存在於其中一個節點,A、B兩個節點僅有相同的元數據,即隊列結構,但隊列的元數據僅保存有一份,即建立該隊列的rabbitmq節點(A節點),當A節點宕機,你能夠去其B節點查看,./rabbitmqctl list_queues發現該隊列已經丟失,但聲明的exchange還存在。
當消息進入A節點的Queue中後,consumer從B節點拉取時,RabbitMQ會臨時在A、B間進行消息傳輸,把A中的消息實體取出並通過B發送給consumer,因此consumer應平均鏈接每個節點,從中取消息。
該模式存在一個問題就是當A節點故障後,B節點沒法取到A節點中還未消費的消息實體。若是作了隊列持久化或消息持久化,那麼得等A節點恢復,而後纔可被消費,而且在A節點恢復以前其它節點不能再建立A節點已經建立過的持久隊列;若是沒有持久化的話,消息就會失丟。這種模式更適合非持久化隊列。
只有該隊列是非持久的,客戶端才能從新鏈接到集羣裏的其餘節點,並從新建立隊列。
假如該隊列是持久化的,那麼惟一辦法是將故障節點恢復起來。
鏡像集羣模式:
核心在於:鏡像集羣會同步消息
該模式解決了上述問題,其實質和普通模式不一樣之處在於,消息實體會主動在鏡像節點間同步,而不是在consumer取數據時臨時拉取。該模式帶來的反作用也很明顯,除了下降系統性能外,若是鏡像隊列數量過多,加之大量的消息進入,集羣內部的網絡帶寬將會被這種同步通信大大消耗掉。因此在對可靠性要求較高的場合中適用,
一個隊列想作成鏡像隊列,須要先設置policy,而後客戶端建立隊列的時候,rabbitmq集羣根據「隊列名稱」自動設置是普通集羣模式或鏡像隊列。但這不是分佈式存儲,而是多節點主備存儲。相比於Kafka的分佈式架構會多消耗資源。
- Kafka實現高可用
Kafka的高可用主要是經過Kafka經過把每一個Topic中的消息分紅多個Partition,每一個Partition作一個集羣。而每個Partition內部集羣經過選舉獲得一個leader,其餘是follower,leader複製消息的讀寫,並把消息複製到follower。Kafka在發送消息時,只有當每一個partition的follower複製到了消息才確認消息已經被存儲。
5. 如何保證消息不會丟失?
-
RabbitMQ方式
1.1 生產者方面服務器
- 開啓RabbitMQ的事務方式txSelect()。當生產者寫入消息失敗時,採起重試機制。但這個寫入是同步的。
- 使用confirm方式: 其中confirm也可使用三種方式:
- 普通confirm
- 批量confirm
- 異步confirm 設置監聽器
channel.confirmSelect();
//普通confirm
if (channel.waitForConfirms()) {
System.out.println("消息發送成功" );
}
//批量confirm 失敗會報錯
channel.waitForConfirmsOrDie();
//異步監聽confirm
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("未確認消息,標識:" + deliveryTag);
}
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println(String.format("已確認消息,標識:%d,多個消息:%b", deliveryTag, multiple));
}
});
1.2 RabbitMQ方面
RabbitMQ在內存緩存消息,當MQ宕機時,會發生消息丟失現象。這一點須要RabbitMQ將消息持久化到硬盤上。減少消息丟失的可能
1.3 消費者方面
RabbitMQ默認是autoAck=true,這樣就使得消費者在接受到消息時,立馬告知MQ我消費了數據,但尚未來得及處理。因此須要把autoAck=false,以後當消息處理完成以後手動提交。網絡
2 kafka方面架構
-
生產端
數據丟失發送在leader向follower同步消息的時候,leader宕機使得消息丟失。
解決方案是設置4個參數:
- replication.factor = n > 1 設置partition有多少個副本數
- kafka咋服務端設置mini.sync.replicas=1 即一個leader至少保證有一個follower存活
- producer在發送消息時,設置acks=all 設置全部消息必須所有寫入replication後返回成功。
- retries=Integer.MAX 把重試次數調製最大
- 消費端
在消費者端關閉自動確認消息,這樣須要手動確認 保證消息不會丟失。
6. 如何保證消息不會重複消費?
- 在消費者消費消息時,把對應的惟一值放入HashSet 或者Redis來避免同一條消息消費屢次
- 使用數據庫的惟一鍵約束來報錯處理
7. 如何保證消費者消費消息時消息的順序性?
消息隊列自己就是爲了把多個任務解耦並行化處理,若是要保證消息的順序消息,實際上就是取消並行處理,改爲串行處理。
- RabbitMQ
將一組消息放入同一個queue,這樣就只會有一個consumer來消費這個queue中的數據,在consumer內部採用隊列的方式來處理順序消息
- Kafka
生產者寫消息時,每次寫入一個topic,將partition對應key值修改同一key,這樣消息只會進入一個partition,也只有一個消費者在進行消費,以後也採用內存隊列的方式順序處理消息。
8. 消息隊列積壓太多,目前消費者處理太慢,如何改進?
目前的消費者難以在短期內處理這麼多條消息,考慮引入多個消費者,但引入這多個消費者只用於消費當前消息,並非長期使用。假定長期使用的消費者爲A、B、C 訂閱的Topic是Order。新申請3 * 10~20 = 30~60個機器 做爲新的消費者組GroupNew。 GroupNew 訂閱新的Topic - OrderFast,從這個topic中獲取消息並消費。將原有長期使用的消費者組修改代碼,不處理消息 直接將消息寫入到新的Topic - OrderFast
9. 消息隊列的機器磁盤快被消息寫滿了怎麼辦?
- 能夠採起上述方案,不一樣的是寫入到另一臺MQ中,而不是在本機的Topic
- 下一個消息直接丟棄不處理,到消息隊列機器恢復以後,將生產者與消費者之間經過代碼查詢出對應的缺失部分,再進行補償式操做。
10. 消息隊列消費消息太慢,消息過時失效怎麼辦?
- 生產環境設置消息不過時
- 若是已通過期失效,那麼須要查出過時失效的消息,從新進行補償式操做
本文由博客一文多發平臺
OpenWrite 發佈!