阿裡三面:MQ 消息丟失、重覆、積壓問題,如何解決?

来源:https://www.cnblogs.com/javastack/archive/2023/09/13/17698757.html
-Advertisement-
Play Games

作者:美得讓人心動 來源:https://blog.csdn.net/gu131007416553/article/details/120934738 面試官在面試候選人時,如果發現候選人的簡歷中寫了在項目中使用了 MQ 技術(如 Kafka、RabbitMQ、RocketMQ),基本都會拋出一個問 ...


作者:美得讓人心動
來源:https://blog.csdn.net/gu131007416553/article/details/120934738

面試官在面試候選人時,如果發現候選人的簡歷中寫了在項目中使用了 MQ 技術(如 Kafka、RabbitMQ、RocketMQ),基本都會拋出一個問題:在使用 MQ 的時候,怎麼確保消息 100% 不丟失?

這個問題在實際工作中很常見,既能考察候選者對於 MQ 中間件技術的掌握程度,又能很好地區分候選人的能力水平。接下來,我們就從這個問題出發,探討你應該掌握的基礎知識和答題思路,以及延伸的面試考點。

案例背景

以京東系統為例,用戶在購買商品時,通常會選擇用京豆抵扣一部分的金額,在這個過程中,交易服務和京豆服務通過 MQ 消息隊列進行通信。在下單時,交易服務發送“扣減賬戶 X 100 個京豆”的消息給 MQ 消息隊列,而京豆服務則在消費端消費這條命令,實現真正的扣減操作。

那在這個過程中你會遇到什麼問題呢?

案例分析

要知道,在互聯網面試中,引入 MQ 消息中間件最直接的目的是:做系統解耦合流量控制,追其根源還是為瞭解決互聯網系統的高可用和高性能問題。

  • 系統解耦
    用 MQ 消息隊列,可以隔離系統上下游環境變化帶來的不穩定因素,比如京豆服務的系統需求無論如何變化,交易服務不用做任何改變,即使當京豆服務出現故障,主交易流程也可以將京豆服務降級,實現交易服務和京豆服務的解耦,做到了系統的高可用。
  • 流量控制:
    遇到秒殺等流量突增的場景,通過 MQ 還可以實現流量的“削峰填谷”的作用,可以根據下游的處理能力自動調節流量。

不過引入 MQ 雖然實現了系統解耦合流量控制,也會帶來其他問題。

引入 MQ 消息中間件實現系統解耦,會影響系統之間數據傳輸的一致性。 在分散式系統中,如果兩個節點之間存在數據同步,就會帶來數據一致性的問題。同理,在這一講你要解決的就是:消息生產端和消息消費端的消息數據一致性問題(也就是如何確保消息不丟失)。

而引入 MQ 消息中間件解決流量控制, 會使消費端處理能力不足從而導致消息積壓,這也是你要解決的問題。

所以你能發現,問題與問題之間往往是環環相扣的,面試官會藉機考察你解決問題思路的連貫性和知識體系的掌握程度。

那面對“在使用 MQ 消息隊列時,如何確保消息不丟失”這個問題時,你要怎麼回答呢?首先,你要分析其中有幾個考點,比如:

  • 如何知道有消息丟失?
  • 哪些環節可能丟消息?
  • 如何確保消息不丟失?

候選人在回答時,要先讓面試官知道你的分析思路,然後再提供解決方案:網路中的數據傳輸不可靠,想要解決如何不丟消息的問題,首先要知道哪些環節可能丟消息,以及我們如何知道消息是否丟失了,最後才是解決方案(而不是上來就直接說自己的解決方案)。就好比“架構設計”“架構”體現了架構師的思考過程,而“設計”才是最後的解決方案,兩者缺一不可。

案例解答

我們首先來看消息丟失的環節,一條消息從生產到消費完成這個過程,可以劃分三個階段,分別為消息生產階段,消息存儲階段和消息消費階段。

  • 消息生產階段: 從消息被生產出來,然後提交給 MQ 的過程中,只要能正常收到 MQ Broker 的 ack 確認響應,就表示發送成功,所以只要處理好返回值和異常,這個階段是不會出現消息丟失的。
  • 消息存儲階段: 這個階段一般會直接交給 MQ 消息中間件來保證,但是你要瞭解它的原理,比如 Broker 會做副本,保證一條消息至少同步兩個節點再返回 ack。
  • 消息消費階段: 消費端從 Broker 上拉取消息,只要消費端在收到消息後,不立即發送消費確認給 Broker,而是等到執行完業務邏輯後,再發送消費確認,也能保證消息的不丟失。

