第一次壓力測試就上手!街口購物節,不壓嗎?
壓測了啦!哪次不壓?
Author
- Peter Liang
主要負責支付系統領域,認為「品質從需求開始、靠開發規範、由測試保證,每一環節都不容疏忽」。
2021 年「1111 街口購物節」活動,街口支付刷新歷年來的流量巔峰!
但,水能載舟亦能覆舟,在慶祝超過 500 萬名註冊用戶的同時,面對如此龐大的流量也需時刻戒慎恐懼,汲取每次的實務經驗以面對接踵而來的流量考驗。而如何評估是否能通過流量考驗,「壓力測試」則會是一把利器 — — 藉由歷史數據、系統拓墣等評估條件所撰寫而成的壓力測試計畫,再搭配合適的壓力測試工具,並仔細分析產出的壓測報告,勢必能提前揭露系統潛在的風險以規避後續所引發的效能問題。
壓力測試計畫的產出
在執行壓力測試之前,需要先行規劃縝密的壓測計畫,像是準備測試環境、探勘歷史數據、規劃測試情境及目標等。與其盲目地執行壓力測試後再抽絲剝繭地解決問題,不如先評估、設計好相對完整的測試計畫再來執行,以提高壓力測試的精確度,避免造成各方資源的耗費。
歷史數據探勘
首先,街口會確認在過往的活動或業務中,是否有類似的數據能夠參考;而在活動設計時,由行銷、專案經理、數據分析等各方統籌出來的觸及數據與預測流量,則會成為此次壓測計畫的目標壓測指標。像是街口每年的購物節活動都會參考前一年的活動數據,再加入當前用戶數量、業務內容、系統流程變更等差異,來思考今年的業務指標、壓測目標的落點。
以「整點搶券」的活動為例,2020 年街口的使用者為 300 萬,當時 RPS 峰值來到 2,600;但 2021 年街口的使用人數已突破 500 萬,此時我們就該思考這樣的成長對於 RPS 峰值所造成的影響。
再者,因應技術專案,許多服務可能會調整,舉凡服務架構的重構、SQL 語法與資料表索引的優化、加入 redis cache 機制、服務容器規格調整等,這些調整都會大大影響歷史數據的評估,都得一併納入考量。
舉例而言,於 2021 年 11 月 11 日凌晨 0 點,街口舉辦「限量折價券搶付」的活動,使用者在活動高峰期透過電商付款頁面選擇「街口支付」進行付款,跳轉進入街口 App 的付款頁面到按下「確認支付」按鈕完成支付。在層層系統的調整後,需先於測試環境反復驗證網路層、資料庫、各服務的流量與機器表現是否有產生瓶頸的可能性,排除真實活動開跑時的潛在隱患,讓活動在高峰期亦能順暢的進行。
系統拓墣分析
由於壓力測試大多是透過模擬真實用戶,平行或接續地執行 HTTP Request 來對服務形成負載。故需要了解實際用戶端從街口 App 發出請求,一直經過整體服務間交互的鏈路究竟為何,包含但不限於:
- 網路層設置(e.g. 防火牆、DNS)
- 服務 Application Load Balancer 機制
- 線上服務實際運行的實體機、虛擬機、Container 規格
- Database Server 規格、I/O 上限瓶頸
- 服務之間的依賴關係
- …
不同服務所經過鏈路也不盡相同,所謂「知己知彼,百戰不殆」,唯有知道系統拓墣的配置,才能在探勘出問題的當下精準定位,讓對應負責人迅速進行修復,以進行下一輪的測試。
當然,也可透過搭建 Apache SkyWalking、Zipkin 等 Application Performance Monitor,更方便地動態追蹤壓測腳本執行當下的服務鏈路狀況。
壓測情境設計
以前面提過的「整點搶券」案例進行延伸,活動於整點開放搶限量的電商折價街口券,預期在活動開始前一小時陸續針對用戶進行推播行銷。當使用者點擊推播訊息後,會直接開啟街口 App 內的活動網頁,並引導用戶點擊「領取」按鈕進行搶券,搶到券後需要在指定時間內進行消費使用。依據整個使用情境,情境設計的思考脈絡如下:
- 推播的目標客群為何?假設同時有已經參加活動、未參加活動兩種不同的用戶類型,不同目標客群的流量與進入活動網頁的鏈路對於系統負載可能有差別。
- 搶券活動開始前一小時 / 活動當下 / 活動結束後十分鐘,這些時段的流量情境可能不盡相同;在訂定測試計畫前也可以依此來設計不同的測試策略。像是活動開始前一小時,流量可能因為分批推播的方式而呈現階梯式上升;而活動當下對於「搶街口券」相關服務的流量應呈現爆量增長;活動結束後十分鐘,此時折價券已被搶完,因此預期服務應逐漸消化流量並回到原本日常活動的量級。此時,則會轉為關注「使用街口券」相關折抵、支付的服務是否能正常消化該活動所導入的流量。
- 在整個活動期間,不能影響街口其他的基礎服務!因此,我們可以透過實時監控其他服務的相關數據,確保不會有顧此失彼的情況發生。
- 若測試後發現前段鏈路的量級爆增,於活動當下,我們可藉由減少其他服務流入前端的流量,以確保活動的主要鏈路與基礎核心服務的穩固性。e.g. 於流量高峰期,停止推播、停止部分導流至 App 之行銷入口以減緩非預期的流量所造成的不佳體驗。
此外,若要模擬實際線上服務的複雜狀況,需盡量減少因情境不同、機器規格不同等造成的 bias,以避免輸出無價值的壓測結果;因此事前的線上服務 log 蒐集、測試資料建立等尤為重要;而執行計畫前,也應在測試環境中建置被壓測服務的實時監控,以評估壓測執行結果,並提供數據與 PM/RD/IT 討論是否應調整服務、機器設置等,以符合活動所需的業務指標。
壓力測試目標
壓力測試的目標會是壓力測試最為重要的部分,可以是若要達成一定業務或服務指標,該做怎麼樣的調整;也可以是評估以公司現有資源狀態,能達到最佳化服務的水準,可能的壓測目標如下:
- 在現有規格下,被壓測的 Server 的 CPU usage 達到最佳使用率(依經驗約為 60 %),在當下服務所能支撐的 RPS 量級為何?
- 當壓測 RPS 量級為 10,000 時,服務本身是否能消化此量級?(遠端 Host TCP/HTTP/SOCKET 連線數、機器表現狀況等)
- 當壓測 RPS 量級為 10,000 時,若要求服務 API 的回應時間需達到 99.5% 皆於 300 ms 內回覆之情境,該做何種調整?
在確立壓力測試的目標之後,後續調整的順序如下:
- 優化程式寫法
在評估壓力測試結果未達標的原因後,「優化程式」為最簡單直接的方式。如對資料庫的 SQL 語法效率較差,導致瞬間大流量引入時易引起資料庫鎖表,導致整個服務請求 queue 住,這部分能透由壓測結果 & 服務監控得知。 - 調整網路層/服務 & 資料庫機器設置
若程式端已無可改善部分,與各方討論後,退而求其次可採取調整網路層設置,或是升級服務及資料庫的機器規格。但隨之而來需思考的議題則是是否有資源能支持機器成本的增加。 - 「排隊」方案
沒錯!就是購物節或疫苗平台常會看到的重整或排隊頁面,採取些排隊的操作來維持服務的穩定也是一種方案,但這樣的操作對於使用者體驗也是種無形消耗需盡可能的避免。 - 「棄車保帥」方案
先確定特定時段所要提供的主要服務為何,若發現主要提供的服務已被流量影響,適時地調整或捨棄次要功能的體驗 — — 棄車保帥,調整不必要的資訊顯示或停用長查詢的部份功能,以確保該活動的主要鏈路與基礎核心服務,在流量高峰期仍能如期提供正常服務。
如此一來,完整的壓力測試計畫應能如期產出,剩下關於時程排入、資源溝通等,各有各的作法,則不在此贅述。
壓測工具的選用、使用結果與分享
俗話說:「工欲善其事,必先利其器」,壓力測試範疇內也有許多好用的測試工具;而街口測試團隊目前使用的工具有以 Python 撰寫的 locust 與以 JavaScript 為腳本撰寫語言的 k6。
在之前的使用經驗中,街口測試團隊之所以使用 locust 作為壓力測試工具,首要是因為目前所使用的自動化測試框架 — pytest,同樣是以 Python 撰寫,且許多因應自動化測試撰寫的工具、設置及測資等皆可復用,團隊上手相對快,也因 scriptable 的特性,讓團隊能更自由的調整腳本。
此外,locust 本身的測試報告能以 web-based UI 呈現,能在測試期間迅速瀏覽整體狀況;也能利用 locust 的 distributed load generation 功能,設置 master 及 worker 關係,讓多台機器能同一時間執行同一腳本,以模擬大量請求的情境。
然而,locust 雖簡易上手,功能也算齊全;但應付瞬時尖峰流量(e.g. spike test 情境)及複雜情境時仍有些吃力,某部分原因在於沒有對 multithreading 進行實作,也不可能每次都以 distributed load generation 的方式執行測試腳本,整體投入的測試成本過高;因此在這次 「1111 街口購物節」的壓力測試計畫中,考慮使用其他壓力測試工具來進行壓測。
而 k6 作為此次「1111 街口購物節」的壓測工具,主要考量有以下優勢:
安裝過程快速
安裝過程迅速簡潔:mac 或 linux 使用者可利用 Homebrew 執行brew install k6
命令即可安裝,且安裝 k6 是不需要先安裝 nodeJS 或任何其他外部 dependencies。
需要最大化性能和效率
- k6 是用 Go 撰寫的,而 Go 是為提高性能而構建的;在 k6 中,每個虛擬用戶都在 goroutine 而不是線程上運行;goroutines 可以由 Go Scheduler 控制,通過「work stealing」和線程之間的工作交接,重用 idle 的線程並自動地分配工作,與構建 Load Balancer 的原理相同,監督工作流程的外部監視器可提高總體績效;Go 在本質上以許多編程語言都沒有的方式實現了負載平衡。
- k6 在處理硬體資源的方式上與許多其他負載測試工具不同:單個 k6 process 將有效地使用負載生成器計算機上的所有 CPU,單個 k6 instance 足以產生 30,000 到 40,000 個虛擬用戶(VUs)同時運行負載測試,此數量的 VU 每秒最多可生成 30 萬個請求(RPS)。
想進行特定目標的檢驗
k6 通過全局閾值(Global Thresholds)來實現了這一點,可以創建自己的指標以在閾值中使用:
如上例,加入了failedRate
這項自定義的測試指標,加入最後的壓測結果呈現中,以觀測特定 HTTP status code 結果。
以下會介紹下街口使用 k6 工具的心得及範例:
k6 Test Schema
.
├── configurations
├── testdata
│ ├── testdata.csv
│ ├── testdata.json
│ └── ...
└── tests
├── {k6_scripts}.js
└── ...
- configurations: 放置 Database Setting、Token、加解密方法等相關設置
- testdata:放置測試資料
- tests:放置各項 k6 performance testing scripts
Test Data Processing
這段 code 用來處理將 testdata.csv 內測試資料作為參數引入,方便引入特定參數 & 篩選特定測試資料;而使用 csv 檔案作為測資導入的媒介,主要是街口測試團隊本身的 API 自動化測試所使用的測試資料,皆是以 csv 檔案做更改、記錄,這樣一來可以復用原本的資料,減少開發、測資生成的成本。
Scenarios
k6 支援許多 Scenarios 的設計,這邊可以看到有使用 __ENV
作為參數傳入,便可在執行 shell scripts 時作為環境參數引入,如:k6 run — env SCENARIOS=claimRequestPerVUIter test.js
,方便用於日後若有導入 Jenkins、自製測試平台等作為壓力測試觸發機制時,能在同一份測試腳本內導入不同測試情境。
以購物節專案為例,街口會需要同時引入 spike test、load test 等不同壓測模型,且會根據前述分析的模擬現實流量狀況,同時或延遲執行各項壓測情境 — — enabledScenarios
:這項設置可在預先撰寫好壓測模型的情況下,不需要更動到 scripts 便能組合特定情境,並執行每一次的壓測腳本。
Test Result
這部分會是執行 k6 腳本後預設輸出的指摽,其中也會包含先前自定義的 index;而結果方面,可以關注以下指標:
http_req_duration
:這項指標是http_req_sending + http_req_waiting + http_req_receiving
的總和;通常會審視這個指標來判別於服務壓測腳本執行下,是否能落在預期處理的時間內,e.g. p(95) ≤ 300 ms。http_req_connection
:Time spent establishing TCP connection to the remote host.;若這時間莫名地很長,很可能是 API 處理過程中,在流量提升的狀況下遭遇 blocked,此時可輔以 APM 或服務相關 log 審視當下發生在哪一環節並予以解決。http_req_blocked
:等待遠端 host 有 free TCP connection slot 所需時間;這時間和上述http_req_connection
可能有關,若處理時間長導致無多餘 slot 被釋出,或是連線機制設置關係,都有可能造成此時間變很長,導致從 client 端發出之 requests 遲遲無法被處理(k6 預設 TO為 30 secs),此時也需搭配 APM 找出瓶頸所在。http_reqs
、iterations
、vus
:檢視這次壓測行為共發出的 HTTP requests 數、執行 function 次數以及產生之虛擬用戶數;可配合視覺化工具或自定義 index ,審視壓測標的是否達標,e.g. max RPS = 10,000
視覺化呈現方式
視覺化呈現採取業界常見的組合:Influxdb + Grafana,達到壓測過程中資料的時序性資料記錄,以及透過視覺化呈現方式,製作出能實時監控的 dashboard;shell scripts 如下:
k6 run --out influxdb=http://localhost:8086/myk6db test.js
在 「1111 街口購物節」專案中,街口測試團隊產出縝密且完整的壓力測試計畫,其中包含歷史數據探勘、系統拓墣分析、壓測情境設計及壓力測試目標,以及說明為何選用 k6 作為這次執行和撰寫壓測腳本的工具;這整套方案著實有效的揭露出系統在流量高峰時不同層級的缺陷,且藉由各部門的協助,才有辦法有效解決壓力測試過程中所遭遇的問題。透過這次的壓力測試,在購物節期間街口的服務支持著比平日多 30 倍的流量、特定時段湧入近 20 萬人在線搶券,並在雙 11 當日順利完成 105 萬筆的交易訂單,為街口支付的歷史締造了一個全新的紀錄!
街口購物節人數成長2倍 逾5千人低於85折買到iPhone 13(聯合新聞網)
2021街口購物節 參與人數大增(工商時報)
JKOPay SDET Team
2022.04.23