成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

RocketMQ 消息積壓千萬級如何快速恢復 生產環境踩坑實錄

開發 前端
回顧這次戰斗,我最大的感悟是:處理積壓不是“頭痛醫頭腳痛醫腳”,而是要從“生產 - 存儲 - 消費”全鏈路分析。比如這次我們只盯著消費者擴容,卻忽略了 Producer 端的流量控制,以及 Broker 端的磁盤 IO 瓶頸。

兄弟們,凌晨三點,手機像被扔進了洗衣機,在枕邊瘋狂震動。我摸索著接起電話,運維小哥帶著哭腔:“哥,訂單系統的 RocketMQ 集群炸了,積壓量直奔兩千萬,現在支付回調全堵著,客服已經收到二十多個商家投訴了!”  

我騰地坐起來,腦子還沒完全清醒,手指已經條件反射地打開監控頁面。好家伙,Consumer 集群的平均消費速度只有 500 條/秒,而堆積量正以肉眼可見的速度往上竄。再看 Producer 端,TPS 倒是穩如老狗——問題出在消費者這邊,典型的“生產快、消費慢”導致的積壓慘案。  

這不是我第一次跟 RocketMQ 積壓打交道,但千萬級的規模還是頭一回。摸著鍵盤的手有點抖,廢話不多說,咱們邊復盤邊聊,看看這種級別的積壓到底該怎么破。  

一、先別急著“重啟服務器”,先把積壓原因盤清楚

很多同學遇到積壓第一反應是“擴容消費者”,但盲目擴容前必須先搞清楚:到底是什么卡住了消費速度? RocketMQ 的消費鏈路就像一條流水線,任何一個環節“堵車”都會導致積壓,咱們得先給這條流水線做個“CT 掃描”。  

1. 第一步:排查 Consumer 是不是“假死”狀態

打開 RocketMQ Dashboard(沒有的同學趕緊裝,運維必備神器),先看 Consumer 分組的“在線客戶端”列表。如果發現某臺服務器的 Consumer 長時間沒有上報心跳(LastHeartbeatTime 超過 2 分鐘),恭喜你,大概率遇到了“消費者假死”。  

這種情況通常是因為消費者線程被 Full GC 卡住,或者代碼里有死循環。我這次就碰到一臺服務器,因為日志打印太猛(沒錯,就是某個同事在循環里寫了 System.out.println),導致 CPU 100%,Consumer 線程直接卡死,積壓量像滾雪球一樣越滾越大。  

踩坑提醒:記得給 Consumer 加上 JVM 監控,重點看 GC 頻率和耗時。我后來發現那臺假死機器的 Young GC 耗時居然超過 500ms,老年代頻繁 Full GC,這種情況下 Consumer 能正常工作才怪。  

2. 第二步:檢查隊列負載是否均衡

RocketMQ 的 Consumer 采用“隊列均分”策略,每個 Consumer 會分配多個 Message Queue(以下簡稱 MQ)。如果某臺 Consumer 分配了 100 個 MQ,另一臺只分配了 10 個,那肯定是“忙的忙死,閑的閑死”。  

怎么看負載情況?Dashboard 里每個 Consumer 實例的“已分配隊列數”一目了然。我這次就發現,有三臺新擴容的服務器因為網絡配置問題,沒連上 NameServer,導致老服務器承擔了 80% 的隊列,消費能力直接被壓垮。  

實操技巧:如果發現隊列分配不均,先重啟 Consumer 實例(觸發重新負載均衡),如果還不行,檢查 Consumer 分組的配置,確保 consumeFromWhere 和 messageModel 設置正確(默認 CLUSTERING 模式下會自動均衡)。  

3. 第三步:看看消費線程是不是“摸魚”

RocketMQ Consumer 的默認消費線程數是 20(對,沒錯,就是這個藏得很深的參數 consumeThreadMin 和 consumeThreadMax)。如果你的業務邏輯比較復雜,比如需要查數據庫、調接口,20 個線程可能根本不夠用,導致大量線程在排隊等待處理。  

我這次查看 Consumer 日志,發現線程池里的任務堆積量超過 1000,而實際在工作的線程只有可憐的 10 個——原來同事在初始化 Consumer 時,手滑把 consumeThreadMin 寫成了 10,Max 也設成 10,相當于固定只有 10 個線程在干活,面對突然暴增的流量,自然頂不住。  

