晉文明,李昌建,錢 巨,2
(1.南京航空航天大學 計算機科學與技術學院,江蘇 南京 210016;2.江蘇省軟件新技術與產業協同創新中心,江蘇 南京 210023)
隨著互聯網的普及,以電子商務網站(eBay、亞馬遜、淘寶)和典型云服務(如Gmail、OneDrive)為代表的許多大型軟件系統承受越來越多的訪問流量[1],這給軟件系統的服務質量帶來了一定的不確定性。負載測試作為軟件測試的一種,是為了檢測系統在負載方面的相關問題從而對系統進行評估的過程[2]。因而,為了保障大型軟件系統服務質量的可靠性,有必要對其進行有效的大規模負載測試[3]。人們研發了LoadRunner[4]、JMeter[5]、httperf[6]等一系列負載測試工具[7],但這些測試工具一般存在兩個問題。一是支持的腳本類型過于單一化,二是僅支持采用進程或線程并發方式發起負載,負載生成機制不夠豐富,負載生成消耗不夠優化,在資源受限的情況下難以生成較大規模的負載。同時,這些測試工具在分布式環境下的部署流程較為復雜,加大了測試的難度。云測試能夠降低負載測試的實施難度,在云負載測試服務方面,以阿里云PTS[8]為代表的云測試系統同樣存在負載生成不夠優化的問題,執行大規模負載測試需要大量云資源作為支撐,增加了測試成本。在云測試工具的相關研究中,文獻[9]實現了一個IaaS云平臺測試系統,該測試系統的負載生成依賴于Apache JMeter來模擬并發負載,只支持以線程并發方式執行JMeter測試腳本,難以在有限資源下生成較大規模的負載。文獻[10]實現了一種自動化云測試平臺,基于測試工具Selenium執行測試腳本以實現自動化測試。但Selenium引擎執行開銷非常大,導致能夠發起的負載規模十分有限,并且該平臺主要用于功能測試。文獻[11]設計了一個基于云計算的軟件測試系統框架,使用動態優先權調度算法實現測試任務的資源分配與執行。其資源分配沒有考慮測試任務的資源特征,僅以測試執行時間為優化目標,可能導致測試資源分配不夠優化。文獻[12]也設計了一個云性能測試工具,支持分布式測試服務器間的同步控制功能,其工作流程為:由控制服務器向測試服務器發送測試執行命令,測試命令保證了所有接收到命令的測試服務器同步執行測試。但是該測試工具只能保證負載測試在初始狀態的同步性,無法保證整個測試在不同階段的同步性。因而,現有研究的測試工具往往存在負載生成機制不夠豐富、測試資源分配不夠優化等問題,導致大規模負載測試成本過高且不易實施。
為了能夠高效地實施大規模負載測試,該文研究了多類型的負載生成、智能化測試資源分配和分布式負載同步控制技術,設計和實現了一種基于OpenStack[13]的大規模云負載測試平臺。該平臺支持進程、線程和協程負載并發機制,結合多類型測試腳本以生成客戶端負載;能夠預測負載測試任務的資源需求,并為其確定云測試主機(OpenStack中部署了測試執行引擎的虛擬節點)資源,實現智能化測試資源分配;采用同步控制算法保證不同測試主機間測試進度的同步性。該平臺為大規模負載測試提供了一個功能豐富、經濟易用的平臺,輔助以智能化測試資源分配、分布式負載同步控制等功能保證了負載測試的執行效果,同時可幫助降低大規模負載測試的實施難度。
基于OpenStack的大規模云負載測試平臺的總體界面如圖1所示。平臺通過導入測試腳本,對待測Web應用進行負載測試,收集相關指標數據,并據此來分析承載Web應用的被測云服務設施的相關表現情況。該平臺支持云負載測試執行、分布式負載同步控制、智能化測試資源分配等主要功能,此外,還支持測試腳本管理、測試結果管理等輔助功能。

圖1 平臺總體界面
為保證靈活性,云測試平臺采用如圖2所示的物理結構,整個系統由前端Dashboard、測試控制中心CloudTest、測試執行引擎TestAgent、監控器VMMonitor四大基本模塊構成。

