😊你好,我是小航,一個正在變禿、變強的文藝傾年。
🔔本專欄《八股消消樂》旨在記錄個人所背的八股文,包括Java/Go開發、Vue開發、系統架構、大模型開發、具身智能、機器學習、深度學習、力扣算法
等相關知識點,期待與你一同探索、學習、進步,一起卷起來叭!
目錄
- 題目
- 答案
- Kafka 消息分區
- 有序消息
- 跨topic的有序消息
- 簡歷優化
- 單分區
- 異步消費
- 多分區
- 數據不均勻
- 槽與槽分配
- 一致性哈希
- 消息失序
題目
💬技術棧:RocketMQ、Kafka、RabbitMQ
🔍簡歷內容:熟悉Kafka消息分區。為解決Kafka線上消息積壓、broker性能抖動問題,針對業務內有序為topic實現了多分區。參考Redis槽與槽分配機制解決了數據不均勻問題。針對分區擴容采用了停頓方案解決消息失序問題。
🚩面試問:你覺得在多分區方案里面,如果某個分區消息積壓了就啟用異步消費,這種解決思路你覺得怎么樣?
💡建議暫停思考10s,你有答案了嘛?如果你有不同題解,歡迎評論區留言、打卡。
答案
Kafka 消息分區
在 Kafka 中,消息是以分區為單位進行存儲的
。每個 topic 都可以被劃分為一個或多個分區,每個分區都是一個 有序、不可變 的消息日志。Kafka 使用 WAL (write-ahead-log)日志來存儲消息
。每個分區都有一個對應的日志文件,新的消息會被追加到文件的末尾,而已經加入日志里的消息,就不會再被修改了。
每個消息在分區日志里都有一個唯一的偏移量(offset),用來標識消息在分區里的位置。 Kafka 保證同一分區內的消息順序,但不保證不同分區之間的順序。而 Kafka 本身暴露了對應的接口,也就是說你可以顯式地指定消息要發送到哪個分區,也可以顯式地指定消費哪個分區的數據。
有序消息
有序消息:消費者消費某個 topic 消息的順序,和生產者生產消息的順序一模一樣。
生產者生產消息的順序,不是指你創建出來消息那個實例的先后順序,而是指broker收到的順序。
跨topic的有序消息
問題:保證不同topic之間消息是有序的
。比如說 msg1 先發送到了 topic_a 上, msg2 被發送到了 topic_b
上。但是在業務層面上,要求 msg1 一定要先于 msg2 消費
。
解決方案:引入一個協調者,這個協調者負責把消息重組為有序消息
。比如說,如果 msg2 先到了,但是 msg1 還沒出來,那么這個協調者要有辦法讓 msg2 的消費者 B 停下來,暫時不消費 msg2。而在 msg1 來了之后,喚醒消費者 A 消費 msg1,并且在消費完 msg1 之后要再喚醒消費者 B 處理 msg2。
簡歷優化
前置準備:
- 在什么業務場景下,你需要用到有序消息?
- 你是如何解決有序消息這個問題的?用的是哪種方案?
- 如果你用的是
單分區解決方案,那么有沒有消息積壓問題
?如果有,你是怎么解決的? - 如果你用的是
多分區解決方案,那么有沒有分區負載不均衡的問題
?如果有,你是怎么解決的?
單分區
解決方案:為了保證消息有序,讓特定的 topic 只有一個分區。這樣所有的消息都發到同一個分區上,那么自然就是有序的。
存在的問題: 性能差、消息積壓。
- 生產端:所有的消息都在一個分區上,也同時意味著所有的消息都發送到了同一個broker上,這個服務器很可能撐不住壓力;
- 消費端:只有一個分區,那么就只能有一個消費者消費,很容易出現消息積壓的問題。
全局有序:沒什么優化手段,只能是換用更加強大的機器。
業務內有序:比如說在下單的場景下,會產生創建訂單消息和完成支付消息。業務上只會要求同一個訂單的創建訂單消息應該優先于完成支付消息,但是不會要求訂單 A 的創建消息需要先于訂單 B 的支付消息。這種解決方案參考下面的異步消費、多分區方案。
異步消費
問題:單分區方案中,由于只有一個消費者,容易造成消息積壓。
解決方案:保證同一個業務內的順序。當消費者從隊列取出來消息之后,把同一個業務的消息丟到同一個隊列里。
消費者線程從 Kafka 里獲取消息,然后轉發到內存隊列里面。在轉發的時候,要把同一個業務的消息轉發到同一個隊列里面。一般來說可以根據業務特征字段計算一個哈希值,比如說直接使用業務 id 作為哈希值。利用這個哈希值除以工作線程數量,然后取余數,得到對應的內存隊列
。
存在的問題:可能存在消息未消費。消費線程取出來了,轉發到隊列之后,工作線程還沒來得及處理,消費者整體就宕機了
,那么這些消息就存在丟失的可能。
多分區
將上面方案的內存隊列換成了多個分區。原本同一個業務的消息發送到同一個分區。
保證同一個業務的消息必然發送到同一個分區:生產者在發送消息的時候,根據業務特征,比如說業務 ID 計算出目標分區,在發送的時候顯式地指定分區就可以了。
引發的問題:數據不均勻、增加分區可能導致消息失序。
數據不均勻
問題:數據不均勻一般是業務造成的。在我們的方案里面,分區是根據業務特征來選擇的
,那么自然有一些分區有很多數據,有一些分區數據很少
。比如說萬一我們不小心把熱點用戶的消息都發到了同一個分區里面,那么這個分區的 QPS 就會很高,消費者也不一定來得及消費,就可能引起消息積壓。
解決方案:Redis 中槽和槽分配的機制,又或者說一致性哈希算法。
槽與槽分配
借鑒 Redis 的槽與槽分配方案。不過 Redis 使用了 16384 個槽,一般的業務用不上那么多槽,所以可以考慮用 1024 個槽
。根據業務的特征來計算一個哈希值,再除以 1024 取余就可以得到所在的槽
。再根據不同槽的數據多少,合理地把槽分配到不同的分區。最好把槽和分區的綁定關系做成動態的
,也就是說我可以隨時調整槽到分區的映射關系,保證所有的分區負載都是均勻的。
動態調整槽與分區的綁定:借助于配置中心來完成。比如說最開始你把槽 1 綁定到分區 2 上
,后面在運行的時候你發現分區 2 數據太多,就把槽 1 重新綁定到了分區 3 上
。
一致性哈希
使用一致性哈希算法來篩選分區。首先要根據數據分布的整體情況,把分區分布在哈希環上,確保每一個分區上的數據分布大體上是均勻的。如果一部分哈希值上數據較多,就多插入幾個分區節點。然后根據業務特征計算一個哈希值,從哈希環上找到對應的分區
。
消息失序
場景:如果中間有增加新的分區,那么就可能引起消息失序
。比如說最開始 id 為 3 的訂單消息 msg1 發到分區 0 上,但是這時候很不幸分區 0 上積攢了很多消息,所以 msg1 遲遲得不到消費。緊接著我們擴容,增加了一個新的分區
。如果這時候來了一個消息 msg2,那么它會被轉發到分區 3 上。分區 3 上面沒有積攢什么數據,所以消費者 3 直接就消費了這個消息
。本來 msg1 應該先于 msg2 被消費。而增加分區之后 msg2 反而被先消費了。
解決方案:對于新加入的分區,可以暫停消費一段時間。如果我們估算 msg1 會在一分鐘內被消費,那么新加入的分區的消費者可以在三分鐘后再開始消費。那么大概率 msg1 就會先于 msg2 消費
。
不過
這種等待的解決方式并不能解決根本問題,只能說是很大程度上緩解了問題
。但是本身增加分區也是一個很不常見的操作,再疊加消息失序的概率也很低,所以我們也可以通過監控發現這種失序場景,然后再手工修復一下就可以了
。
簡歷總結:
最開始我進公司的時候就遇到了一個 Kafka 的線上故障。我司有一個業務需要用到有序消息,所以最開始的設計就是對應的 topic 只有一個分區,從而保證了消息有序。
可是隨著業務增長,一個分區很快就遇到了性能瓶頸。只有一個分區,也就意味著只有一個消費者,所以在業務增長之后,就開始出現了消息積壓。另外一方面,這個分區所在的broker的負載也明顯比其他服務器要大,偶爾也會有一些性能抖動的問題。
后來我仔細看了我們的業務,實際上,我們的業務要求的不是全局有序,而是業務內有序。
換句話來說,不一定非得用一個分區,而是可以考慮使用多個分區。所以我就給這個 topic 增加了幾個分區,同時也增加了消費者。優化完之后,到目前為止,還沒有出現過消息積壓的問題。
當然,為了避免在單分區增加到多分區的時候,出現消息失序的問題,我用了一個很簡單的方案,就是對應的消費者在啟動之后,并沒有立刻消費,而是停頓了三分鐘,從而避免了潛在的消息失序問題。
往期精彩專欄內容,歡迎訂閱:
🔗【八股消消樂】20250624:消息隊列優化—延遲消息
🔗【八股消消樂】20250623:消息隊列優化—系統架構設計
🔗【八股消消樂】20250622:Elasticsearch查詢優化
🔗【八股消消樂】20250620:Elasticsearch優化—檢索Labubu
🔗【八股消消樂】20250619:構建微服務架構體系—保證服務高可用
🔗【八股消消樂】20250615:構建微服務架構體系—鏈路超時控制
🔗【八股消消樂】20250614:構建微服務架構體系—實現制作庫與線上庫分離
🔗【八股消消樂】20250612:構建微服務架構體系—限流算法優化
🔗【八股消消樂】20250611:構建微服務架構體系—降級策略全總結
🔗【八股消消樂】20250610:構建微服務架構體系—熔斷恢復抖動優化
🔗【八股消消樂】20250609:構建微服務架構體系—負載均衡算法如何優化
🔗【八股消消樂】20250608:構建微服務架構體系—服務注冊與發現
🔗【八股消消樂】20250607:MySQL存儲引擎InnoDB知識點匯總
🔗【八股消消樂】20250606:MySQL參數優化大匯總
🔗【八股消消樂】20250605:端午節產生的消費數據,如何分表分庫?
🔗【八股消消樂】20250604:如何解決SQL線上死鎖事故
🔗【八股消消樂】20250603:索引失效與優化方法總結
🔗【八股消消樂】20250512:慢SQL優化手段總結
🔗【八股消消樂】20250511:項目中如何排查內存持續上升問題
🔗【八股消消樂】20250510:項目中如何優化JVM內存分配?
🔗【八股消消樂】20250509:你在項目中如何優化垃圾回收機制?
🔗【八股消消樂】20250508:Java編譯優化技術在項目中的應用
🔗【八股消消樂】20250507:你了解JVM內存模型嗎?
🔗【八股消消樂】20250506:你是如何設置線程池大小?
🔗【八股消消樂】20250430:十分鐘帶背Duubo中大廠經典面試題
🔗【八股消消樂】20250429:你是如何在項目場景中選取最優并發容器?
🔗【八股消消樂】20250428:你是項目中如何優化多線程上下文切換?
🔗【八股消消樂】20250427:發送請求有遇到服務不可用嗎?如何解決?
📌 [ 筆者 ] 文藝傾年
📃 [ 更新 ] 2025.6.25
? [ 勘誤 ] /* 暫無 */
📜 [ 聲明 ] 由于作者水平有限,本文有錯誤和不準確之處在所難免,本人也很想知道這些錯誤,懇望讀者批評指正!