劃重點:消費線程數不是越多越好,要看 CPU 核心數(一般設置為 CPU 核心數的 2 - 3 倍)。如果是 IO 密集型任務,可以適當多開,比如設到 50;如果是 CPU 密集型,超過 32 基本沒意義,反而會因為線程上下文切換拖慢速度。  

二、千萬級積壓的“急救三連招”,先把積壓量壓下來

搞清楚原因后,接下來就是“急救階段”。記住:千萬級積壓時,任何微小的優化乘以千萬都會放大成顯著效果。咱們分步驟來,先讓消費速度追上生產速度,再慢慢“消化”歷史積壓。  

1. 第一招:臨時擴容 Consumer,先把“車道”拓寬

這是最直接的辦法,相當于給高速公路多開幾條車道。RocketMQ 的 Consumer 是“無狀態”的,理論上可以無限擴容,但要注意兩個關鍵點:  

(1)擴容數量不超過 MQ 總數

每個 MQ 同一時間只能被一個 Consumer 消費,比如集群有 100 個 MQ,最多開 100 個 Consumer 實例(每個實例分配 1 個 MQ)。我這次集群有 200 個 MQ,當前只有 10 個 Consumer,理論上可以先擴容到 50 個實例,把隊列分配率拉滿。  

(2)別踩“IP 不對等”的坑

之前擴容時,運維小哥直接復制了老服務器的配置,結果新服務器的 clientIP 被錯誤設置成了內網 IP,而 NameServer 在公網,導致 Consumer 注冊時,其他實例根本找不到它。最后不得不手動加上 rocketmq.client.endpoint 參數,指定公網地址才解決。  

 實操步驟:  

  • 臨時創建一個新的 Consumer 分組(比如加個后綴 -tmp),避免和原有消費者搶資源  
  • 啟動時指定 --consumerThreadMin 50 --consumerThreadMax 50(臨時調高線程數)  
  • 觀察 Dashboard 上的“消費速度”,理想情況下,每臺新服務器能分到 4 - 5 個 MQ,消費速度能提升 3 - 5 倍  

2. 第二招:開啟批量消費,讓消費者一次“搬多箱貨”

RocketMQ 支持批量消費,默認每次拉取 1 條消息(參數 consumeMessageBatchMaxSize 默認為 1)。如果你的業務邏輯允許,可以改成一次拉 10 - 32 條,減少網絡交互次數,提升吞吐量。  

我這次把這個參數改成 16,配合前面的擴容,消費速度從 500 條/秒直接跳到 8000 條/秒——相當于原來每次跑一趟搬 1 箱貨,現在搬 16 箱,效率自然飆升。但要注意:  

(1)批量處理時保持冪等性

因為可能會重復消費(比如處理到第 10 條時消費者掛了,重啟后這 16 條會重新消費),所以業務代碼必須支持冪等(比如用唯一 ID 去重)。我們當時就吃了虧,沒做冪等,導致數據庫出現重復訂單,最后不得不寫腳本去重,血的教訓!  

(2)別貪心設太大的值

超過 32 之后,吞吐量提升不明顯,反而會增加內存壓力(每條消息都會存到內存里)。我們試過設成 100,結果 Consumer 內存使用率瞬間超過 80%,差點觸發 OOM,最后穩定在 16 - 32 之間最佳。  

3. 第三招:暫停 Producer 或限流,先“掐斷源頭”

如果積壓量實在太大,比如像我們這次已經到兩千萬,而消費速度一時半會兒追不上,可以考慮暫時讓 Producer 停止發消息,或者降低發送頻率。  

注意:暫停 Producer 前一定要和業務方溝通,我們當時是電商大促期間,暫停支付回調消息會影響商家收款,最后只能和前端商量,在用戶支付成功頁增加“稍后刷新”提示,同時對 Producer 做限流(從 2000 TPS 降到 500 TPS),給消費者爭取緩沖時間。  

踩坑提醒:暫停 Producer 后,記得監控 Consumer 的“堆積量”是否開始下降(理想情況下每分鐘下降 10 - 20 萬)。如果沒變化,可能是消費者有重試邏輯在反復投遞(比如消息處理失敗后進入重試隊列,導致積壓量“假死”),這時候需要檢查 maxReconsumeTimes 參數(默認 16 次,超過后進入死信隊列)。  

三、積壓消化中的“連環坑”,每一步都可能翻車

當積壓量開始下降,千萬別掉以輕心,這時候往往會遇到各種“隱性炸彈”。我們這次就踩了三個大坑,每個都讓我在凌晨四點的會議室里冒冷汗。  

