淺談HTTP介面效能測試指令碼的編寫
作為測試界的老司機,最近接到一項任務需要寫新的效能測試程式碼。由於之前的測試程式碼風格和自己習慣的編碼風格差別實在太大,因此放棄了模仿原來的測試程式碼繼續新增測試用例的想法,自己從頭開始寫了一些測試程式碼。
結果,進行測試過程中居然出現了問題,問題,問題……
問題發現在用自己新寫的測試程式碼測試一個檔案下載介面的時候,發現了一個奇怪的現象,在併發比較小的情況下(50併發),TPS、響應時間什麼的一切看起來都很正常。但是當併發加到100+時,TPS曲線就變得很神奇了,同時還有部分請求失敗,測試結果如圖1所示:
被測系統的架構相對簡單,業務服務前端搭個Nginx,客戶端請求都是把請求傳送到Nginx,然後通過Nginx轉發到業務伺服器。 測試環境架構如圖2所示,被測服務為圖2中的NefsProxy:
接下來就要尋找原因了:
→首先懷疑是不是測試指令碼在每個請求結束後沒有釋放連線。問題的現象是連線數高導致新連線被Nginx拒絕。但是review了程式碼後,發現每次請求結束後都呼叫了:
因此,應該不是測試客戶端沒有主動釋放連線引起的。
→後來想起Nginx配置了將請求強制轉換為長連線。
Nginx的配置如下:
問題好像有點頭緒了。一般配置長連線是為了提高服務端效能,為什麼在我的測試中反而起到了反作用呢?
→接下來就要回過頭來好好看看自己測試程式碼的實現邏輯了。
其中,NosObjectOperation.getObject是java程式碼實現的,每次getObject方法被呼叫時,會new一個HttpClient物件,然後通過HttpClient傳送Http請求。
到這裡,問題的本質慢慢浮出水面:客戶端每傳送一次請求,都會new一個HttpClient,並與Nginx新建一個連線,而Nginx這邊又設定了強制長連線,每個Worker的最大空閒連線數為1024(keepalive
1024)。同時,測試環境的Nginx配置了4個Worker。因此,Nginx最多會保持4096個空閒連線。所以,由於連線數過多,在空閒連線被釋放前,新的連線可能就會被拒絕。
既然問題的原因找到了,該怎麼修改測試指令碼呢?
當然最簡單的就是每個請求處理結束後強制將連線關閉。這樣雖然能解決連線數多的問題,但是也同時間接的讓Nginx強制長連線的配置失效了,達不到長連線提升效能的目的。
因此,我採用了這樣的解決方法:儘量模擬真實的使用者場景,每個測試執行緒使用一個HttpClient物件(也就是在python指令碼的__init__方法裡new一個HttpClient物件,getObject測試方法都呼叫這個物件傳送Http請求,同時java方法nosObjectOperation.getObject也不再每次自己建立新的HttpClient物件,而使用傳入的在Python指令碼方法__init__中建立的HttpClient物件):
Attention:使用grinder進行效能測試時,每次建立測試執行緒時會呼叫__init__方法,也就是說有多少個併發執行緒,就會被呼叫多少次。通過這樣的修改,如果測試時是100個併發執行緒,那測試客戶端和Nginx之間就只會有100個ESTABLISHED的連線。
問題解決了,用新的指令碼跑一次測試,結果如圖3所示,很讓人滿意:
1)做效能測試,測試指令碼的編寫也是一個很重要的環節,只有模擬真實使用者使用情況的指令碼才能跑出真實的、有參考意義的效能測試結果。
2)當效能測試結果與預期不一致時,定位問題時首先要看測試指令碼是否有問題,並對測試環境的各項配置(Nginx、Tomcat等)進行梳理。
3)當使用grinder測試框架進行比較複雜的效能測試,編寫測試指令碼時要弄清楚grinder測試框架的執行機理,各項配置的作用等。