方案看似萬無一失,每個階段都能保證消息的不丟失,但在分散式系統中,故障不可避免,作為消息生產端,你並不能保證 MQ 是不是弄丟了你的消息,消費者是否消費了你的消息,所以,本著 Design for Failure 的設計原則,你還是需要一種機制,來 Check 消息是否丟失了。

緊接著,你還可以向面試官闡述怎麼進行消息檢測? 總體方案解決思路為:在消息生產端,給每個發出的消息都指定一個全局唯一 ID,或者附加一個連續遞增的版本號,然後在消費端做對應的版本校驗。

具體怎麼落地實現呢?你可以利用攔截器機制。 在生產端發送消息之前,通過攔截器將消息版本號註入消息中(版本號可以採用連續遞增的 ID 生成,也可以通過分散式全局唯一 ID生成)。然後在消費端收到消息後,再通過攔截器檢測版本號的連續性或消費狀態,這樣實現的好處是消息檢測的代碼不會侵入到業務代碼中,可以通過單獨的任務來定位丟失的消息,做進一步的排查。

這裡需要你註意:如果同時存在多個消息生產端和消息消費端,通過版本號遞增的方式就很難實現了,因為不能保證版本號的唯一性,此時只能通過全局唯一 ID 的方案來進行消息檢測,具體的實現原理和版本號遞增的方式一致。

現在,你已經知道了哪些環節(消息存儲階段、消息消費階段)可能會出問題,並有瞭如何檢測消息丟失的方案,然後就要給出解決防止消息丟失的設計方案。

回答完“如何確保消息不會丟失?” 之後,面試官通常會追問“怎麼解決消息被重覆消費的問題?

比如:在消息消費的過程中,如果出現失敗的情況,通過補償的機制發送方會執行重試,重試的過程就有可能產生重覆的消息,那麼如何解決這個問題?

這個問題其實可以換一種說法,就是如何解決消費端冪等性問題(冪等性,就是一條命令,任意多次執行所產生的影響均與一次執行的影響相同),只要消費端具備了冪等性,那麼重覆消費消息的問題也就解決了。

我們還是來看扣減京豆的例子,將賬戶 X 的金豆個數扣減 100 個,在這個例子中,我們可以通過改造業務邏輯,讓它具備冪等性。

最簡單的實現方案,就是在資料庫中建一張消息日誌表, 這個表有兩個欄位:消息 ID 和消息執行狀態。這樣,我們消費消息的邏輯可以變為:在消息日誌表中增加一條消息記錄,然後再根據消息記錄,非同步操作更新用戶京豆餘額。

因為我們每次都會在插入之前檢查是否消息已存在,所以就不會出現一條消息被執行多次的情況,這樣就實現了一個冪等的操作。當然,基於這個思路,不僅可以使用關係型資料庫,也可以通過 Redis 來代替資料庫實現唯一約束的方案。

在這裡我多說一句,想要解決“消息丟失”和“消息重覆消費”的問題,有一個前提條件就是要實現一個全局唯一 ID 生成的技術方案。這也是面試官喜歡考察的問題,你也要掌握。

在分散式系統中,全局唯一 ID 生成的實現方法有資料庫自增主鍵、UUID、Redis,Twitter-Snowflake 演算法,我總結了幾種方案的特點,你可以參考下。

我提醒你註意,無論哪種方法,如果你想同時滿足簡單、高可用和高性能,就要有取捨,所以你要站在實際的業務中,說明你的選型所考慮的平衡點是什麼。我個人在業務中比較傾向於選擇 Snowflake 演算法,在項目中也進行了一定的改造,主要是讓演算法中的 ID 生成規則更加符合業務特點,以及優化諸如時鐘回撥等問題。

當然,除了“怎麼解決消息被重覆消費的問題?”之外,面試官還會問到你“消息積壓”。 原因在於消息積壓反映的是性能問題,解決消息積壓問題,可以說明候選者有能力處理高併發場景下的消費能力問題。

你在解答這個問題時,依舊要傳遞給面試官一個這樣的思考過程: 如果出現積壓,那一定是性能問題,想要解決消息從生產到消費上的性能問題,就首先要知道哪些環節可能出現消息積壓,然後在考慮如何解決。

