晋文明,颜硕彦,钱 巨,2
(1.南京航空航天大学计算机科学与技术学院,江苏 南京 210016;2.软件新技术与产业化协同创新中心,江苏 南京 210023)
随着互联网的普及,现代的许多大型软件系统,如电子商务网站(如eBay、亚马逊、淘宝)和云服务(如Gmail、OneDrive)等必须具有支持数百或数千万并发用户请求的能力[1]。研究表明,这些系统的故障往往是由于系统无法及时扩展以满足用户需求,而不是功能缺陷。负载测试作为软件测试的一种,用于检查软件系统在正常或预设的极端负载条件下的性能表现[2]。因而,为了保证大型软件系统的服务质量以及业务峰值稳定性,有必要对其进行有效的大规模负载测试[3]。负载测试是一项典型的资源密集型工作[4],尤其是大规模负载测试,需要大量客户端资源作为支撑。因而,研究如何在客户端经济高效地发起大规模负载十分重要。文献[4]基于轻量化容器技术实现负载测试资源的弹性分配;文献[5-6]关注于客户端负载的分配策略,其中文献[5]采用平衡算法保证了集群中不同测试节点上负载生成请求的平衡,文献[6]根据网络延迟和端到端响应时间自适应地改变每个客户机提交给每个服务器的负载比例,以尽量减少请求的响应时间,保证了客户机上负载生成的效率。文献[7-8]关注于待测服务端承受负载能力的研究,其中,文献[7]采用单虚拟机、虚拟机资源DRX扩展等方法以分析Web能够承受的并发用户量;文献[8]依据负载测试过程中Web应用服务器的资源消耗,建立性能模型,预测出在不同配置下目标Web应用程序的最大并发用户数。文献[9-13]均关注于测试负载模型的研究,通过分析用户的行为特征,抽取测试负载的模型,从而设计出真实的测试负载。上述文献均没有深入分析客户端负载生成的相关因素及其影响。在工业界,人们研发了S-Client[14]、LoadRunner[15]、JMeter[16]、httperf[17]、WebLOAD[18]等一系列负载测试工具[19],但这些测试工具并未讨论如何经济地在客户端发起负载。总而言之,目前缺乏关于客户端负载生成的影响因素研究,导致客户端负载生成缺乏指引,不易经济高效地生成大规模客户端负载。
为了保证客户端经济高效地发起大规模测试负载,本文以私有云OpenStack[20]作为实验环境,从负载生成机制和测试集群构建2个方面分析影响客户端负载生成的因素,在相关概念和能力指标的基础上设计实验方案,依托于大规模云负载测试框架开展实验,得出一组关于大规模客户端负载生成的初步结论。本文研究为测试人员在客户端发起大规模测试负载提供了数据参考,有助于优化客户端资源的使用,降低大规模负载测试的成本。
本文在一套基于云的大规模负载测试框架上开展实验研究,总体架构如图1所示。该框架可部署在私有云OpenStack中,亦可部署在公有云环境中。其工作流程为:由用户配置负载测试任务并下达至测试控制中心,控制中心将负载测试任务分配到对应的测试执行引擎上,由测试执行引擎对负载测试任务进行解析,依据负载变化策略,在客户端生成预设规模的并发负载,每个负载依赖于相应的脚本执行驱动(与测试脚本的形式一一对应)发起对待测Web应用的网络调用,待测Web应用处理客户端负载请求并给出响应,由测试执行引擎收集每个负载的实时执行数据并汇总给测试控制中心,形成对客户端负载生成能力的分析与评估。
图1 总体架构
本文称基于某种并发机制执行一类测试脚本来生成负载的方式为负载生成机制。负载生成机制由并发机制和测试脚本形式决定。
并发机制分为进程并发、线程并发以及协程并发。其中进程的创建与销毁开销大,单机进程数量有限,导致客户端支持生成的负载规模十分有限。在负载生成领域,基于多线程并发的负载生成方案是当前的一种主流的方式[21]。协程是一种轻量级的并发机制[22],由于其占用资源小、切换代价小等特性,也可用于生成大规模负载。然而,不同机制对于客户端负载生成的影响如何尚不清晰,本文将探究线程、协程并发机制对客户端负载生成的影响。不同并发机制说明如表1所示。
表1 不同并发机制
测试脚本在整个脚本化测试[23]的过程中起着关键的作用,不同形式的测试脚本对资源的需求各不相同,负载生成效果差异较大。因而,本文将探究不同形式测试脚本对客户端负载生成的影响,不同形式脚本间的比较如表2所示。其中,Selenium脚本能够描述Web页面的打开、点击等行为,可表达复杂动态页面Web应用上的动作,灵活性较强;JMeter脚本是一段XML配置,描述了一组针对Web应用的网络访问报文,脚本的适用性较强;Java脚本使用Apache HTTP库通信,对网络的访问较为直接,对资源的需求较小,执行效率较高。
表2 不同测试脚本
大规模负载测试需要在客户端发起较大规模的测试负载,而单台机器往往难以满足需求,因此需要借助于测试集群[24]。搭建测试集群的一个关键问题是如何为测试集群中的测试主机分配资源,合理的资源分配策略(测试集群中主机硬件资源配置和数目)既能保证负载生成的效果,又能保证机器硬件资源的充分利用。因而,本文研究不同主机资源配置策略对客户端负载生成的影响。
基于上述分析,本文对这些主要因素开展定量实验,实验的总体流程如图2所示。
图2 实验总体流程图
针对上文分析的负载生成机制和测试集群构建,本章提出了如表3所示的基本实验问题。
表3 基本实验问题
为评估不同因素对客户端生成的影响,本章总结了客户端负载生成的相关概念,并提出了客户端观测响应时间、可信度等评估客户端负载生成能力的指标,其中相关概念作为后续实验的基础,能力指标作为实验结果分析的依据。
与客户端负载生成相关的主要概念包括动作、事务、测试脚本、负载等。
定义1动作。在待测Web服务的前端页面,通过操作页面上的一个元素组件(按钮、超链接、文本框等),产生了一个动态行为,称这个操作为一个动作action。
定义2事务。一系列动作组织成的合法序列称为一个事务ts。所谓合法序列就是按时间顺序执行这些动作能够形成一条合法的动作路径。具体的表示如公式(1)所示:
ts=
(1)
定义3测试脚本。一组用于表达动作序列的可执行语句序列称为测试脚本S,如公式(2)所示,其中si代表一条执行语句。测试脚本以文件的形式(如Python、XML、Java)存在,支持被自动化测试工具识别并按照预定义的语句执行。
S=
(2)
定义4负载。一个服务器端区分处理的客户端访问称为一个负载。对服务器而言,一个网络连接可以标识一个客户端访问。负载可表示为事务的序列,如公式(3)所示,其中tsi代表一个事务。
load=
(3)
负载作为一个客户端访问,本文将其生命周期划分为初始状态、执行状态和完成状态3个阶段,其状态转换图如图3所示。
图3 负载状态转换图
本文采用如图4所示的负载活动图来表示一个负载的生命周期,其中ts表示客户端完成负载资源分配的时刻,tsj表示负载中的一个事务,actioni表示事务中的一个动作,ti1表示客户端开始执行动作actioni的时刻,ti2表示服务器端接收动作actioni请求的时刻,ti3表示服务端响应动作actioni的时刻,ti4表示客户端获取动作actioni响应的时刻,te表示客户端完成负载资源回收的时刻。同一个事务中的不同动作间在时间上满足偏序关系,对于连续的动作actioni和actioni+1,只有当客户端获取actioni的响应后才能开始actioni+1的执行,即actioniactioni+1。
图4 负载活动图
本节提出了一组衡量客户端负载生成能力的指标,作为后续客户端负载生成实验结果分析的依据。
定义5客户端观测响应时间。给定一个动作action,规定客户端从开始执行action到接收action响应的时间称为action的客户端观测响应时间ORT。
定义6服务器端实际响应时间。给定一个动作action,规定服务器端从接收action的请求到发出响应的时间称为action的服务器端实际响应时间ART。
定义7客户端并发负载规模。同一时刻客户端处于执行状态的负载数量称为客户端并发负载规模。
由于网络传输、客户端硬件资源有限等原因,导致客户端生成的负载并不一定能对服务器端产生相应规模的负载压力,因而,客户端并发负载规模指标并不能准确地反映客户端负载生成能力。为了更准确地衡量客户端负载生成能力,本文采用吞吐量指标来衡量客户端负载转换为服务器端有效负载的能力;提出可信度指标用于表示客户端观测数据的准确程度,可信度越高,说明客户端对负载的动作处理越快,表明了客户端负载生成能力越强。
定义8吞吐量。单位时间内客户端成功执行的事务数量称为吞吐量。
定义9可信度。给定一个动作action,规定其客户端观测响应时间与服务器端实际响应时间的偏差程度称为可信度credibility,可信度的计算公式为:
如表4所示,本文将部署了测试控制中心模块的主机称为测试控制节点,部署了测试执行引擎模块的主机称为测试执行节点,部署了待测Web应用模块的主机称为待测服务节点。
表4 模块部署信息
如表5所示,实验中涉及的测试执行节点的硬件资源配置为H1~H6,测试控制节点的硬件资源配置为H7,待测服务节点的硬件资源配置为H8。
表5 主机硬件资源配置
实验1分析在相同硬件和系统配置下,协程并发机制相较于线程并发机制的负载创建能力如何?资源利用效率如何?
实验设计:给定硬件资源配置为H1的测试执行节点,测试执行节点的系统参数调至最优,设定客户端并发负载规模为1000, 2000, …, 10000等若干个等级,采用Java脚本作为测试脚本。分别基于协程并发机制和线程并发机制生成预设规模的客户端并发负载,以吞吐量、客户端资源使用等指标来考察不同并发机制的负载创建能力、资源利用效率。
实验分析:从图5和图6可以发现,在相同硬件资源和系统配置下,协程并发机制相较于线程并发机制,在相同的所生成的客户端并发负载规模下,吞吐量更高,表明客户端负载更多地转化成了服务器端的有效负载,且可信度更高,表明客户端观测数据更准确、客户端对负载的动作处理更快,说明协程的负载创建能力强于线程。
图5 并发机制—吞吐量
图6 并发机制—可信度
从图7和图8可以发现,在相同硬件资源和系统配置下,协程并发机制相较于线程并发机制,在相同的所生成的客户端并发负载规模下,内存消耗更少,上下文切换次数更少,表明客户端资源浪费更少,说明协程的资源利用效率高于线程。
图7 内存占用
图8 上下文切换次数
实验结论:在相同硬件和系统资源配置下,协程的负载创建能力强于线程,且资源利用效率更高。说明协程并发机制更适合生成大规模客户端负载。
实验2分析在相同硬件和系统配置下,不同形式的测试脚本支持生成的最佳客户端并发负载规模如何?负载生成效果如何?
初步实验表明,随着客户端并发负载规模的增大,吞吐量会逐步增加至最大,说明客户端的系统性能已达到最佳,若客户端并发负载规模继续增大,将导致吞吐量下降。因而,本文认为最高吞吐量对应的客户端并发负载规模为最佳客户端并发负载规模。
实验设计:给定硬件资源配置为H1的测试执行节点,测试执行节点的系统参数调至最优,设定客户端并发负载规模为表6所示的Load.small、Load.medium、Load.large这3个级别,选取线程并发作为并发机制。分别采用Java测试脚本、JMeter测试脚本和Selenium测试脚本(不同形式的测试脚本描述的事务一致)生成预设规模的客户端并发负载,以吞吐量、可信度、客户端并发负载规模等指标考察不同形式测试脚本的负载生成效果。
表6 负载规模级别
实验分析:从图9可以看到,在相同硬件资源和系统配置下,Java脚本的最佳客户端并发负载规模能够达到4000台左右,而JMeter脚本的最佳客户端并发负载规模为1500台左右,Selenium脚本的最佳客户端并发负载规模只有100台左右。
图9 不同测试脚本期望的客户端并发负载规模
从图10可以看到,在所生成的最佳客户端并发负载规模下,吞吐量方面,Java脚本>JMeter脚本>Selenium脚本,表明Java脚本的负载创建能力较强,JMeter脚本的负载创建能力一般,而Selenium脚本的负载创建能力较弱;可信度方面,Java脚本>JMeter脚本>Selenium脚本,Java脚本的客户端观测数据的准确性较高,JMeter脚本的客户端观测数据准确性尚可,而Selenium脚本的客户端观测数据的准确性相对较差。基于上述分析,说明Java脚本的负载生成效果较好,JMeter脚本的负载生成效果一般,而Selenium的负载生成效果相对较差。
图10 不同测试脚本的吞吐量与可信度
实验结论:在相同硬件和系统配置下,在资源受限的情况下,Java测试脚本更适合生成大规模客户端负载;在资源相对充足的情况下,JMeter测试脚本也能生成一定规模的客户端负载;而Selenium测试脚本难以生成较大规模的客户端负载。
实验3分析基于云的不同主机资源分配策略对客户端负载生成的影响如何?
实验设计:以私有云OpenStack作为实验环境,给定16 vCPU+32 GB的硬件资源池,给定一组节点硬件资源配置从小到大的资源分配策略(如表7所示),测试执行节点的系统参数调至最优,设定客户端并发负载规模为20 000,40 000,60 000,80 000,100 000等若干个等级,选取多协程并发作为并发机制,采用Java脚本作为测试脚本。分别基于设定的主机资源分配策略生成预设规模的客户端并发负载,以吞吐量、客户端资源使用等指标来考察不同主机资源分配策略对客户端负载生成的影响。
表7 测试主机资源分配策略
实验分析:从图11和图12可以发现,在硬件资源(CPU和内存)总和相同且网络带宽资源足够的情况下,给定的不同主机资源分配策略在相同的所生成的客户端并发负载规模下,资源分配策略Strategy4和Strategy5的吞吐量和CPU资源使用率高于Strategy1、Strategy2、Strategy3,说明较细粒度的主机资源分配方案相较于粗粒度的主机资源分配方案,对应测试集群的负载创建能力更强,且客户端CPU资源使用更加充分。
图11 不同资源分配策略的吞吐量
图12 不同资源分配策略的CPU使用率
实验结论:在硬件资源总和相同的情况下,细粒度的主机资源分配粒度(如1 vCPU+2 GB)相较于粗粒度的主机资源分配策略(如16 vCPU+32 GB)对应的测试集群的负载创建能力更强,客户端CPU资源使用更充分,更适合生成大规模客户端负载。
本文实验分析了影响客户端负载生成的相关因素,实验结果表明,在客户端资源受限的情况下,以协程并发作为并发机制,Java脚本作为测试脚本,测试集群中主机资源分配粒度细化些,能够生成更大规模的客户端负载。本文为测试人员在客户端发起大规模负载提供了数据参考,使得大规模负载生成更为经济高效,有助于降低大规模负载测试的执行成本与实施难度。