多應用程式伺服器的公平記憶體分配

日本伺服器租用的部署通常始於一個看似簡單的架構:多個 Web 服務、資料庫、快取、背景任務和排程作業共用一台機器。直到流量驟增、低效查詢或失控的背景任務讓原本充足的 RAM 變成爭搶資源,問題才會浮現。此時,「公平」並不代表為每個程序平均切分記憶體,而是要保護重要的使用者請求,並讓低優先級工作負載以可預測、可控制的方式降級。
記憶體爭搶在造成停機前,往往先表現為延遲問題
工程師通常在程序被終止後才發現問題,但那已經是事故鏈條的後段。在記憶體耗盡之前,主機往往已開始回收頁面、逐出有用的快取、將不活躍頁面寫入交換空間,或讓記憶體分配操作停滯。請求可能仍能完成,但長尾延遲會變得難以預測。因此,即使監控面板顯示「還有可用 RAM」,也不一定代表系統健康。
更有價值的問題是:哪個工作負載正在被延遲,它又在等待什麼?一個交易介面可能只需要較小但穩定的工作集;一個非同步報表產生器可能佔用更大的工作集,卻可以被暫停或重試。若對兩者一視同仁,最終往往是分配速度最快的任務取得了不成比例的資源。
- 關鍵路徑:請求處理、身分驗證、交易處理和有狀態資料服務。
- 重要但可復原:索引、佇列消費者、媒體轉換和內部 API。
- 機會型任務:分析、預覽、開發工具、批次匯出和歷史資料回填。
這種分類方式比靜態的「服務 A 分多少、服務 B 分多少」表格更耐用。容量、流量和程式碼都會變化,而業務重要性通常變化得更慢。
應量測真正相關的記憶體,而不只是常駐記憶體位元組數
單一程序的常駐記憶體很有參考價值,但它無法解釋整台機器的狀態。檔案快取、匿名頁、共享映射、核心資料結構、Socket 緩衝區和暫存檔都可能影響回收行為。某個程序單獨看似乎佔用不高,但其工作負載產生的快取抖動足以影響全部服務。
應建立能夠將工作負載身分與主機症狀關聯起來的觀測視圖。比較目前用量、歷史峰值、分配失敗、重新啟動次數、交換空間活動和請求延遲,並確保這些指標位於同一時間範圍內。對於成組執行的工作負載,既要檢查資源群組層級的用量,也要檢查單一程序;否則,衍生程序和輔助程序會掩蓋真正的資源歸屬。
- 記錄一個完整的正常業務週期,包括排程任務和本地流量高峰。
- 標示每項服務的穩定記憶體佔用,以及短時間內的峰值。
- 尋找相關事件:延遲上升、回收活動、交換空間讀取、分配失敗或被強制重新啟動。
- 在修改策略之前,先透過可控的壓力測試重複驗證。
壓力停滯指標在這裡尤其有價值。它們能反映任務因資源爭搶而無法繼續執行所損失的時間。即使總體使用量低於簡單閾值,記憶體壓力也可能暴露糟糕的使用者體驗。作業系統還可以依資源群組輸出這些訊號,因此很適合用於定位資源噪音鄰居。
先為主機預留安全餘量
不要把所有可用位元組都分配給已命名的服務。作業系統需要空間來處理頁表、網路、檔案系統中繼資料、執行階段突發負載,以及事故期間的維運存取。這部分餘量不是閒置容量,而是復原容量。缺少它時,輕微的記憶體波動就可能讓除錯工具本身也無法執行。
一個實用策略應將容量劃分為三個池:
- 為關鍵服務保留的受保護基線。
- 供正常成長使用的彈性共享區域。
- 刻意不分配的緊急預留空間。
不要把交換空間當作容量規劃的替代品。交換空間可以為冷頁面提供短暫緩衝,但長期交換會把容量問題轉化為延遲問題。一個在等待儲存回應的工作負載即使仍在執行,也很難稱得上健康。應監控換入活動和壓力指標,而不是僅憑交換空間使用量判斷故障。
針對不同任務使用保護、節流和硬限制
現代資源控制介面提供的能力不只單一最大值。其記憶體控制器通常提供盡力而為的受保護下限、會觸發回收與節流的高位邊界,以及硬性上限。這些控制項解決的是不同問題,不能混用。
memory.low表示回收優先級。在一般資源爭搶下,低於該邊界的頁面會在未受保護群組之前得到保護。memory.high是壓力邊界。超過該邊界後,系統會減緩記憶體分配,並促使工作負載回收自身頁面,從而形成早期預警,而不是突然墜落。memory.max是最後一道邊界。如果使用量無法降至該限制以下,資源群組內可能發生局部記憶體耗盡事件並終止任務。
對於產生收入的請求路徑,可設定適度的受保護下限和謹慎選擇的高位邊界。對於批次處理任務,則應設定很少或不設定保護、更早的高位邊界,以及嚴格的最大值。這樣,緊急工作有更大機率繼續執行,而批次處理會先減速。故障範圍也會被限制在局部:工作任務群組可以重新啟動或削減任務,而不會拖垮資料庫或 Web 層。
不要為每個資源群組設定過度保護。若所有受保護下限之和超過機器實際可持續的範圍,「保護」就會變成系統無法解決的衝突。結果可能是其他位置發生高成本回收,並產生虛假的安全感。保護是對資源稀缺時優先級的聲明,不是憑空製造容量的承諾。
建立優先級階梯,而不是平均分配策略
平均份額看起來中立,卻忽略了相依關係的先後順序。沒有可用資料連線的請求服務沒有實際價值;可以無限擴張的快取則可能擠壓兩者。應先梳理相依關係,再從底層開始分配記憶體策略。有狀態服務和請求關鍵元件,應先於可選消費者獲得保護。
下表可協助確定每個工作負載的策略:
| 問題 | 若答案為「是」 | 策略含義 |
|---|---|---|
| 它是否直接影響線上使用者請求? | 延遲會立即造成影響。 | 提供受保護基線,並及早告警。 |
| 這項工作能否安全重試? | 延遲可以接受。 | 設定較低的高位邊界和嚴格上限。 |
| 它是否持有持久狀態? | 復原過程可能複雜。 | 保護其工作集,並測試故障行為。 |
| 它能否在離峰時段執行? | 它在繁忙時段造成了不必要的競爭。 | 在購買更多容量前,先遷移或限速。 |
應謹慎使用「可重新啟動性」這一判斷。只有當佇列語意、冪等性和清理邏輯都經過驗證後,可丟棄的工作任務才能被嚴格限制。若終止任務會遺留鎖、未完成上傳或重複訊息,這並不是真正的優雅降級。
控制並行性,因為單靠記憶體限制並不完整
許多事故源於乘法式並行:更多工作程序、更多連線、更多請求緩衝區,或更多同時執行的任務。單一工作程序的預算可能看似合理,但一旦自動擴展規則或程序管理器同時建立大量程序,總量就會失控。
應將並行限制與每個資源群組的邊界一同設定。限制佇列消費者、連線池、子程序和並行任務數量。讓呼叫方明確感知背壓,而不是允許無限制的程序內佇列。相較於靜默吞噬記憶體直至影響無關流量的長佇列,短佇列與明確拒絕通常更容易維運。
排程任務也需要單獨關注。備份、匯入、報表、掃描和維護操作不應都在同一時刻啟動。應錯開時間、限制並行度,並賦予它們更低優先級。這是在不犧牲功能的前提下降低資源爭搶成本最低的方法之一。
有意識地設計故障行為
硬限制不是效能功能,而是故障範圍邊界。觸達該限制時,資源群組中的程序可能失敗,或被選中終止。對於可從持久輸入重建的工作任務,這可以接受;但對於主要狀態持有者,這可能是災難性的。在正式環境流量替你驗證之前,先自行測試結果。
應為每個資源群組記錄以下預期回應之一:
- 節流後自動復原。
- 在繼續處理現有請求的同時拒絕新任務。
- 從持久化輸入中重新啟動。
- 容錯移轉至獨立元件。
- 升級處理,因為容量不足或程式碼缺陷必須修復。
全域記憶體耗盡保護應始終是最後的保障,而不是策略引擎。當回收無法釋放足夠空間時,作業系統會使用啟發式規則選擇犧牲對象。你可以影響該選擇,但若將其作為常規排程機制,系統會非常脆弱。應優先採用資源群組層級的限制和明確的優先級決策。
透過對抗性測試驗證策略
紙面上看似合理的配置,可能在突發負載下失敗。應在非正式環境中演練幾類不愉快的情況:不斷成長的快取、導致請求並行性上升的緩慢下游相依項、大量分配記憶體的資料任務,以及永不釋放頁面的記憶體洩漏。觀察的不應只是元件是否存活,也應確認關鍵請求延遲是否仍在服務目標範圍內。
每次測試後,檢查事件計數器、壓力訊號、日誌和復原時間。如果低優先級資源群組觸達其高位邊界,這本身就是有價值的回饋。如果它跨越硬限制並損害了關鍵服務,則表示邊界設定或相依模型存在問題。策略變更應足夠小,以便清楚判斷因果關係。
何時不該繼續最佳化,而應擴充容量
資源控制能讓過載更安全,卻無法創造實體容量。當關鍵元件在合理並行性、受限快取和經過驗證的限制條件下,仍反覆承受壓力時,就應升級配置或拆分工作負載。將有狀態服務與突發型應用程式工作分離,往往比持續調校一台已無任何餘量的機器更簡單。
對於營運日本伺服器租用業務的團隊而言,長期目標不應是最大化平均使用率,而應是在資源爭搶時保持可預測的行為:關鍵路徑始終保有足夠的工作記憶體,可選任務優先減速,並且每一次故障都被限制在已知邊界內。這才是正式環境中公平分配的真正含義。
