阿里二面掛了!被問「1000 萬簡訊 1 小時發完,怎麼設計執行緒池?」, 面試官:你管這叫執行緒池調優?

> 千萬級推送不僅考參數調優,更考架構防禦!本文拆解 1000 萬簡訊 1 小時發完的真實現場:從 N_threads = N_cpu × U_cpu × (1 + W/C) 黃金公式,到動態監控調優,再到防止 OOM 的「生產級」拒絕策略。文末附帶 P7 級面試套路模板,助你掃平執行緒池深坑。

寫在開頭:

前兩天有個在大廠搬磚的兄弟找我吐槽,說面試掛在了「執行緒池」上。

面試官沒問那些死記硬背的原理,直接丟了一個業務題:

「我們要發 618 行銷簡訊,1000 萬條,要求 1 小時內發完。你打算怎麼設計執行緒池?核心參數給多少?拒絕策略選哪個?」

這哥們沒多想:「簡單啊,算一下 1 小時 3600 秒,每秒發 2800 條。直接搞個 FixedThreadPool,執行緒數開到 500,隊列給大一點不就行了?」

面試官冷笑一聲,連追三問:

  1. FixedThreadPool 預設隊列是 LinkedBlockingQueue,長度是 Integer.MAX_VALUE(近似無界),千萬級資料還沒發完,記憶體就 OOM 了,你負責?」
  2. 「如果簡訊供應商限流了,你的任務積壓在隊列裡,應用重啟任務全丟了怎麼辦?」
  3. 「你怎麼證明你配的執行緒數是最優的?是拍腦袋想的,還是有數據支撐?」

他瞬間原地石化。

其實,這道題考的是「高併發下的資源掌控力」。今天 Fox 帶你拆解執行緒池的 3 種實戰境界。


一、 為什麼 Executors 是生產環境的「禁區」?

在大廠規範裡,嚴禁使用 Executors.newFixedThreadPoolnewCachedThreadPool

  • OOM 隱患: 預設的無界隊列能塞到 Integer.MAX_VALUE 的任務數量。在 1000 萬資料的衝擊下,還沒等到執行緒處理,你的 JVM 堆記憶體就先爆了。
  • 資源耗盡: CachedThreadPool 允許創建的執行緒數也是無上限,瞬間的高併發能直接把 CPU 100% 跑滿,甚至耗盡系統資源。

Fox 的結論:生產環境必須手動建立 ThreadPoolExecutor,且必須配合有界隊列(bounded queue)。


二、 核心架構:執行緒池調優的 3 種境界

境界 1:利用「黃金公式」計算初始值

面試官問你執行緒數給多少,千萬別直接說 200 或 500。你要先問:「這任務是 CPU 密集型還是 IO 密集型?」

簡訊推送涉及網路呼叫,屬於典型的 IO 密集型。

根據經驗公式:

N_threads = N_cpu × U_cpu × (1 + W/C)

  • N_cpu:CPU 核心數
  • U_cpu:目標 CPU 利用率
  • W/C:等待時間與計算時間的比值(Wait/Compute)

實戰落地:對於千萬級推送,通常 W/C 很大,建議初始執行緒數設為 2 × N_cpu 起步,並根據壓測結果調整。

境界 2:動態調優 + 全鏈路監控

參數是「死」的,流量是「活」的。大廠 P7 的標準做法是:使用「動態執行緒池」。

  1. 參數動態化:核心參數(CoreSize、MaxSize、QueueSize)不要寫死在程式碼裡,接入設定中心(如 Apollo、Nacos)。
  2. 監控預警:監控隊列剩餘容量、執行緒池活躍度。當隊列超過 80% 滿時,自動觸發警告或動態擴容。

Fox 提示:業界著名的開源專案 Hippo4J 或 DynamicTp 就是做這件事的,面試時提一句加分不少。

境界 3:拒絕策略的「終極防線」

當 1000 萬資料湧入,執行緒池滿了,拒絕策略(RejectedExecutionHandler)選哪個?

  • AbortPolicy(預設):直接丟出例外,千萬別選,資料直接丟失。
  • CallerRunsPolicy(推薦):讓提交任務的執行緒(例如撈資料的執行緒)自己去執行。這其實是一種「天然的背壓(Backpressure)」。提交端自己去發簡訊,就沒空再去資料庫撈新任務,從而減緩任務產生速度,給執行緒池喘息的機會。

