基于locust的性能测试优化

问题产生的背景

以往在测试web服务的性能时,使用的工具有loadrnner、tsung、locust、jmeter等。这些工具的基本思路都相同,在一个文件里面定义一个用户所要发起的请求,之后交给工具来模拟多个用户重复执行我们定义的行为,最后返回平均请求的响应时长。
近期在测试几个web服务的性能时,发现了一个问题,就是当一个页面需要加载多个接口的数据时,浏览器所发出的请求是并发的。虽然我们测试时,在文件中定义了单个用户索要发出的所有请求,但是进行性能测试时,对于一个测试用户来说,所有的请求都是顺序发出的。由此引出了如下两个问题:

a.  性能测试过程中工具所虚拟出来的用户数,产生的压力要小于实际环境下同等数量的真实用户。
比如loadrunner,一个文件中定义了两个请求,性能测试时我们将用户数设置成100,那么在压测时,同一时间段并发的请求数是100个,因为每个用户是顺序发起请求。但是实际环境中,100个用户同时访问页面,同一时间的并发请求数应该是200个,因为浏览器会并行发起多个请求。这样,测试报告中的用户数,就不能直接用来作为实际用户数量进行评估了

b.  接口的平均响应时长,并不能用来评估页面的加载时间。
一个页面有两个接口a  b,我们模拟以一个用户访问了两次,这个时候,以接口响应时长为维度的数据报告大致是如下形式:

如果以平均响应最慢的那个接口来评估,那么可以说页面的平均加载时间是3.5s。
但是实际上,第一次访问时,接口a b,最大的响应时长是5s,这个时候页面的加载时间是5s。第二次访问时间是4s。那么平均的页面响应时间应该是4.5s。4.5s和3.5s之间的差距,在性能测试中还是无法忽略的,在数据波动较大的情况下可能会出现更极端的结果。

解决办法

a.  这里可能会有人想到可以起多个用户,每个用户向不同的接口发起请求。假如一个页面有两个接口,要模拟一个真实用户的话,那么测试工具就跑两个用户,这两个用户分别对这两个接口发起请求,这样同一时间的请求并发数就和实际的一致了。这样应该也可以,但是在一些请求频率的细节上,还是和实际的单个用户并发访问有些差别。再有就是最后我们得到的测试结果,还是以接口的响应时长为维度的。
b.  另外一种比较完美的解决方法就是我们自己实现一个html&js解析器,完全模拟浏览器的行为。不过这种方式实现难度较大,且性能测试时,高并发环境下要运行那么多的解析器,资源消耗也是一个很大的问题。
c.  最后一种解决方案,就是利用现有的性能测试工具,修改内部任务调度方式,修改任务统计时间逻辑。把每个用户下的任务改为并发执行,时间统计为所有任务全部执行完毕的时间,作为一次单个事件进行统计。

基于locust进行性能测试优化

本次二次开发之所以选择locust,是因为这个工具开源,基于python,轻量。稍微花些心思就能看懂它的源码。关于locust的内部原理介绍这里不多说。

简单介绍一下locust内部各个代码文件的功能:

Runners.py
主要实现虚拟用户的调度,集群方案下与slave通信,根据压力配置创建对应数量的虚拟用户,并实现测试任务的启动、停止等操 作。
Core.py
定义虚拟用户,每个虚拟用户在运行时所执行的任务以及任务调度都在这个文件中完成。其中ClassTaskSet主要实现每个用户下任务的调度功能。后续我们修改并发执行任务时,也是主要修改这里的代码。
Stats.py
用于维护任务中每个请求的具体信息,包括平均响应时长、最大最小响应时长、请求个数、频率灯、等信息。运行时RequestStats类会为每个请求都创建一个StatsEntry实例,后续所有该请求所产生的数据都由这个实例来维护。
Clients.py
主要用来重写requests中一些类和函数,方便测试web接口时调用。
Main.py
入口文件,解析用户自定义的文件,解析命令行参数等。
Web.py
Locust运行时的web界面,实现前后台交互。后续增加统计数据类型时,需要修改此文件。


过多的介绍代码内容太枯燥了,所以感兴趣的话大家可以自己下载一下源码看一下。原始的locust中,单个虚拟用户的任务执行主要是在core.py中的TaskSet类,run函数中实现了任务的调度执行,主要流程就是在一个while循环中,持续维护一个非空的任务队列,然后每次pop一个任务并执行。当前任务执行完后,进入下一次循环。这里如果我们想要单个用户能够并发执行他的任务的话,就要修改这个函数,将原来的顺序执行改为一次并发执行所有任务,等到当前所有任务都执行完毕后,记录下本次循环执行的时长,再次进入下一个循环。
部分代码内容如下:
  • core.py, 修改run函数中的循环内容,增加execute_tasks函数,修改execute_task函数以及其它一些函数。主要是将原来的顺序执行任务,改为一次性并发执行多个任务。
    def run(self, *args, **kwargs):
        .....
        .....
        while (True):
            try:
                .....
                self.schedule_task()
                try:
                    self.execute_tasks()
                except RescheduleTaskImmediately:
                    pass
                self.wait()
            except InterruptTaskSet as e:
                .....
    
    def execute_tasks(self):
        start_time = round(time()*1000,0)
        self._task_pool = Group()
        for task in self._task_queue:
            self.execute_task(task["callable"], *task["args"], **task["kwargs"])
        self._task_pool.join()
        end_time = round(time()*1000,0)
        jobtime = end_time - start_time
        events.job_finish.fire(name=self.locust.name, jobtime=jobtime)
    
    def execute_task(self, task, *args, **kwargs):
        if hasattr(task, "__self__") and task.__self__ == self:
            self._task_pool.spawn(task, *args, **kwargs)
        elif hasattr(task, "tasks") and issubclass(task, TaskSet):
            task(self).run(*args, **kwargs)
        else:
            self._task_pool.spawn(task, self, *args, **kwargs)
    
    def schedule_task(self):
        self._task_queue = []
        for task in self.tasks:
            task = {"callable":task,"args":[],"kwargs":{} }
            self._task_queue.append(task)
        random.shuffle(self._task_queue)

  • stats.py  增加MyEntry类,用来维护并发任务执行下的各种数据,后续统计测试结果的时候会用到。