1. 坑一:消費過快導致“內存溢出”

前面提到我們把批量消費設成 16,線程數開到 50,消費速度確實上去了,但半小時后,一臺 Consumer 突然掛掉,日志里寫著“java.lang.OutOfMemoryError: GC overhead limit exceeded”。  

原因分析:  

  • 批量消費時,每條消息都會解析成 Java 對象,16 條一批,每秒處理 500 批,每秒產生 8000 個對象  
  • Consumer 堆內存默認只有 1G(很多同學不知道,RocketMQ 的 Consumer 啟動腳本默認堆大小是 -Xms1g -Xmx1g),新生代很快被占滿,觸發頻繁 Full GC  

解決方案:  

  • 給 Consumer 增加內存,改成 -Xms4g -Xmx4g(根據服務器配置調整,建議不超過物理內存的 70%)  
  • 調整 JVM 參數,比如 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m,避免元空間溢出  
  • 最重要的:在業務代碼里及時釋放對象引用,比如處理完消息后把對象設為 null,幫助 GC 回收  

2. 坑二:網絡帶寬成了“最后一公里瓶頸”

當積壓量降到 500 萬時,消費速度突然卡住,無論怎么擴容都上不去。檢查服務器監控,發現網卡吞吐量達到 90%(我們用的是 1G 帶寬的服務器)。  

解決思路:  

RocketMQ 的消費流程是“拉取消息”→“處理消息”→“提交消費位點”,其中“拉取消息”是網絡密集型操作。當批量拉取 16 條消息時,每條消息平均 1KB,一次拉取就是 16KB,每秒 500 次拉取就是 8MB/s,50 個 Consumer 實例就是 400MB/s,接近 1G 帶寬的極限。

實操方案:  

  • 給部分 Consumer 實例更換成 10G 帶寬的服務器(臨時采購,有錢任性)  
  • 調整拉取策略,比如增加 pullBatchSize 參數(默認 32,我們設成 64,減少拉取次數)  
  • 對消息體進行壓縮(Producer 發送時啟用 GZIP 壓縮,Consumer 自動解壓縮),我們的消息體從平均 1KB 壓縮到 300 字節,帶寬占用直接降了 70%  

3. 坑三:死信隊列突然“爆炸”,積壓量反彈

當積壓量降到 100 萬時,運維小哥突然喊:“死信隊列里的消息怎么突然多了 50 萬?” 原來是很多消息重試 16 次后仍失敗,自動進入死信隊列,而我們沒有配置死信隊列的消費者,導致這部分消息被“遺忘”了。  

處理步驟:  

  1. 先暫停死信隊列的自動投遞(在 RocketMQ 控制臺找到死信隊列對應的 Consumer 分組,設置 maxReconsumeTimes 為 0,停止重試)  
  2. 寫一個臨時消費者,專門消費死信隊列,把消息內容記錄到日志文件,然后人工排查失敗原因(我們發現大部分是因為數據庫連接超時,凌晨數據庫負載高導致)  
  3. 修復業務代碼后,把死信隊列的消息重新發送回原隊列(用 RocketMQ 的 sendMessageInTransaction 接口,避免重復消費)  

經驗教訓:死信隊列是“最后的防線”,平時一定要監控死信隊列的堆積量,建議設置報警閾值(比如超過 1000 條就提醒),否則等問題爆發時,又是一場硬仗。  

四、積壓恢復后的“亡羊補牢”,避免下次再掉坑

經過 6 個小時的奮戰,積壓量終于歸零,看著監控曲線慢慢 flatten 下來,我揉著酸痛的脖子,開始總結這次的教訓。其實很多問題都是可以提前預防的,以下是我梳理的“防積壓三板斧”,建議寫進運維手冊:  

1. 事前:給 Consumer 裝“儀表盤”,實時監控關鍵指標

別只看“堆積量”,這幾個指標更重要:  

  • 消費延遲(consumeLatency):消息產生到被消費的時間差,超過 10 秒就該警覺  
  • 拉取吞吐量(pullTPS):如果突然下降 50%,可能是網絡或 Broker 有問題  
  • 消費線程池利用率:用 ThreadPoolExecutor.getActiveCount() 監控,長期接近 consumeThreadMax 說明線程不夠用   

我們后來給每個 Consumer 實例加了 Prometheus 監控,配合 Grafana 儀表盤,現在積壓預警比心跳還準。  

2. 事中:準備“應急預案模板”,讓新人也能快速上手

