面試官靈魂暴擊!消息不丟不重,消息隊列咋選?
兄弟們,今天咱們來聊聊面試的時候,面試官最愛問的一個問題,那就是消息隊列的選擇,而且還專門盯著 "消息不丟不重" 這倆核心指標使勁兒薅。咱設(shè)想這么個場景啊,你正襟危坐,面試官推了推眼鏡,似笑非笑地說:"小伙子,聽說你用過消息隊列,那你給我講講,要是項目里要求消息不丟不重,你咋選合適的 MQ 呢?" 這時候,要是你支支吾吾說不明白,那可就芭比 Q 了,說不定面試官心里都在想:"這孩子,怕是連消息隊列的門都沒摸著吧。"
所以咱今天就把這事兒掰扯明白,讓你在面試官面前,那就是妥妥的 "消息隊列小靈通",啥問題都能接得住。
一、消息不丟不重:為啥是 MQ 的核心命門?
咱先搞清楚,為啥消息不丟不重這么重要。咱舉個簡單的例子,就說電商場景吧,用戶下了個訂單,這訂單消息要是丟了,那商家可就不知道用戶買了啥,用戶等著收貨也等不到,這不得急眼?要是消息重復(fù)了,商家可能就會發(fā)兩次貨,這成本可就上去了。再比如金融場景,那每一筆交易消息都至關(guān)重要,丟了或者重復(fù)了,那可能就是實實在在的資金損失啊。所以說,消息不丟不重,那就是消息隊列的 "生命線",是咱選擇 MQ 時必須要過的 "生死關(guān)"。
(一)消息丟失:那些讓人頭疼的坑
消息丟失可能發(fā)生在三個地方:生產(chǎn)者端、消費者端、Broker 端。生產(chǎn)者端可能因為網(wǎng)絡(luò)問題,消息沒發(fā)送到 Broker 就沒了;消費者端可能因為處理消息的時候出錯,還沒處理完就認為消息已經(jīng)消費了;Broker 端可能因為存儲故障,比如磁盤壞了,消息還沒來得及持久化就沒了。
(二)消息重復(fù):那些讓人無奈的煩惱
消息重復(fù)也有幾個原因,生產(chǎn)者端可能因為重試機制,導(dǎo)致消息重復(fù)發(fā)送;Broker 端可能在復(fù)制消息的時候,因為網(wǎng)絡(luò)問題,導(dǎo)致消費者收到重復(fù)的消息;消費者端可能因為消費成功后,給 Broker 的確認消息沒發(fā)送成功,Broker 以為沒消費,又發(fā)了一次。
二、主流 MQ 大閱兵:各顯神通的防丟防重術(shù)
現(xiàn)在市面上主流的 MQ 有不少,像 RabbitMQ、Kafka、RocketMQ,還有 ActiveMQ、Pulsar 等等。咱今天就重點挑幾個大家常用的來嘮嘮,看看它們在防丟防重方面都有啥絕招。
(一)RabbitMQ:英倫紳士的嚴謹范兒
RabbitMQ 就像個英倫紳士,講究的就是一個嚴謹、規(guī)范。它在防丟防重方面,有不少實用的機制。
1. 生產(chǎn)者端:確認機制顯身手
RabbitMQ 提供了兩種確認機制:普通確認模式和批量確認模式。在普通確認模式下,生產(chǎn)者每發(fā)送一條消息,就會等待 Broker 的確認,確認收到了,才會發(fā)送下一條。要是沒收到確認,那就可能需要重試。不過這效率可能有點低,所以就有了批量確認模式,生產(chǎn)者發(fā)送一批消息,然后等待 Broker 對這一批的確認。這樣效率能提高一些,但要是其中有一條消息沒確認,可能就得把這一批都重試,有點 "一損俱損" 的意思。
還有一種是異步確認模式,生產(chǎn)者發(fā)送消息后,不需要阻塞等待確認,而是通過回調(diào)函數(shù)來處理確認和失敗的情況。這樣就不會阻塞生產(chǎn)者的發(fā)送流程,提高了效率。
2. 消費者端:手動確認保安全
RabbitMQ 默認是自動確認消息的,也就是消費者收到消息后,Broker 就認為消息已經(jīng)消費了。但這樣要是消費者處理消息的時候出錯了,消息就丟了。所以咱一般會開啟手動確認模式,消費者處理完消息后,手動發(fā)送一個確認信號給 Broker,Broker 才會把消息從隊列中移除。要是處理過程中出錯了,消費者可以發(fā)送拒絕信號,讓 Broker 把消息重新放回隊列,或者丟棄。
3. Broker 端:持久化存儲加鏡像隊列
RabbitMQ 的持久化包括隊列持久化和消息持久化。隊列持久化就是把隊列的元數(shù)據(jù)存儲到磁盤上,這樣 Broker 重啟后,隊列還在。消息持久化就是把消息存到磁盤上,而不是內(nèi)存里,這樣即使 Broker 宕機了,重啟后消息還能恢復(fù)。不過要注意,消息持久化到磁盤也不是立馬就存進去的,可能會先存在緩存里,然后批量寫入磁盤,所以還是有可能丟失一部分消息,這時候就需要結(jié)合事務(wù)機制或者確認機制來保證。
鏡像隊列則是把隊列的數(shù)據(jù)復(fù)制到多個 Broker 節(jié)點上,這樣即使一個 Broker 節(jié)點掛了,其他節(jié)點還有隊列的數(shù)據(jù),保證了高可用性,也間接防止了消息丟失。
4. 防重復(fù):去重靠業(yè)務(wù)
RabbitMQ 本身在防重復(fù)方面沒有特別強的機制,主要還是靠業(yè)務(wù)層來處理。比如給每條消息加一個唯一的 ID,消費者在處理消息前,先檢查這個 ID 是否已經(jīng)處理過了,如果處理過了,就跳過。
(二)Kafka:暴躁老哥的高效防丟術(shù)
Kafka 就像個暴躁老哥,追求的是高吞吐量、低延遲,但在防丟防重方面,也有自己的一套辦法,雖然不像 RabbitMQ 那么嚴謹,但勝在高效。
1. 生產(chǎn)者端:acks 參數(shù)定乾坤
Kafka 的生產(chǎn)者發(fā)送消息時,通過 acks 參數(shù)來控制消息的確認機制。acks=0 的時候,生產(chǎn)者發(fā)送完消息就不管了,也不等待 Broker 的確認,這時候消息很容易丟失,一般很少用。acks=1 的時候,只要 Leader 節(jié)點收到消息,就給生產(chǎn)者確認,這時候如果 Follower 節(jié)點還沒同步消息,Leader 節(jié)點掛了,消息就丟了。acks=-1(或者 all)的時候,需要所有的 In - Sync Replicas(ISR)中的節(jié)點都收到消息,才給生產(chǎn)者確認,這樣消息的可靠性最高,但吞吐量會有所下降。
還有生產(chǎn)者的重試機制,當(dāng)發(fā)送消息失敗時,生產(chǎn)者會自動重試,不過要注意重試可能會導(dǎo)致消息重復(fù),所以需要消費者端做好去重處理。
2. 消費者端:offset 管理是關(guān)鍵
Kafka 的消費者通過 offset 來記錄消息的消費位置。默認情況下,消費者會自動提交 offset,也就是在消費消息的同時,定期向 Broker 提交自己當(dāng)前的 offset。但這樣如果消費者在處理消息的時候掛了,可能有一部分消息已經(jīng)處理了,但 offset 還沒提交,或者 offset 已經(jīng)提交了,但消息還沒處理完,就會導(dǎo)致消息重復(fù)或者丟失。所以咱一般會使用手動提交 offset 的方式,消費者處理完消息后,手動提交 offset,這樣就能保證消息要么處理完并提交 offset,要么沒處理完,重新消費。
3. Broker 端:副本機制保可靠
Kafka 的 Broker 端通過副本機制來保證消息的可靠性。每個分區(qū)都有多個副本,包括一個 Leader 副本和多個 Follower 副本。生產(chǎn)者發(fā)送消息到 Leader 副本,F(xiàn)ollower 副本從 Leader 副本同步消息。當(dāng) Leader 副本掛了,會從 ISR 中的 Follower 副本中選舉出新的 Leader 副本,保證消息不會丟失。
4. 防重復(fù):冪等性加事務(wù)
Kafka 從 0.11 版本開始支持冪等性生產(chǎn)者,通過給每條消息生成一個唯一的 PID 和 Sequence Number,保證即使生產(chǎn)者重試,也不會產(chǎn)生重復(fù)的消息。不過冪等性只能保證單個分區(qū)內(nèi)的消息不重復(fù),跨分區(qū)和跨會話的情況下,還是需要使用事務(wù)機制。事務(wù)機制可以保證在多個分區(qū)上的操作要么全部成功,要么全部失敗,從而避免消息重復(fù)或丟失。
(三)RocketMQ:六邊形戰(zhàn)士的全能防丟防重
RocketMQ 就像個六邊形戰(zhàn)士,在各個方面都表現(xiàn)得很均衡,防丟防重機制也很完善,啥場景都能應(yīng)對。
1. 生產(chǎn)者端:同步異步確認任選
RocketMQ 的生產(chǎn)者發(fā)送消息時,可以選擇同步發(fā)送、異步發(fā)送和單向發(fā)送。同步發(fā)送會等待 Broker 的確認,確保消息發(fā)送成功,適合對可靠性要求高的場景。異步發(fā)送則是發(fā)送消息后,通過回調(diào)函數(shù)來處理確認結(jié)果,不阻塞線程,提高效率。單向發(fā)送就是只發(fā)送消息,不等待確認,適合對可靠性要求不高,但追求吞吐量的場景。
還有生產(chǎn)者的重試策略,當(dāng)發(fā)送消息失敗時,會按照一定的策略重試,比如默認重試 2 次,而且可以配置不同的重試間隔。
2. 消費者端:手動確認加順序消費
RocketMQ 的消費者可以選擇集群消費和廣播消費模式。在集群消費模式下,默認是自動提交消費進度,但也可以開啟手動提交,消費者處理完消息后,手動調(diào)用 ack 方法來確認消息已經(jīng)消費。對于順序消息,消費者會按照消息的順序來消費,并且在處理完一條消息后,才會處理下一條,保證了順序性和可靠性。
3. Broker 端:持久化存儲加主從架構(gòu)
RocketMQ 的 Broker 端通過 CommitLog 文件來存儲消息,并且支持異步刷盤和同步刷盤。異步刷盤是指消息先寫入內(nèi)存緩沖區(qū),然后定期寫入磁盤,吞吐量高,但可能會丟失少量消息。同步刷盤是指消息必須寫入磁盤后,才給生產(chǎn)者確認,可靠性高,但吞吐量低。
主從架構(gòu)則是將 Broker 節(jié)點分為 Master 節(jié)點和 Slave 節(jié)點,Master 節(jié)點負責(zé)處理讀寫請求,Slave 節(jié)點從 Master 節(jié)點同步數(shù)據(jù),當(dāng) Master 節(jié)點掛了,Slave 節(jié)點可以切換為 Master 節(jié)點,保證服務(wù)的可用性和消息的可靠性。
4. 防重復(fù):唯一鍵加去重服務(wù)器
RocketMQ 在生產(chǎn)者端可以給消息設(shè)置唯一的 Key,消費者端在消費消息時,可以根據(jù)這個 Key 來判斷是否已經(jīng)消費過。另外,RocketMQ 還提供了去重服務(wù)器,可以在 Broker 端對重復(fù)的消息進行去重處理,不過這需要開啟相應(yīng)的配置。
三、實戰(zhàn)選型:到底該翻誰的牌子?
現(xiàn)在咱了解了各個 MQ 在防丟防重方面的機制,那在實際項目中,到底該怎么選呢?咱得結(jié)合具體的場景來分析。
(一)小而美的 RabbitMQ
適合場景:中小型項目,對可靠性要求較高,業(yè)務(wù)場景不太復(fù)雜,比如簡單的訂單通知、短信發(fā)送等。
優(yōu)勢:輕量級,容易部署和維護,社區(qū)活躍,文檔豐富,插件眾多,可以滿足各種個性化需求。
劣勢:吞吐量相對較低,在處理大規(guī)模消息時,性能可能會有所下降,而且集群管理相對復(fù)雜。
(二)吞吐王者 Kafka
適合場景:大數(shù)據(jù)場景,比如日志收集、實時數(shù)據(jù)處理、流量削峰等,對吞吐量和實時性要求很高。
優(yōu)勢:超高的吞吐量,支持海量消息的處理,分布式架構(gòu)設(shè)計,可擴展性強,適合構(gòu)建實時數(shù)據(jù)管道。
劣勢:消息的延遲相對較高,在可靠性要求極高的場景下,需要仔細配置 acks 參數(shù)和副本機制,而且對消息的順序性支持不夠好,除非是單個分區(qū)內(nèi)的順序。
(三)全能選手 RocketMQ
適合場景:大型分布式系統(tǒng),對可靠性、吞吐量、順序性、事務(wù)等都有要求的場景,比如電商、金融等復(fù)雜業(yè)務(wù)場景。
優(yōu)勢:功能全面,支持事務(wù)消息、順序消息、延遲消息等多種特性,高可用性和高可靠性,集群管理相對簡單,適合國內(nèi)的技術(shù)生態(tài)。
劣勢:需要一定的學(xué)習(xí)成本,雖然比 Kafka 簡單,但比 RabbitMQ 還是復(fù)雜一些,而且社區(qū)活躍度相比 Kafka 和 RabbitMQ,稍微差那么一丟丟。
(四)其他 MQ:各有千秋
ActiveMQ:老牌的 MQ,支持多種協(xié)議,比如 JMS、AMQP 等,適合傳統(tǒng)的 Java 企業(yè)應(yīng)用,但性能和吞吐量相對較低,社區(qū)活躍度也不如以前了。
Pulsar:新興的 MQ,支持多租戶、持久化存儲和非持久化存儲,在云原生場景下表現(xiàn)不錯,但普及度還不夠高,生態(tài)還在完善中。
四、避坑指南:這些坑別踩!
(一)過度追求可靠性,忽略性能
有些小伙伴覺得,既然消息不能丟不能重,那就把所有的可靠性機制都打開,比如 Kafka 的 acks=-1,同步刷盤,RocketMQ 的同步刷盤,主從架構(gòu)等等。這樣確實能保證消息的可靠性,但會極大地影響性能,導(dǎo)致吞吐量下降,延遲增加。所以咱得根據(jù)實際場景,在可靠性和性能之間找到一個平衡點。
(二)忽略業(yè)務(wù)層的去重處理
雖然各個 MQ 都有一些防重復(fù)的機制,但都不是萬能的,比如 RabbitMQ 主要靠業(yè)務(wù)層去重,Kafka 的冪等性只能保證單個分區(qū)內(nèi)的去重,RocketMQ 的去重服務(wù)器也需要配置。所以咱在項目中,一定要在業(yè)務(wù)層做好去重處理,比如給消息加唯一 ID,處理前先檢查是否已經(jīng)處理過。
(三)不重視監(jiān)控和報警
即使我們選擇了合適的 MQ,配置了完善的防丟防重機制,也不能掉以輕心。我們需要對 MQ 的運行狀態(tài)進行實時監(jiān)控,比如隊列的堆積情況、消息的發(fā)送和消費速率、Broker 節(jié)點的健康狀態(tài)等等。一旦出現(xiàn)異常,要及時報警,以便快速處理問題,避免消息丟失或重復(fù)的發(fā)生。
五、總結(jié):沒有最好的 MQ,只有最適合的 MQ
回到面試官的問題,消息不丟不重,消息隊列咋選?其實沒有一個標準答案,關(guān)鍵是要結(jié)合項目的具體需求,比如業(yè)務(wù)場景、數(shù)據(jù)量、吞吐量、延遲要求、可靠性要求等等。RabbitMQ 就像個精致的小皮鞋,適合在平坦的小路上走;Kafka 就像個越野卡車,適合在大數(shù)據(jù)的泥濘道路上狂奔;RocketMQ 就像個全能的 SUV,各種路況都能應(yīng)對。
咱在選擇的時候,要先搞清楚項目的核心需求,是要可靠性優(yōu)先,還是性能優(yōu)先,或者是功能全面。然后再深入了解各個 MQ 的特性和機制,看看哪個最符合咱的需求。同時,也要注意在實際使用中,合理配置各個參數(shù),做好業(yè)務(wù)層的處理和監(jiān)控報警,這樣才能讓消息隊列真正發(fā)揮作用,既不丟消息,也不重消息,讓面試官對你刮目相看。