Bootstrap

一探究竟!locust性能测试全解析

最近在工作中使用locust做性能压测,发现这款工具非常强大,特别是在单台压测机能够制造更多的并发量,于是整理成了这份文档,故分享给大家。

(一)认识Locust

1.主流性能测试工具对比

工具

Locust

Jmeter

Loadrunner

是否收费

免费

免费

收费

语言

Python

Java

C

并发机制

协程

线程

进程/线程

单机并发能力

场景压测

支持

支持

支持

分布式

支持

支持

支持

2.Locust性能测试工具介绍
  • 简介

Locust是一款易于使用的分布式负载测试工具,一个locust节点就可以在一个进程中支持数千并发用户,基于事件,通过gevent使用轻量级执行单元。

  • 线程和协程的区别

    • 一个线程可包含多个协程

    • 线程和进程是同步机制,协程是异步

    • 线程的切换由操作系统调度,协程由用户自己进行调度

    • 协程相较于线程更加轻量,资源消耗更低

  • 为什么选择locust?

    • 基于协程,低成本实现更多并发

    • 有第三方插件,易于扩展

    • 支持分布式、高并发能力

(二)Locust基础语法

1.Locust安装

pip install locust

2.Locust文档详解

https://cloud.tencent.com/developer/article/1594240

3.Locust核心概念
    User类

    概念:表示要生成进行压力测试的用户

    用户:每个user实例相当于一个用户

task    

    概念:用于处理用户的行为,比如:登录会员-> 获取会员信息-> 刊登物件

  • TastSet:定义用户将执行的一组任务的类,测试任务开始后,每个locust用户会从TastSet中随机挑选一个任务执行

4.Locust运行与监控
  • 图形化界面

    1. 通过命令行启动Locust服务,然后访问http://localhost:8089

    2. 在页面设置执行压测的策略,并发的用户数,每秒启动的用户数,执行多长时间等

    3. 点击start,开启执行压测

图片

  • 命令行界面

locust -f locustfile.py --headless -u 100 -r 10 -t 30s
  • 启动参数

  1. -f, --locustfile:指定 locustfile 的文件路径。

  2. -H, --host:指定要测试的主机地址。

  3. -u, --users:设置并发用户数。

  4. -r, --spawn-rate:设置用户生成速率。

  5. -t, --run-time:设置测试运行时间。

  6. -L, --no-reset-stats:在测试期间不重置统计数据。

  7. –csv:将结果保存为 CSV 文件。

  8. –headless:以无界面模式启动 Locust

  9. --web-host IP地址:设置web页面的访问IP

5. 性能指标

  • RPS

Requests per Second:每秒完成的请求数,类似于TPS,反映服务器的处理能力

  • 响应时间

Average Response Time:每个请求的平均响应时间

  • 用户数

Number of Users

图片

(三)Locust扩展应用

1. 参数化

1.1 通过引入队列的方式,实现方式是将测试数据插入队列,然后依次取出,数据取完后locust会自动停止。

1.2 若是需要将测试数据循环使用,则每次取出数据后,然后再插入队尾,即可实现循环压测


import queue

# 读取测试数据(读取csv文件的测试数据)

csv_list = csv_to_list('user.csv')

# 创建队列

q = queue.Queue()


# 遍历列表数据,将数据放入队列

for i in csv_list:

q.put(i)


# 从队列取出数据,原理:先进先出

q.get()
2. 检查点
  • 默认检查点

locust默认情况会使用默认的检查点,比如当接口超时,链接失败等原因,会判断请求失败。

  • 手动设置检查点

使用self.client提供的catch_response=True参数,添加locust提供的success和failure方法。其中failure方法需要传入一个参数,内容就是失败的原因。

3. 思考时间

wait_time

  • 固定时间:constant(wait)

  • 区间随机时间:between(min_wait, max_wait)

  • 自适应时间constant_pacing(wait),设置每个任务的最长时间

4. 权重

测试中,存在多个User Class,默认情况下locust将为每个User Class的实例的数量是相同的。通过设置weight属性,来控制locust为我们的User Class 生成不同数量的实例。

方式一

在HttpUser类中任务函数通过@task设置,能够在同个Class类中设置不同任务的权重比


class QuickstartUser1(HttpUser):

# 设置该任务权重为:1

@task(1)

def task_demo1(self):

print("执行的第一个任务")


# 设置该任务权重为:3

@task(3)

def task_demo2(self):

print("执行的第一个任务")

方式二

文件中存在多个用户类场景,可以通过在类的属性中定义权重,,在启动locust服务后,会更加设置的权重来执行任务,比如下图中QuickstartUser1和QuickstartUser2执行权重是:1:3

 

class QuickstartUser1(HttpUser):

# 设置该类下的权重为:2

weight = 1


def task_demo1(self):

print("执行的第一个任务")


def task_demo2(self):

print("执行的第一个任务")


class QuickstartUser2(HttpUser):

