一怒之下,我寫了一個開源流量測試工具

youou發表於2021-09-09

繼一怒之下我寫出了 Vivian(詳見“”)之後。又在等待客戶審批流程的時間裡自己寫了一個流量測試工具。

背景

客戶的站點是透過 Wordpress 搭建的,這個應用放在一臺 EC2 虛擬機器上。奇葩的是,這個應用的 MySQL 資料庫也在這臺虛擬機器上,之前做過一次 RDS 遷移,失敗了,原因未知。看起來這個應用和資料庫就像筷子兄弟一樣,不離不棄,而且沒有辦法透過 AutoScaling Group 進行水平擴充套件。也就是說,所有的東西都在一臺虛擬機器上。

我所要做的,就是把這個架構重新變成可自動水平擴充套件且高可用高效能有快取低消耗具備監控和更加安全且有版本控制並可以透過持續交付流水線來半自動部署的架構。你可以重新讀一下上一句加粗文字的內容。沒錯,目前他們連版本控制都沒有,所有的操作在伺服器上透過 mv 之間 scp 進行。

很不巧的時候,這個“筷子兄弟”應用在上週開始,晚上隨機的 Down 機,表現為資料庫被刪。但透過日誌可以發現,是由於記憶體資源不足導致的 MySQL 資料引擎載入不了導致的。

由於需要做“筷子兄弟”拆分手術,目的是要把資料庫和應用程式分開,並且需要進行一些服務的重啟和拆分。這些操作中會導致停機時間,為了能夠度量這個停機時間,便於做出更好的決策,客戶希望在測試環境上能夠透過模擬生產環境的工作狀態來完成這個任務。我設計了方案,包括以下幾點:

  1. 知道每一個可能引起停機的操作引起停機的時長。

  2. 測試 RDS 能帶來多少的效能提升。

  3. 找出整個架構引起停機的根本問題。

  4. 在 500 個併發使用者訪問的情況下,會出現的效能拐點。

  5. 能夠度量應用的資源損耗。

客戶已經購買了 NewRelic 和 Flood.io (我在 17 期技術雷達裡提交的條目,叉會腰。)但是 Flood.io 的賬號分配需要一個額外的審批才可以使用,也就是說,我得等到第二天才能使用。

我想,也許 github 上會有這樣的工具能夠滿足我這個簡單的需求,搜了一圈,沒有合適的。

於是,一怒之下,我用了大概兩個小時的時間用 Python 編寫了這樣一個測試工具。

工具的設計

There are only two hard things in Computer Science: cache invalidation and naming things.

-- Phil Karlton

命名是一件很困難的事情。於是,為了紀念這個事情,一開始我用提出這個需求的客戶的名字(Dave)來命名它,但可能不太好記憶。所以最後還是用 Wade (Web Application Downtime Estimation)作為這個工具的名字。它很簡單,可以在 找到。

如果我需要知道停機時長,我必須要先能夠持續不斷的發出 http 請求,並記錄下相應 狀態不是 200 OK 的返回。我並不希望應用是一個死迴圈,因此我需要能夠加入時間控制。我期望用下面的這樣的方式來使用:

wade -t 10 -u

其中,-t 代表時間,10 代表持續分鐘,-u 表示要測試的 url。我期望這個工具能夠連續的幫我輸出每次請求的時間和 HTTP 狀態字。

例如:

[2018-07-05 22:30:57]status:200[2018-07-05 22:31:08]status:200[2018-07-05 22:31:15]status:200

這實際上是在構造一個命令列工具的 End-2-End 測試的設計,#我們可以用 BDD 的方式來構造命令列工具的 End-2-End 自動化測試#。這個需求其實很簡單,我大概花了半個小時就完成了。

在實際應用中,你需要先執行這個程式,然後再執行那些可能引起停機的操作。於是我準備讓它執行30分鐘,因為這些操作會執行多久我並不清楚。

很好,經過測試,這些操作只會引起 5 秒左右的停機。

不過,我好像忘了一個重要的事情……

那就是這個應用是單執行緒的,也就是說,這個實際上和真實的場景相差很遠。我需要能夠有 500 個併發 HTTP 請求,於是我把它改造成了多執行緒的。我期望用下面的這樣的方式來使用:

wade -t 10 -n 5 -u

其中,-n 代表執行緒數量。