很多人應該還記得我寫過:CallerRunsPolicy(回退給呼叫者執行)是個坑,因為它會阻塞主執行緒。但!在千萬級推送這種「離線批量場景」下,這個「坑」反而成了神技。

  • 在線 Web 場景(避坑):如果是處理使用者請求,絕對不能用它,否則 Tomcat 執行緒被佔滿,整個網站會直接卡死。
  • 離線批量場景(神器):我們從 DB 裡撈千萬級資料往執行緒池塞。如果池子滿了,觸發 CallerRunsPolicy,讓「撈資料的執行緒」自己去發簡訊。
  • 高階奧義:天然背壓(Backpressure)。當「生產者」被迫去做「消費者」的工作時,它就沒空再去撈新資料了。這會自動減緩任務產生速度,給執行緒池喘息時間,徹底規避 OOM 風險。

三、 最後的「防杠」指南:萬一服務掛了怎麼辦?

面試官看你答得不錯,通常會祭出最後一招:「任務在記憶體隊列裡,機器當掉了,100 萬條簡訊沒發出來,怎麼補救?」

滿分回答:

  1. 本地持久化:在任務入隊前,先在資料庫/Redis 記錄一個「發送中」的狀態(或寫入任務表)。
  2. Ack 機制:執行緒處理完後,回寫更新狀態為「已完成」。
  3. 離線補償:啟動一個定時任務(T+N),定期掃描那些狀態為「發送中」且超過 10 分鐘未完成的任務,重新投遞。

四、 面試標準答案模板(直接背誦)

「針對 1000 萬簡訊推送,我不會使用 Executors 快捷建立,因為無界隊列有 OOM 風險。

第一,參數設定:我會基於公式進行壓測,由於是 IO 密集型,初始執行緒數設為 2 × N_cpu 起步,並通過壓測微調。

第二,拒絕策略:我會選 CallerRunsPolicy。它能透過『背壓』機制,讓提交端在任務過載時參與處理,從而限制任務的生產速度,保證系統不崩。

第三,動態化:為了應對簡訊供應商波動,我會接入動態執行緒池框架,實時監控隊列積壓情況並動態調整核心執行緒數與隊列大小。

第四,可靠性:結合資料庫狀態位與定時補償任務,確保即便機器重啟,任務也不會丟失。」


五、 進階思考:單機扛住了,那「分散式」呢?

聊到這裡,肯定有人會問:「Fox,單機執行緒池調優我懂了,但如果 1000 萬任務發到一半,機器當掉了怎麼辦?如果是 1 億資料,單機頻寬和 CPU 根本吃不下呢?」

這正是大廠面試官最喜歡的「奪命連環炮」。

在真實的生產環境下,我們絕對不會把雞蛋放在一個籃子裡。單機調優是「術」,集群架構才是「道」。

現在的互動問題來了:

面試官追問:「現在給你 5 台機器組成的集群,你如何設計一套架構,保證這 1000 萬條簡訊在 1 小時內『不重複、不遺漏、高併發』地發出去?」

提示幾個思考維度:

  1. 任務分片:5 台機器怎麼分工才不會搶任務?(例如基於 Hash、Range 或使用分散式隊列)
  2. 狀態流轉:機器掛了,剩下的任務怎麼接管?(例如任務持久化 + 協調/領取機制)
  3. 全域控速:怎麼保證 5 台機器加起來不把供應商的網關沖垮?(例如令牌桶、全域速率限制)

歡迎在評論區留下你的設計思路!


寫在最後

技術面試拼的不是死記硬背的參數,而是你對「系統穩定性」的敬畏之心。能提前預判 OOM 風險、考慮到背壓問題、兼顧資料可靠性,這才是你和普通開發者拉開差距的關鍵。

覺得有用的兄弟,按讚+收藏,面試前翻一翻,直接避開坑、穩拿分!

想吃透更多高頻面試題、避開面試地雷?可以關注公眾號【Fox愛分享】!我整理的面試寶典已更新至 200 多萬字,光高併發、分散式的專案場景題就有幾百道,全是面試剛需,需要面試的同學直接自取,幫你少走彎路、快速上岸~

文章首發地址:mp.weixin.qq.com/s/j2J7z9U53…


原文出處:https://juejin.cn/post/7626595191144595456


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝5   💬6   ❤️3
425
🥈
我愛JS
📝2   💬7   ❤️2
206
🥉
💬1  
4
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登