擴展的觸發與縮回
擴展的觸發與縮回,一個決定何時加實例、一個決定何時減實例,兩者的難度不對稱。擴容相對簡單——加一台機器、等它就緒、開始分流。縮回難得多,因為減一台實例前,要先把它身上的流量與在途工作安全移走,硬砍會中斷正在處理的請求。這一章講擴縮這個閉環,重點放在縮回這個常被忽略、也更容易出事的方向。什麼訊號代表系統飽和、該不該擴,是 規模拐點判斷 的主題,這裡承接的是「確認要擴之後,這個擴縮怎麼運作」。
擴展前先窮盡低成本手段
加實例是有成本的手段,不該是遇到壓力的第一反應。一個健康的擴展決策,會先窮盡零成本或低成本的手段,確認真的不夠了才擴機。以讀取變慢為例,加副本或加機器之前,先做過補索引、讓儀表板改讀預聚合的摘要表、把降採樣 job 調到避開高峰時段——這些預聚合類的手段常常是「加機器前」最有效的降載,做完可能就不需要擴了。擴展的觸發原則是按觀察到的真實瓶頸行動、不按預測搶跑,且在擴機之前先把便宜的優化用盡。
觸發訊號分層,各層對應不同動作
擴展的觸發不是單一訊號、單一動作,成熟的系統分層應對。本站 collector 的 ingestion 就分四層防線,每層有各自的觸發條件與動作:源頭的 SDK 在超過粒度時自動降取樣、單機的背壓與限流在寫入接近滿載時擋、水平擴展在單機 CPU 或連線飽和時加實例、佇列解耦在突發流量超過整個 collector 群的即時處理能力時插入緩衝。訊號從源頭到基礎設施逐層升級,先用便宜的層擋,擋不住才動到貴的層。
共享儲存的擴展也有量化的觸發訊號。collector 的 SQLite 後端撞到「database is locked 每分鐘出現一次以上」或「聚合查詢超過 3 秒」,就是該換 PostgreSQL 的訊號;PostgreSQL 撐到每秒數萬筆持續寫入或需要自動降採樣,就是該換時間序列資料庫的訊號。這些訊號的共通原則還是那條:按觀察到的瓶頸切換,不按預測提前重構。
縮回要先 drain,不能硬砍
縮回的核心難點是實例身上還有活。減一台實例前,要走跟關閉服務同一套收束流程:先把它從負載平衡的目標裡摘掉(停止送新流量)、等它手上的在途請求處理完、再真正終止。這跟 模組四 graceful shutdown 是同一套機制——縮容其實就是一次有計畫的實例退場,退場的固定順序(摘流量、drain、終止)在那裡展開。硬砍一台正在處理請求的實例,那些請求全部中斷,用戶端看到的就是一批莫名其妙的失敗。
縮回還有兩個容易出事的地方。一是縮太快造成容量不足——流量剛回落就急著縮,結果下一波又上來、新實例還沒起好。二是縮縮擴擴的抖動,訊號在門檻上下跳、實例反覆增減,這靠冷卻時間(cool-down)壓住:擴或縮之後強制等一段時間再判斷,不讓它對每個瞬間波動都反應。擴縮該選哪個訊號、冷卻時間怎麼配,在 規模拐點判斷 的擴縮訊號段有完整對照。
有一種源頭的縮回不靠減實例,而靠降載。collector 的背壓 buffer 滿了就回 429 加一個 Retry-After,SDK 收到 429 自動把取樣率從 1.0 降到 0.5、再降到 0.1;等連續成功幾十次,再逐步回升到 1.0。這是一個閉環的自動降載與恢復——不加機器、直接在源頭把進來的量壓下去,撐過尖峰再放回來。對短暫的高峰,這種源頭降載比擴實例划算得多。
該擴、還是該解耦
擴展到某個點會遇到一個判斷:繼續加實例、還是改變架構。當 collector 群已經水平擴展、仍無法即時消化突發流量時,繼續加實例的邊際效益在下降,這時該考慮插入一個佇列解耦——collector 簡化成「接收、驗證、寫進佇列、回 202」,後面的 worker 按自己的速度消化積壓,把「即時處理」換成「保證不丟、慢慢處理」。但這個決策有反向的一面:如果只是短暫的高峰,佇列的維護成本可能高於它的收益,這時回到源頭用動態取樣降量更划算。佇列解耦的完整設計在 模組七 突發流量 展開,這裡的判斷點是:加實例、源頭降載、佇列解耦是三個不同成本的選項,按尖峰是短暫還是持續來選。
下一步路由
- 什麼訊號代表系統進入飽和、該擴容 → 規模拐點判斷
- 縮回的實例退場順序、drain 怎麼做 → 模組四 Graceful shutdown
- 佇列解耦怎麼接、突發流量的完整應對 → 模組七 突發流量
- 垂直還是水平——擴的方向怎麼選 → 垂直與水平擴展的判斷