有了多執行緒,我就需要改變這些應用的輸出。對於多執行緒應用,輸出需要知道每個執行緒的執行情況並且要能夠彙總。因此,我期望應用能夠這樣輸出:

{'Thread': 0, '2XX': 2, '3XX': 0, '4XX': 0, '5XX': 0}
{'Thread': 3, '2XX': 2, '3XX': 0, '4XX': 0, '5XX': 0}
{'Thread': 1, '2XX': 4, '3XX': 0, '4XX': 0, '5XX': 0}
{'Thread': 4, '2XX': 4, '3XX': 0, '4XX': 0, '5XX': 0}
{'Thread': 2, '2XX': 4, '3XX': 0, '4XX': 0, '5XX': 0}

因為,其實3XX 類的返回值在某些情況下也應該算是正確,而 4XX 和 5XX 類的返回值應該分開統計。因此,我改進了一下這個工具。

在改進後我重新測試,我找到了問題的答案:

我成功的把資料庫遷移到了 RDS 上,並在測試環境例項上停止了 MySQL 程式,帶來了 40 倍的效能提升。發現這個應用的資料庫需要最少 10 GB 的記憶體才能正常工作。

當我以 500 個執行緒去持續請求的時候,我把伺服器弄掛了。輸入的響應很慢,且執行命令會返回-bash: fork: Cannot allocate memory 的錯誤。透過減少程式數,我發現一個使用者請求會佔用 110 MB 左右記憶體,要滿足 500 個使用者的併發訪問,主機需要最少 64 GB 的記憶體。

由於併發使用者增長的同時,記憶體也在增長,實體記憶體用完之後會使用 Swap 區的虛擬記憶體空間。當 Swap 區的空間佔滿後,這個時候因為沒有可分配記憶體,所以應用響應奇慢。即便是我終止了測試請求,仍然沒有緩解,我猜之前的請求已經在 HTTP 端排隊,在請求沒有結束或者超時釋放資源,後續的請求會繼續排隊。

那個…… 好像,我對這個伺服器進行了一次 DoS (拒絕服務)攻擊

載入了 NewRelic,我發現這個應用在載入首頁的時候效能是最低的,而大部分的資源都消耗在了 select 查詢上。因此,我判斷其中的表或者資料有問題,會進行大量載入。其次,可以透過給首頁增加頁面快取,或者在資料庫庫端加入快取,來緩解資源佔用。畢竟,首頁的訪問時最頻繁的。

最後,我們可以把 wade 在測試中度量到的資料當做是架構演進中的驗收測試或者冒煙測試,整合在持續部署流水線中,在變更基礎設施或者部署應用之後執行。我們需要非功能的架構級別的自動化測試來保護應用架構的重構。

反思 - 少即是多

如果沒有這個工具,想得到以上的答案。我需要同時在三個服務(AWS CloudWatch, NewRelic, Flood.io)之間來回切換,並且蒐集到需要的資料。那麼多的資料,找到一個簡單直接能反應問題的資料也很困難。而在等待賬號審批的過程中,我就寫下了這個工具。這個工具覆蓋了客戶會關心的基本場景和資料之間的關係。而這三個工具不能同時都滿足(其實NewRelic 其實就差一點點)。雖然每個工具在各自領域 和所面對的客戶都是非常強大的工具,而一個真實客戶需求的場景 - 找到在正常壓力下影響停機時間的因素 - 卻很難被滿足。

所以,對於非目標的使用者和使用場景,產品豐富的功能和資料有可能是需要被過濾的噪音。一個產品所要面對的使用者場景越多樣,它所引入的噪音就會越多。而更多的增值服務和高價值服務,則被淹沒在了這樣的噪音裡。

支付寶和銀行的手機端應用就有這樣的問題,什麼都做的事情,一般什麼都做不好。

最後

作為一個兩個小時之內完成的工具,wade 缺乏各種自動化測試。但是,從 wade 設計過程我們可以看出,雖然我沒有寫自動化測試,但是設定期望並完成期望的結果是一致的。從這個角度上講,TDD 也是把大腦中對程式的設計過程記載下來的一個活動

如果對這個工具感興趣,歡迎 PR:



作者:顧宇
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4729/viewspace-2804198/,如需轉載,請註明出處,否則將追究法律責任。

相關文章