class MyEntry(object):
    name = None
    num_jobs = None
    job_times = None
    total_job_time = None
    min_job_time = None
    max_job_time = None

    def __init__(self,name):
        .....
        ....
    def log(self,jobtime):
        self.num_jobs += 1
        self.total_job_time += jobtime
        if self.min_job_time == 0:
            self.min_job_time = jobtime
        else:
            self.min_job_time = min( self.min_job_time, jobtime)
        self.max_job_time = max( self.max_job_time, jobtime)

        if jobtime < 100:
            rounded_jobtime = jobtime
        elif jobtime < 1000:
            rounded_jobtime = round(jobtime,-1)
        elif jobtime < 10000:
            rounded_jobtime = round(jobtime,-2)
        else:
            rounded_jobtime = round(jobtime,-3)

        self.job_times.setdefault(rounded_jobtime,0)
        self.job_times[rounded_jobtime] += 1
    @property
    def avg_job_time(self):
        if self.num_jobs == 0:
            return 0
        else:
            return float(self.total_job_time)/self.num_jobs
  • web.py 修改requests_stats函数,增加针对并发任务的数据统计与计算。
    for k,v in runners.locust_runner.job_stats.items():
        stats.append({
            "name": v.name,
            "num_requests": v.num_jobs,
            "avg_response_time": v.avg_job_time,
            "min_response_time": v.min_job_time,
            "max_response_time": v.max_job_time,
            "num_failures": 0,
            "current_rps": 0,
            "median_response_time": 0,
            "avg_content_length": 0
        })

除了以上三个地方之外还有其它一些地方的代码也需要修改,主要是用来支持任务并发执行后,一些数据统计与记录等内容。整体来说本次优化所做的修改,修改的代码量在几百行左右。

验证测试

自己搭建了一个测试的php页面,用于进行修改后的效果验证。根据url的参数值,sleep对应的时间之后再进行响应,页面的代码如下:
<?php
   sleep($_GET['a']);
?>
编写locust测试脚本,对接口发起请求,分别传入参数1 2 4,也就是这三个请求的响应时间分别为1s 2s 4s.


在MyTest中给name变量赋值为MyBrowser,之后在测试结果页面,针对MyBrower的时长统计,都是该用户每次并发请求所消耗的时长。结果如下:

因为并发请求时,每次统计的是所有请求全部响应完成的时长,所以MyBrowser的结果中,平均时长是4s。

接下来我们验证一下,在第一章节中反馈的问题b。 修改被测试页面的代码,当请求接口a时,随机sleep 1s或4s。请求接口b时,随机sleep 2s或5s。
在locust测试脚本中,对接口a b发起请求。


我们来看一下测试结果:

这里可以看到,接口a的平均响应是2.5s。接口b的平均响应时长是3.5s。如果我们取最慢的那个接口,那么按照老的思维方式,最终的性能结果是3.5s。
但是,如果以并发任务执行的时长为统计维度,我们看到MyBrower中统计的平均时长是4s。
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
搭建 Locust 性能测试框架相对简单,以下是基本步骤: 1. 安装 Python:Locust 是基于 Python 编写的,所以首先需要安装 Python。您可以从 Python 官方网站下载并安装最新版本的 Python。 2. 安装 Locust:一旦 Python 安装完成,可以使用 pip 命令来安装 Locust。在命令行中输入以下命令安装 Locust: ``` pip install locust ``` 3. 创建测试脚本:创建一个 Python 文件,例如 `locustfile.py`,用于编写 Locust 测试脚本。在脚本中,您可以定义用户行为、任务以及性能测试的逻辑。 4. 编写测试脚本:在 `locustfile.py` 文件中,您可以使用 Locust 提供的装饰器来定义用户行为,例如 `@task` 和 `@between`。您还可以设置用户的行为频率和任务执行的时间间隔。 5. 启动 Locust:在命令行中导航到测试脚本所在的目录,并输入以下命令来启动 Locust: ``` locust -f locustfile.py ``` 这将启动 Locust 并提供一个 web 界面,您可以通过浏览器访问该界面。 6. 配置和运行测试:通过 Locust 的 web 界面,您可以配置并运行测试。您可以设置并发用户数、用户生成速率等参数,并监控测试的指标和结果。 7. 分析测试结果:Locust 提供了丰富的测试结果和统计信息,您可以通过 web 界面或者命令行查看并分析测试结果,以评估系统的性能。 这就是搭建 Locust 性能测试框架的基本步骤。希望对您有所帮助!如有任何进一步的问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值