因為消息發送之後才會出現積壓的問題,所以和消息生產端沒有關係,又因為絕大部分的消息隊列單節點都能達到每秒鐘幾萬的處理能力,相對於業務邏輯來說,性能不會出現在中間件的消息存儲上面。毫無疑問,出問題的肯定是消息消費階段,那麼從消費端入手,如何回答呢?

如果是線上突發問題,要臨時擴容,增加消費端的數量,與此同時,降級一些非核心的業務。通過擴容和降級承擔流量,這是為了表明你對應急問題的處理能力。

其次,才是排查解決異常問題,如通過監控,日誌等手段分析是否消費端的業務邏輯代碼出現了問題,優化消費端的業務處理邏輯。

最後,如果是消費端的處理能力不足,可以通過水平擴容來提供消費端的併發處理能力,但這裡有一個考點需要特別註意, 那就是在擴容消費者的實例數的同時,必須同步擴容主題 Topic 的分區數量,確保消費者的實例數和分區數相等。如果消費者的實例數超過了分區數,由於分區是單線程消費,所以這樣的擴容就沒有效果。

比如在 Kafka 中,一個 Topic 可以配置多個 Partition(分區),數據會被寫入到多個分區中,但在消費的時候,Kafka 約定一個分區只能被一個消費者消費,Topic 的分區數量決定了消費的能力,所以,可以通過增加分區來提高消費者的處理能力。

總結

至此,我們講解了 MQ 消息隊列的熱門問題的解決方案,無論是初中級還是高級研發工程師,本篇文章的內容都是你需要掌握的,你都可以從這幾點出發,與面試官進行友好的交流。我來總結一下今天的重點內容。

  • 如何確保消息不會丟失? 你要知道一條消息從發送到消費的每個階段,是否存在丟消息,以及如何監控消息是否丟失,最後才是如何解決問題,方案可以基於“ MQ 的可靠消息投遞 ”的方式。
  • 如何保證消息不被重覆消費? 在進行消息補償的時候,一定會存在重覆消息的情況,那麼如何實現消費端的冪等性就這道題的考點。
  • 如何處理消息積壓問題? 這道題的考點就是如何通過 MQ 實現真正的高性能,回答的思路是,本著解決線上異常為最高優先順序,然後通過監控和日誌進行排查並優化業務邏輯,最後是擴容消費端和分片的數量。

在回答問題的時候,你需要特別註意的是,讓面試官瞭解到你的思維過程,這種解決問題的能力是面試官更為看中的,比你直接回答一道面試題更有價值。

另外,如果你應聘的部門是基礎架構部,那麼除了要掌握本講中的常見問題的主線知識以外,還要掌握消息中間件的其他知識體系,如:

  • 如何選型消息中間件?
  • 消息中間件中的隊列模型與發佈訂閱模型的區別?
  • 為什麼消息隊列能實現高吞吐?
  • 序列化、傳輸協議,以及記憶體管理等問題
  • … >

好了,今天就分享這麼多,有收穫的話,給個三連唄!

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2022最新版)

2.勁爆!Java 協程要來了。。。

3.Spring Boot 2.x 教程,太全了!

4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!