圖2 系統物理結構
前端Dashboard是一個Web前端頁面,在該頁面下,包含測試集群、云應用、云主機、測試腳本、云應用性能測試、測試結果等主頁面。測試管理中心CloudTest一方面通過暴露的RESTful接口與Dashboard交互;另一方面與測試執行引擎交互,接收實時負載執行數據;與監控器交互,接收主機的實時監控數據。測試執行引擎是執行負載測試的主要模塊,對用戶配置的負載測試任務進行解析,按照預設的負載變化策略在指定的時間節點上生成相應規模的負載,發起對被測目標應用的網絡調用,并統計每個負載的執行數據,匯報至測試控制中心。監控器周期性地采集宿主機的資源使用信息匯報至測試控制中心。其中,測試控制中心、測試執行引擎、監控器等模塊均支持部署在私有云OpenStack上,亦可部署在公有云環境中。
由于進程或線程并發是多個子例程通過操作系統時間片輪轉來實現,占據資源大、任務切換代價較高,導致進程或線程并發方式下單機的負載執行性能一般。協程是一種程序組件[14],其高性能主要體現在如下幾個方面:(1)協程間的切換由用戶空間控制,無需操作系統參與,上下文切換的開銷極?。?2)協程本身是輕量級的,資源占用小,基本不存在數量限制;(3)協程具有非阻塞異步I/O模型的特性,在IO密集型程序中執行性能非常高。因而,協程可模擬大規模負載。
該云測試平臺在支持進程、線程負載并發機制的基礎上,引入了協程負載并發機制。Quasar為Java提供了高性能的輕量級線程,使用Quasar Fiber可實現Java協程,平臺利用Quasar Fiber和異步Appache Http組件實現并發客戶端負載的生成,其主要流程如圖3所示。

圖3 協程執行流程
如圖4所示,平臺利用云環境(如,OpenStack)中的測試主機來發起大規模負載測試,通過分布式并發并行調用產生對目標應用的并發客戶端負載。測試主機上具體由訪問被測目標應用的任務進程、任務進程內的任務線程或任務協程來完成網絡訪問。不同任務進程、任務線程以及任務協程模擬了不同的虛擬客戶端,總的任務進程、任務線程和任務協程的數量大致對應了所生成客戶端負載的總體規模。

圖4 負載并發機制
傳統負載測試工具支持的腳本過于單一化,導致無法很好地適用于不同的測試目標和目的。文中的云測試平臺支持執行Selenium、JMeter、Java等類型的測試腳本,基于多類型測試腳本來生成客戶端負載。腳本間比較如表1所示。

