2026 年初,我從軟體大廠的資深工程師職位離職,加入新創擔任技術經理。對於技術、管理還矇矇懂懂,希望藉由這個系列的記錄,在多年後回頭檢視自己的成長。
在上一篇中,我們統一了各個環境的部署方式,讓後續改動與測試更有信心。總算,可以回頭看過去系統遇到流量峰值時的問題。
這個系統是建立在 AKS K8s 的群集上。過去,在系統接獲瞬間流量時,由於自動擴展的機制遲遲沒有作用,導致客戶抱怨等待時間過長。
首先,我打算先了解,所謂的「系統高峰」是多少使用者同時對系統發出請求?當前自動擴展機制預期達到的效果是什麼?預期系統高峰處理的時間又是多長?
當前的需求下,瞬間系統高峰約在 100-200 位使用者,同時進行請求。我們的系統中有 conumser pods 在負責消化這些請求,並且在處理之前,是透過一個叫做 hot standby job 的機制進行自動擴展。最後,因為一個請求最終端到段的處理時間,預期是六分鐘,最佳的情況下,我們希望使用者的預期等待時間都在這個區間內。
200 個需求其實不算多,既然有自動擴展機制,且自動擴展的 replicaset 最大是 200,理當能順利處理。那,究竟是為什麼還會有延遲?
於是,我實際進行測試,透過 k6 這個工具做高併發的請求,來模擬使用者的使用情境。殊不知,尚未測到 200 人,光是一半的 100 人總執行時間就到了五十分鐘。除此之外,單一 request 處理的速度也隨著使用者增加,而跟著提升。
很明顯的,系統之中有 bottleneck。
但在處理系統的 bottleneck 之前,我發現一個更嚴重的問題:自動擴展機制並沒有如預期的擴展 consumer。儘管流量提高,而且 K8s 資源還足夠的情況下,擴展的 consumer 也只有十來個上下。可想而知,在工人沒有增加的情境,自然工作的處理效率低下也是合情合理。
既然如此,我們就必須回頭看剛剛提到的 hot standby job 到底是怎麼進行擴展的。這個機制是透過一個類似 monitor 的工人,定時的去監控當前 consumer 的狀態,今天只要 consumer 處於「工作狀態」,monitor 就會去計算我們尚需要的工人數量,來去呼叫 K8s API,增加 consumer 數量。
乍聽之下,這個機制相當合理,但卻發生了幾個斷點:首先,consumer 會在狀態轉換之際,將狀態儲存到 redis 中。monitor 工人再透過讀取 redis 中的狀態,來更新擴展條件。兩者更新的頻率是不一致的,導致一來一往間產生了時間差。其二,原先的實作中,consumer 狀態轉換的情境考慮的不完整,導致儘管請求已經處理結束,狀態還遺留在 redis 中。
我們會發現,這其實是一個相當複雜的設計。問題是,我們的需求真的需要這麼複雜的設計嗎?
K8s 中常見的,會透過 HPA,或是 KEDA 的做法來自動擴展。我們的需求 HPA 或是 KEDA 沒辦法滿足,需要自己造一個 hot standby job 的機制嗎?
於是,我請教了工程師,過去之所以這樣設計的原因。因為被客戶抱怨處理時間過長,所以這個機制想要維持數個「已經暖機完畢」的 consumer,當任務來時,這些 consumer 不必等待 image 下載與設定的時間,可以直接開始接任務工作。
後續我用數據證明,這段時間其實只佔了 200 毫秒左右,跟本無傷大雅。所以,做決策時有數據輔助是相當重要的。
既然系統的設計,無法解決我們的需求,那我們應該要用一個更好維護,且更穩定的工具來進行自動擴展。權衡之下,KEDA 可以做到 Event-Driven 的擴展機制,比起 HPA 更能達到我們監控 rabbitmq 訊息隊列長度的需求。最後,我們選擇改用 KEDA 來進行自動擴展的方案。
果然,改了 KEDA 後,整套系統變得更加穩定且易於後續追蹤外,高峰流量的處理時間,也從原先的將近一個小時,降低到了 20 分鐘以內!整整進步了約 30%,雖然離目標時間還有差距,但第一自動擴展的 consumer 數量達到了預期,第二系統變得簡單穩定。算是成功的邁出了第一步!