性能測試是一個入門簡單,但是精通難,很依賴實踐經驗的技術活。如何編寫壓測腳本隻是小術,而如何快速找到問題的原因,壓出瓶頸卻是大有學問。不過本文先從術入手,先對一個自己臨時寫的的一個網站進行壓測,希望能幫大家更好了解性能測試産品,特别是腳本編寫的部分。
開始壓測第一件事情絕對不是直接動手就寫壓測腳本。一個規範的性能測試需要包括需求調研、測試準備、執行壓測、生成壓測結果并做彙總幾個部分。這些步驟都有其存在的意義,保證我們壓測不會跑偏,這裡針對具體的case我們分析下(注:本文涉及的機器會在本文釋出前釋放,相關請求位址不再可用,大家就不要壓文中的位址了)。
這一步我們需要先知道自己要壓的系統的情況。需要根據實際的項目情況進行需求調研。
這是一個很簡單的測試系統,功能上涉及的主要是首頁浏覽、一個登入功能和一個登入後的一個簡易下單操作。
ecs上安裝tomcat,部署的一個簡單java應用。其中登入需要用賬号密碼去查詢資料庫的使用者表,目前表裡就初始化了一個admin/123作為登入賬号。購買頁面的下單操作也會往資料庫裡寫一條記錄。這裡隻用了一台ecs,沒有使用負載均衡。總體而言,是一個簡單的一台ecs+一個rds的應用。
這次壓測沒有分生産系統和測試環境。不過在實際場景裡,需要注明生産環境和測試的環境的差別,并在壓測的過程中加以注意。
目前系統裡隻有少量幾條測試資料,是以資料庫查詢的話,理論上不會有資料庫慢查詢(實際上這次也就壓測涉及的資料庫查詢隻有登入的時候會查使用者表,而使用者表目前隻有一條記錄)。而關于寫入,目前沒有在表上做索引。實際工作中,不僅需要考慮到系統的目前資料量,還需要顧及未來2-3年的資料量情況,以免以後資料量增加的時候負載跟不上。
硬體準備是否充分。這裡可以先評估的是峰值的網絡帶寬。cpu、記憶體主要是需要根據壓測的結果進行評估,但是帶寬可以根據預先估算的tps乘以每個請求涉及的檔案的大小來估算。我這裡是壓瓶頸,回頭看下瓶頸是不是在帶寬上。
主要涉及網站預期的性能名額,比如tps、響應時間、成功率、壓測過程中的涉及的ecs/rds的負載。我這裡就想看看它能“走多遠”,先不設定tps的名額。但是響應時間,我希望首頁、登入、下單的響應時間能在2秒内,請求成功率在99.9%,在壓測的過程中ecs的各項名額低于80%,資料庫的資源使用率低于60%。如前面提到的,設定名額的時候最好能考慮到未來2-3年的情況,至少要考慮到近期的峰值(比如接下來是否會有大促)的性能要求。
涉及首頁、登入、下單3個頁面。
首頁包括1個html1個css1個圖檔。
登入頁面通過post請求送出。如果賬号密碼錯會302回到登入頁面。如果是登入成功,會跳到成功頁面提示處理成功。為聯機操作。
下單頁面也一樣通過post請求送出目前購買的商品和數量。伺服器會判斷目前session裡的使用者資訊,如果取不到判斷為沒登入狀态,會302跳到登入界面。下單的邏輯很簡單,沒有對庫存做校驗,隻是增加一條記錄。也是聯機操作。
本系統不涉及跑批作業,也不涉及其他外部系統。
我也沒編出來: ) 不過大家實際使用中,需要注意使用者的行為方式,比如服務對象,他們的使用均值、高峰如何,一般都是如何使用系統的。這對于腳本編寫邏輯和壓測的目标的設定有非常重要的參考意義。
測試準備主要包括測試環境的配置、測試内容的梳理和測試政策的設定。
本例子沒有分測試環境/線上環境,直接就開始壓了。真實的壓測例子裡,需要記錄生産環境和測試環境在系統架構圖、部署圖、硬體配置、軟體環境,并分析其差異。這裡我就例子裡的被壓系統做下記錄:
系統架構圖用的是serverlet+jdbc直接連mysql,沒有連接配接池,或者諸如ssh、ibatis等常用架構。因為太簡單這裡就不畫圖了。
部署情況為一台ecs上安裝了tomcat 8,然後直接拷上war包完事。mysql的資料用的是之前調試代碼裡就創好的表,沒有走平時的上線流程之類的。
硬體配置為:
ecs的配置為華東1區域的2核4g i/o優化執行個體,使用作業系統為centos 6.8 64位。公網帶寬購買時設定為5mbps(峰值)。
rds的配置為1核2g通用型mysql 5.6。能達到的最大iops為1000,最大連接配接數為600。
軟體環境上為java 8,tomcat沒有調jvm參數,沒有調過其他參數。
本例子先隻涉及單交易負載測試,基準測試、混合場景下的測試先暫時不考慮。需要壓測的頁面為:
子產品
涉及的請求
參數
前提條件
首頁
<a href="http://120.55.240.49:8080/demo/">http://120.55.240.49:8080/demo/</a>
無
登入
<a href="http://120.55.240.49:8080/demo/login.jsp">http://120.55.240.49:8080/demo/login.jsp</a>
username=admin&password=123
下單
<a href="http://120.55.240.49:8080/demo/buy.jsp">http://120.55.240.49:8080/demo/buy.jsp</a>
goods=g02&count=1
我們會先調試通過後,先用1-5個并發保證壓測能跑起來,然後逐漸調整并發使用者數,每次調整後停留至少30秒觀察伺服器的負載和資料庫的負載,以及諸如tps、響應時間的性能名額。在觀察到伺服器的tps達到瓶頸或者負載達到上限後停止壓測,認為服務的處理能力已經達到。整個壓力過程中的并發數是人為根據當時的情況動态調整的。這裡不要起來就是幾千一萬的壓力去壓,否則一般情況下,除了把伺服器壓挂掉外别的什麼都說明不了。
關于監控模型,我們配置ecs、rds為監控對象。不過因為性能測試的監控資料有延遲,ecs為1分鐘,rds為5分鐘,是以在壓測的過程中,會登入到ecs上,使用top指令來觀察更加實時的ecs負載,并登入到rds的dms上使用實時性能功能觀察rds的負載。
首頁是一個簡單的靜态頁面,這裡主要是展示一下如何使用性能測試産品提供的腳本錄制工具的使用方法。
産生的腳本為(第一次建議先隻看注釋不看代碼,就是#之後的)
可以看到函數裡需要注意的是<code>def __init__(self)</code>做初始化,這裡暫時不涉及,後面會提到。初始化後壓測服務會多次調用<code>def __call__(self)</code>。最後調用一次<code>__del__(self)</code>收尾。
在儲存按鈕邊上有個調試按鈕,點選後可以看到調試的結果。
在腳本調試的過程中,每個請求的内容,響應内容一目了然。執行日志裡還有提供壓測的過程中的日志。如果中間有自己列印了一些日志,也可以在這裡看到。關于日志列印的功能後面也會實踐裡提到。
儲存了腳本後,去建立一個壓測場景:
把這個場景運作起來。看到并發很低,從性能測試産品上可以看到性能參數圖:
同時對比一下ecs的負載名額:
看到ecs的cpu根本沒用掉。通過top指令看到的cpu、記憶體的使用情況也是如此。同時我還用<code>iftop -i eth1</code>看了下公網網卡的流量情況,和監控上看到的一樣,公網帶寬被打滿了
從壓測結果可以看到,瓶頸在公網帶寬上。因為ecs購買的公網帶寬比較小,而首頁的靜态檔案比較大(圖檔比較大),可以考慮在夠用的情況下減少圖檔的分辨率減少圖檔的大小。另外可以做到動靜分離,一些靜态檔案就放到對象存儲oss上面,再配合cdn就完美了。壓測的時候也就不需要在壓測這些已經放在oss/cdn的檔案。
同樣的登入功能也是腳本錄制出來的。這裡就不重複說明。因為後面的登入後下單的這個例子包括登入的所有功能點,這裡登入就先跳過。
下單是本次測試的最複雜的一個子產品。首先,下單前需要登入,但是我們這次隻是為了測試下單的工單,所有所有的請求,我們希望隻登入一次(實際上如果是用了多台施壓機,腳本裡寫一次登入,實際上是每台機器一次,一共登入會被執行多次)。根據前面講的腳本的組成邏輯,我們需要把登入寫在<code>def __init__(self)</code>裡。除了登入,我們還希望測試每次下單購買的是不同的商品和數量,這時候需要用到參數檔案。另外因為我們這次是希望壓測下單的過程中ecs和資料庫的壓力,對于之前的瓶頸公網帶寬,我們假設已經通過動靜分離解決了,是以在這裡壓測腳本裡我們不涉及靜态資源,走内網壓測。還有我們希望在這個例子裡對腳本代碼做一次調試,是以需要做一些日志列印。
登入功能可以在錄制的時候直接錄制好:
先輕按兩下上面的初始化,然後錄制登入功能。
登入錄制好了後,輕按兩下事物然後開始錄制下單頁面。
最後得到的壓測腳本如下:
分析下這個腳本,特别注意<code>__init__(self)</code>裡調用了<code>self.init1()</code>,做了登入,然後做了cookies的設定。其他地方同前一個腳本基本一樣。
調試一下,結果還可以。
檢查點用于檢查請求的傳回内容是否符合預期。有的時候,我們針對失敗不會直接報3xx甚至是4xx或者5xx,而是傳回200,但是響應的内容裡提示報錯資訊。隻有傳回的内容是200而且内容是success!我們才認為這個下單操作是成功的。于是我們修改action1,增加檢查點的功能,修改為
我們修改腳本界面,把所有的公網ip換成内網ip。以前版本的pts還是需要設定#innerip:,但是現在看來不設定也是沒有問題了。
另外在壓測腳本上,需要把壓測模式選擇為内網壓測。
如果我們先用模闆模式寫一個簡單的帶參數的壓測腳本,然後切換到腳本模式,可以發現和沒有參數化比,主要改了
1 參數化相關引用
2 <code>__call__(self)</code>裡調用<code>params.nextrecord(u'xx.csv')</code>使得參數檔案進入下一行
3 用<code>params.getparamvalue(u'xx.csv:uid')</code>等對參數進行替換
我們寫了個order.csv檔案,内容如下:
然後針對前面提到的3處修改點,修改了我們的腳本,這樣每次請求都會到參數檔案裡擷取不同的參數來發請求l。再把參數檔案上傳上來,最後調試截圖:
可以看到
為了驗證日志列印功能,我們可以在腳本裡列印一些日志。最後算上前面提到的參數等功能,最後的完整腳本為:
大家可以對比一下這個版本和上個版本的差別,就可以很清楚的知道這兩個功能的用法。
還是和以前一樣的壓,并發先建立一個場景,這次設定施壓機為3台,這樣可以3台3台地增加并發數。然後從3台開始,再改成30,再到300,最後到600停住,看到延遲已經超過預期了。需要注意的是,一開始是因為響應時間超過預期才停止增加并發數的,但網站不能服務是600并發保持了約四分鐘後發生的。
先截個壓測場景的圖,因為是内網壓測,注意施壓機所在叢集和ecs要一樣。
然後看業務名額。首先tps基本沒變化。到後來服務運作了一段時間後挂了,tps就掉到0了。
随着并發的增加,響應時間增加了。但是因為後來服務不可用後響應時間變得太長了導緻前面的響應增加看不出來了。
我用另外一次在出現問題就停手的壓測記錄截圖來看大家會清楚一些,這裡的響應時間的幾個波動分别是3->30->300->600帶來的。
并發是是3->30->300->600
接下來是ecs的負載
然後是rds的
因為rds的監控是5分鐘1次,我壓測的時間不長截出來圖并不好看。不過可以看到,資料庫的負載不高
這次是比較典型的把伺服器壓挂了的例子,我們登入到伺服器上,用top指令看到cpu已經滿了。
把11941這個pid用<code>jstack 11941 > ~/11941.dump</code>抓個現場,打開看下。裡面茫茫多的
雖然從mysql上不論是監控還是執行個體診斷報告上看都很正常,但是從這裡可以判斷是連接配接資料庫寫入訂單資料的步驟出現問題。出現問題的是通過buy裡使用synchronized申請com.mysql.jdbc.jdbc4connection對象鎖未成功。到這裡再聯系前面的資料庫連接配接數一共就2個(其中有1個還是dms用掉的,其實隻用掉了一個),終于枉然大悟:之前做demo的時候圖友善,資料庫的連接配接是使用單執行個體模式做的,全部的請求用的是同一個資料庫連接配接。回頭可以考慮用資料庫連接配接池配一個應該可以提高應用的性能。