# 设置该类下的权重为:3

weight = 3


def task_demo3(self):

print("执行的第一个任务")


def task_demo3(self):

print("执行的第一个任务")
5. 集合点

什么是集合点?

在性能测试过程中,假设需要测试系统能否支持1000人同时访问该业务,然后在调用该业务之前加入集合点,当所设置的虚拟用户运行到集合点时,便开始检测集合点是否以满足1000人,如果不满足,已到达集合的用户在该位置等待,当设置的虚拟用户达到1000人时,然后才会去执行1000人同时访问该业务,从而通过高并发的压测,测试系统的性能瓶颈。

 

# 通过gevent并发机制,使用gevent的锁来实现

from locust import HttpUser, TaskSet, task, between, events

from gevent._semaphore import Semaphore



all_locusts_spawned = Semaphore()

all_locusts_spawned.acquire() # 阻塞线程



def on_hatch_complete(user_count,**kwargs):

"""

Select_task类的钩子方法

"""

print(f"{user_count}个虚拟用户产生完成")

# 创建钩子方法

all_locusts_spawned.release()



# 挂在到locust钩子函数(所有的Locust示例产生完成时触发)

events.spawning_complete.add_listener(on_hatch_complete)


n = 0



class UserBehavior(TaskSet):

def on_start(self):

global n

n += 1

print("%s个虚拟用户开始启动" % n)

all_locusts_spawned.wait() # 同步锁等待


@task

def test2(self):

url = '/detail'

with self.client.get(url, headers={}, catch_response=True) as response:

print("用户同时执行查询")



class WebsiteUser(HttpUser):

host = 'https://www.baidu.com'

wait_time = between(1, 2)

tasks = [UserBehavior]
6. 分布式

如果单台机器不足以模拟所需的用户数量,Locust可以可以设置为单机主从模式和多机主从模式,具体可参考文档:https://blog.csdn.net/qq_36076898/article/details/109015136?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-109015136-blog-131328446.235^v43^pc_blog_bottom_relevance_base6&spm=1001.2101.3001.4242.1&utm_relevant_index=1

7. 高性能FastHttpUser

相对于HttpUser是直接继承python-request,FastHttpUser则是使用更快的geventhttpclient代替,它提供了一个相似的 API,在相同的并发条件下使用FastHttpUser能有效减少压测机的资源消耗,从而达到更大的http请求数

(四)性能测试场景实例

压测场景

备注:多场景串连,模拟用户登录-> 然后获取用户的物件列表

1.压测策略

并发用户数:100个,每秒增加2个用户,压测时长:1min, 登录接口和获取物件列表权重比为:1:10

2.测试脚本
 

# -*- coding:utf-8 -*-

# Time: 2024/5/18

import os

from locust import HttpUser, task, between,constant, constant_pacing, SequentialTaskSet

from loader import csv_to_list

import queue

# 读取测试数据

csv_list = csv_to_list('user.csv')

# 创建队列

q = queue.Queue()

# 将数据放入队列

for i in csv_list:

q.put(i)



# 多个任务按照顺序执行

class UserBehavior(SequentialTaskSet):

access_token = None

headers = {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'}


@task(1)

def login(self):

"""登录 """

self.headers.update({'Content-Type': 'application/x-www-form-urlencoded', 'device': 'pc', 'deviceid': '48jjqt2uk0r8gopajpc616hqm7'})

data = {'login_type': 'pwd'}

data.update(q.get())

q.put(data)

# print(data)

with self.client.post("/v2/user/login", headers=self.headers, data=data, verify=False, catch_response=True, name='登录') as response:

if response.status_code != 200:

response.failure('Failed to get land list api')

if response.json()['status'] != 1:

response.failure('Failed to get land list')

else:

self.access_token = response.json().get('data').get('access_token')


@task(10)

def get_user_house_list(self):

""""获取物件列表"""

payload = {

"device": "pc",

"deviceid": "bno6bbc7na848krqc6vpi39s54",

"current_page": 1,

"page_size": 15

}

self.headers.update({'access-token': self.access_token})

with self.client.post("/v1/user/ware/open", headers=self.headers, json=payload, verify=False, catch_response=True, name='获取物件列表') as response:

if response.status_code != 200:

response.failure('Failed to get land list')

if response.json()['status'] == 1:

response.success()

# print(response.json())


# HttpUser类是Locust的基类,用于定义用户行为

class QuickstartUser(HttpUser):

wait_time = between(1, 2)

host = 'https://test.123.com'

tasks = [UserBehavior]
3.测试结果

图片

图片

结论:通过曲线图可以看到,当用户数达到64,RPS峰值达到16.8,随着压力进一步的增大,RPS明显区域平稳,此时基本可以看出该接口RPS趋于16,即RPS =16,另外随机压力的增大,响应时间是越来越慢,故实际的符合要求的RPS明显要低于16

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:【文末自行领取】

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!

;