表1 不同類型的測試腳本
如Selenium腳本示例所示的腳本能夠描述Web頁面的打開、點擊等行為,可表達復雜動態頁面Web應用上的動作,靈活性最強,能測試的Web應用類型最廣。但是腳本的執行開銷非常大,并且依賴Python引擎支撐,而當前大部分Python引擎的并發執行性能都較弱。因而,在資源較為一般的測試主機上,很難發起較大規模的負載。
from selenium import webdriver
st=tester();
class Addbook(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Firefox()
self.driver.implicitly_wait(30)
self.base_url="http://192.168.1.130/"
def test_addbook(self):
driver=self.driver
driver.get(self.base_url+"/BookManager?method=list")
driver.find_element_by_link_text(u"添加圖書").click()
if __name__ == "__main__":
unittest.main()
如JMeter腳本示例所示的腳本是一段XML配置,描述了一組針對Web應用的網絡訪問報文。利用JMeter腳本執行引擎可以提取JMeter腳本中所描述的網絡報文,對其進行重放。JMeter腳本執行引擎的一個優勢是可以直接利用其他工具的腳本。相對Selenium腳本,JMeter腳本的負載生成能力明顯更強。然而,JMeter腳本不能像Python腳本那樣隨意修改,輕松插入新的動作代碼,因此,靈活性弱于Selenium腳本。
class="HTTPSamplerProxy" testname="Open blazemeter. com"> entType="Arguments"> weibo.com 如Java腳本示例所示的腳本使用Apache HTTP庫通信,對網絡的訪問較為直接,避免JMeter中的腳本解釋開銷。Java腳本支持協程等高性能機制,依賴于協程并發方式執行腳本,相對于其他測試腳本,其資源開銷最低,執行效率最高,在同樣的CPU和內存配置下,腳本能夠更大規模的并發。然而,此種腳本需要手工編寫,且需要事先編譯,制作代價較高。 import java.util.*; public class TestCase implements Runnable{ private static String URL = "http://www.baidu.com"; public void run() { ResultStatus status = ResultStatus.PASSED; try { Response response = Request.Get(URL).execute(); StatusLinestatusLine = response. returnResponse().getStatusLine(); int statusCode = statusLine.getStatusCode(); if(statusCode!=200) { status = ResultStatus.FAILED; } } } } 在實施大規模負載測試的過程中,由于單臺機器的硬件資源能力較為有限,難以生成較大規模的負載。為了生成足夠規模的負載,往往需要多臺測試主機同時提供測試服務。分配的主機資源如果過少,將會導致無法發起相應規模的負載;分配的主機資源如果過多,將會造成測試資源的冗余。因而,如何為負載測試任務分配資源顯得十分關鍵。 文中的云測試平臺支持智能化測試資源分配功能。平臺以每一處理周期內用戶下達的負載測試任務的序列T= 對于目標負載測試任務序列T中的每個測試任務Ti,云測試平臺按照如下流程預測得到其資源需求向量R= 首先,從平臺的測試腳本池中選擇目標負載測試任務(假設目標負載規模為5 000)的測試腳本(如,edit_order.jar腳本)作為訓練的對象,平臺支持為其自動配置一個較小負載規模(一般為500)的測試活動,并在單臺測試主機(一般配置為2 CPU核心和4 GB內存,其資源向量可用CPU核心數和內存大小的二維向量(2, 4)表示)上自動執行該測試活動,完成預熱執行工作。 第二步,在預熱執行過程中,部署在測試主機上的測試執行引擎收集實時負載執行數據L(t)={(t,load)},監控器實時收集測試主機的資源使用數據R(t)={(t,R)},使用處理函數M(X,Y)將負載執行數據和資源使用數據融合,使其在時間節點上對應,得到負載_資源數據LR,可表示為: LR=M(L(t),R(t))={(load,R)} 第三步,以負載資源數據LR作為訓練模型的輸入,通過回歸學習方法,抽取edit_order.jar腳本的資源模型(可用映射f:load→R表示,其輸入為目標負載規模load,輸出為負載測試任務所需資源向量R)。如圖5所示,其中CPU資源模型為fcpu(x)=0.284lnx+0.07,內存資源模型為fram(x)=0.013x2/3+0.29,從而預測負載測試任務的資源需求為(2.49,4.09),則至少需要3臺配置為1核心CPU和2 GB內存的云測試主機提供測試服務。重復上述步驟,預測出目標負載測試任務序列中每個測試任務的資源需求向量。 圖5 資源模型 對于待分配資源的負載測試任務序列T,基于4.1節所述的方法可預測其資源需求向量為R。云測試平臺采用一種基于遺傳演化的多目標資源分配算法為T分配云測試資源,其總體執行流程如圖6所示。在資源分配的過程中,為了減少資源的浪費,降低測試的成本開銷,并保證測試的執行效率,算法以最小資源冗余、最低測試執行成本為一組優化目標,關于目標函數的定義這里不再贅述。 圖6 算法執行流程 首先,平臺支持獲取當前云測試環境OpenStack的網絡拓撲結構圖(物理節點、虛擬節點以及路由節點間的網絡通信結構)。依據網絡拓撲結構以及虛擬節點可用資源等信息生成負載測試任務序列T的一組初始化資源分配方案,并按照向量S所示編碼方式對資源分配方案進行編碼。 然后,對編碼后的資源分配方案進行交叉、變異操作,形成初步子代種群。對初步子代種群中的資源分配方案進行修復并優化,確保分配方案的可行性與優越性;父子種群合并,將合并后的種群中的優良分配方案選擇進入新的種群,使得分配方案的最小資源冗余、最低測試執行成本等目標朝著更優的方向進化,從而完成一次進化。重復上述過程直至預設進化代數,最終得到目標負載測試任務序列的最優資源分配方案。 最后,平臺依據最優資源分配方案,將負載測試任務序列中的各測試任務分配到對應的OpenStack云測試主機上,并由平臺對云測試主機進行統一化管理。分配完成后,即可開始目標負載測試任務的執行,平臺收集實時負載執行數據以對測試進行運行時監控,從而形成對各負載測試執行的性能評估。 給定如圖7所示的云測試環境G和一組資源需求已知的負載測試任務T=,在文中的云測試平臺上分別采用多目標資源分配和隨機資源分配方法為其分配測試資源。實驗結果如圖7所示,在相同規模的云測試環境下,對于相同的負載測試任務序列,多目標資源分配方法相較于傳統的隨機分配方法,能夠減少云測試主機的占用數,降低測試執行成本。 圖7 實驗環境與實驗結果 隨著測試的執行,不同測試主機上的測試進度可能差別越來越大,最終導致不同機器上對待測Web應用的訪問可能不是按預期來并發的,所產生的負載壓力與預期設定不符。 文中的云測試平臺支持分布式負載同步控制功能。對每個負載測試配置,測試平臺支持為其設定一組同步集合點,典型的同步策略包括按比例同步,即當一定比例的任務進入同步點后,已經進入同步點的任務即可繼續向下執行;以及按絕對數量同步,即當某指定數量的任務進入同步點后,已經進入同步點的任務即可繼續向下執行。 在為負載測試配置設定了同步集合點后,可以在測試腳本中添加同步集合點進入原語,如含同步集合點的測試腳本示例中的st.rendezvous("dosearch")。測試腳本執行到該同步集合點語句時,會進入等待狀態,直到退出同步集合點的條件得到滿足。該同步集合點可以使得該集合點后的語句都盡可能在同一時刻得到執行。 st=tester(); class Baidu(unittest.TestCase): def setUp(self): self.driver=webdriver.Firefox() self.base_url="https://www.baidu.com/" def test_baidu(self): driver=self.driver driver.get(self.base_url + "/") st.rendezvous("dosearch") driver.find_element_by_id("su").click() if __name__ == "__main__": unittest.main() 同步集合點僅對負載變化策略中同一時刻點上發起的負載有效。對于原定測試計劃中理論上應發生于同一時刻的客戶端負載,平臺采用同步控制算法以確保同步集合點能夠有效發揮作用,保證不同測試主機間測試進度的同步性。同步控制算法的總體流程如下: input:待執行的負載測試活動 output:每個步驟上的有效負載規模的集合?(load) begin 獲取負載變化策略,測試集群的主機數量testClusterSize和負載變化步驟數steps; while step 讀取當前步驟上預設的負載規模loadScale; whiletrue 為每個負載啟動一個負載執行器; 在負載執行器內執行測試腳本; 腳本進入renderzvous語句后,向測試主機發出enterRendezvous消息; 當前等待執行的腳本數actualLoad++; if actualLoad==loadScale(測試主機滿足退出同步集合點的條件) then 測試主機向測試控制中心發出enterRendezvous的消息; 等待執行的測試主機數agentCount++; actualLoad→?(load); while agentCount 阻塞等待執行的測試主機上進入同步集合點的腳本; end while 測試控制中心向測試集群中各測試主機發出exitRendezvous; 測試主機將同步退出消息轉發給各個正在等待的腳本; 腳本收到消息后,繼續向下執行測試腳本中的后續語句; end end while end while return ?(load); end 研究了多類型負載生成、智能化測試資源分配和分布式負載同步控制技術,實現了一種基于OpenStack的大規模云負載測試平臺。該平臺具有如下特性:支持進程、線程和協程負載并發機制,結合多類型測試腳本生成客戶端負載,相較于已有測試工具,客戶端負載生成更為高效;支持智能化測試資源分配功能,實現面向云負載測試任務的資源優化分配;支持分布式負載同步控制功能,保證不同測試主機上網絡活動的并行性。該測試平臺為測試人員實施大規模負載測試提供了一個功能豐富、經濟易用的平臺,能夠降低大規模負載測試的難度。4 智能化測試資源分配
4.1 測試資源預測

4.2 測試資源分配


5 分布式負載同步控制
6 結束語