5.《Java開發手冊(嵩山版)》最新發佈,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 通過這個解釋,我們將瞭解當Python程式顯示類似NameError: name '' is not defined的錯誤時,即使該函數存在於腳本中,也會出現這種情況。 我們還學習了當我們使用拼寫錯誤的變數或沒有導入的內置函數時會發生什麼,以及如何在Python中避免這些錯誤。 避免在Python聲 ...
  • 本文將使用實際的例子來解釋Python的urlparse() 函數來解析和提取URL中的功能變數名稱。我們還將討論如何提高我們解析 URL 的能力和使用它們的不同組件。 用urlparse() 從 URL 中提取功能變數名稱 urlparse() 方法是Python的urllib 模塊的一部分,當你需要將URL拆分 ...
  • Java實現常見查找演算法 查找是在大量的信息中尋找一個特定的信息元素,在電腦應用中,查找是常用的基本運算,例如編譯程式中符號表的查找。 線性查找 線性查找(Linear Search)是一種簡單的查找演算法,用於在數據集中逐一比較每個元素,直到找到目標元素或搜索完整個數據集。它適用於任何類型的數據集 ...
  • Matplotlib 提供了大量配置參數,這些參數可以但不限於讓我們從整體上調整通過 Matplotlib 繪製的圖形樣式,這裡面的參數還有很多是功能性的,和其他工具結合時需要用的配置。 通過plt.rcParams,可以查看所有的配置信息: import matplotlib.pyplot as ...
  • 大家好,我是TJ 今天給大家推薦一個開源協作式數字白板:tldraw。 tldraw的編輯器、用戶界面和其他底層庫都是開源的,你可以在它的開源倉庫中找到它們。它們也在NPM上分發,提供開發者使用。您可以使用tlDraw為您的產品創建一個臨時白板,或者將其作為構建自己應用的工具來使用。 線上體驗 tl ...
  • 基於java學生考勤管理系統設計與實現,可適用於學生考勤系統,校園考勤,大學考勤管理,企業考勤系統,公司考勤系統,學校考勤系統。 ...
  • 隨著JDK19的發佈,虛擬線程也逐漸被大家瞭解和使用,然而,主流java框架是否支持虛擬線程這一特性呢?咱們應用開發者如何通過框架使用虛擬線程特性?經過精心準備,欣宸原創在第一時間為您帶來詳細的實戰系列,與您共同學習 ...
  • 大家好,又見面了。 在此前我的文章中,曾分2篇詳細探討了下JAVA中Stream流的相關操作,2篇文章收穫了累計 10w+閱讀、2k+點贊以及 5k+收藏的記錄。能夠得到眾多小伙伴的認可,是技術分享過程中最開心的事情。 吃透JAVA的Stream流操作,多年實踐總結 講透JAVA Stream的co ...
一周排行
    -Advertisement-
    Play Games
  • 1、預覽地址:http://139.155.137.144:9012 2、qq群:801913255 一、前言 隨著網路的發展,企業對於信息系統數據的保密工作愈發重視,不同身份、角色對於數據的訪問許可權都應該大相徑庭。 列如 1、不同登錄人員對一個數據列表的可見度是不一樣的,如數據列、數據行、數據按鈕 ...
  • 前言 上一篇文章寫瞭如何使用RabbitMQ做個簡單的發送郵件項目,然後評論也是比較多,也是準備去學習一下如何確保RabbitMQ的消息可靠性,但是由於時間原因,先來說說設計模式中的簡單工廠模式吧! 在瞭解簡單工廠模式之前,我們要知道C#是一款面向對象的高級程式語言。它有3大特性,封裝、繼承、多態。 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 介紹 Nodify是一個WPF基於節點的編輯器控制項,其中包含一系列節點、連接和連接器組件,旨在簡化構建基於節點的工具的過程 ...
  • 創建一個webapi項目做測試使用。 創建新控制器,搭建一個基礎框架,包括獲取當天日期、wiki的請求地址等 創建一個Http請求幫助類以及方法,用於獲取指定URL的信息 使用http請求訪問指定url,先運行一下,看看返回的內容。內容如圖右邊所示,實際上是一個Json數據。我們主要解析 大事記 部 ...
  • 最近在不少自媒體上看到有關.NET與C#的資訊與評價,感覺大家對.NET與C#還是不太瞭解,尤其是對2016年6月發佈的跨平臺.NET Core 1.0,更是知之甚少。在考慮一番之後,還是決定寫點東西總結一下,也回顧一下.NET的發展歷史。 首先,你沒看錯,.NET是跨平臺的,可以在Windows、 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 添加節點(nodes) 通過上一篇我們已經創建好了編輯器實例現在我們為編輯器添加一個節點 添加model和viewmode ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...
  • 類型檢查和轉換:當你需要檢查對象是否為特定類型,並且希望在同一時間內將其轉換為那個類型時,模式匹配提供了一種更簡潔的方式來完成這一任務,避免了使用傳統的as和is操作符後還需要進行額外的null檢查。 複雜條件邏輯:在處理複雜的條件邏輯時,特別是涉及到多個條件和類型的情況下,使用模式匹配可以使代碼更 ...
  • 在日常開發中,我們經常需要和文件打交道,特別是桌面開發,有時候就會需要載入大批量的文件,而且可能還會存在部分文件缺失的情況,那麼如何才能快速的判斷文件是否存在呢?如果處理不當的,且文件數量比較多的時候,可能會造成卡頓等情況,進而影響程式的使用體驗。今天就以一個簡單的小例子,簡述兩種不同的判斷文件是否... ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...