把這次的急救步驟寫成腳本:  

# 快速擴容消費者腳本 
for i in {1..50}; do  
  java -jar consumer.jar \ 
  --consumerGroup order_consumer_tmp \ 
  --namesrvAddr xxx:9876 \ 
  --consumeThreadMin 50 \ 
  --consumeMessageBatchMaxSize 16 \ 
  --clientIP $(curl ifconfig.me) & # 自動獲取公網 IP 
done  

# 暫停死信隊列腳本 
curl -X POST http://rocketmq-dashboard:8080/consumer/update \ 
-H "Content-Type: application/json" \ 
-d '{"groupName":"order_consumer","maxReconsumeTimes":0}'

記住:應急預案一定要定期演練,我們后來組織了一次“積壓模擬演練”,發現腳本里居然有路徑錯誤,還好提前發現,否則實戰時又要抓瞎。  

3. 事后:給消息處理加“保險絲”,防止單個消息拖垮整體

這次發現,90% 的消費延遲都是因為個別“毒消息”(比如超大消息體、格式錯誤)導致的。解決方案:  

  • 設置消息處理超時時間:用 CompletableFuture 包裝業務邏輯,超過 500ms 自動放棄,記錄到日志  
  • 隔離消費線程池:給不同類型的消息分配不同的線程池,比如訂單消息和日志消息分開處理,避免互相影響  
  • 增加本地重試機制:業務代碼里先重試 3 次,失敗后再交給 RocketMQ 的重試隊列,減少 Broker 壓力   

我們給訂單消費加了本地重試后,RocketMQ 的重試次數下降了 60%,消費速度穩定多了。  

五、總結:千萬級積壓不可怕,怕的是沒有“系統化思維”

回顧這次戰斗,我最大的感悟是:處理積壓不是“頭痛醫頭腳痛醫腳”,而是要從“生產 - 存儲 - 消費”全鏈路分析。比如這次我們只盯著消費者擴容,卻忽略了 Producer 端的流量控制,以及 Broker 端的磁盤 IO 瓶頸。  


責任編輯:武曉燕 來源: 石杉的架構筆記
相關推薦

2024-08-02 10:55:30

2025-03-28 08:40:00

C#異步編程

2024-10-09 08:09:11

2024-11-20 18:16:39

MyBatis批量操作數據庫

2021-06-09 08:21:14

Webpack環境變量前端

2025-05-16 10:53:43

開發異步編程JavaScrip

2021-06-26 15:31:25

Dubbo應用級服務

2023-12-21 08:01:41

RocketMQ消息堆積

2022-03-31 08:26:44

RocketMQ消息排查

2025-04-02 08:17:42

2024-04-23 08:46:45

消息積壓KafkaMQ

2025-02-08 08:42:40

Kafka消息性能

2024-12-12 14:56:48

消息積壓MQ分區

2022-03-14 11:05:01

RocketMQRedis緩存

2025-06-27 07:15:30

2025-06-03 06:30:05

2023-08-03 07:13:59

2022-06-27 11:20:13

工具內存GO

2022-02-07 08:55:57

Go程序代碼

2023-01-18 23:20:25

編程開發
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 免费视频中文字幕 | 亚洲精品黄 | 91在线电影| 国产精品视频网 | 精品一区二区三区四区视频 | 亚洲视频1区 | 成人在线免费观看av | 亚洲区一区二区 | 一级片在线观看视频 | 欧美一区二 | 色综合久久久久 | 狠狠插狠狠操 | 一级毛片免费视频观看 | 偷拍第一页| 免费观看一级毛片 | 欧美成人精品二区三区99精品 | 国产一区精品 | 国产成人在线免费 | 国产综合欧美 | 国产午夜精品一区二区三区嫩草 | 网色| 国产精品一区二区三区在线 | 欧美一级免费看 | 欧美日韩国产一区二区三区 | 精品视频一区二区 | 在线免费观看亚洲 | 国产在线视频一区 | 亚洲国产一区在线 | 911影院 | 成人精品毛片国产亚洲av十九禁 | 亚洲精品电影网在线观看 | 成年人免费看的视频 | 欧美精品欧美精品系列 | 精品国产欧美一区二区三区成人 | 91激情电影| 欧美一级片在线观看 | 日韩一区二区三区在线观看视频 | 亚洲一区二区日韩 | 日韩精品无码一区二区三区 | 欧美日韩1区 | 成人激情视频在线观看 |