尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Locust压测工具入门:Python实现高效性能测试

Locust压测工具入门:Python实现高效性能测试 Locust 这名字做性能测试的同行应该都不陌生。我在软件测试这行摸爬滚打了十几年从最早用 JMeter 点点点到后来被 Python 生态圈拉拢过去接触 Locust 之后确实有种“原来压测还能这么玩”的感觉。这篇内容就是围绕 Locust 是怎么一回事、该怎么装、装完怎么跑通第一个脚本给各位想入坑性能测试或者想给自己技术栈里添个新武器的朋友做一个尽量详细、能直接照着操作的梳理。先说说这篇博文能解决什么问题。如果你是刚接触软件测试听说性能测试很重要但被 JMeter 的线程组、监听器、CSV 参数化这些概念弄得有点头大或者你是写 Python 的想用自己熟悉的语言搞定压测脚本再或者你就是想找一款能写代码、能分布式、颜值还过得去的压测工具那这篇内容应该能帮你少走不少弯路。我们从最基础的“它是什么、为什么选它”开始一步步到安装、写脚本、跑起来看报告整个过程尽量用大白话讲清楚。1. 性能测试是什么为什么偏偏选中 Locust很多人一提到软件测试脑子里全是功能测试、点点点、找 Bug。但等你真的进入项目里尤其是用户量稍微大一点的 C 端产品性能测试的地位立刻就不一样了。服务器动不动就来个 502数据库连接池被打满接口响应时间从 50 毫秒飙升到 3 秒这些场景如果等到上线之后才被真实用户撞见那基本就是事故现场。性能测试要干的事情就是在系统上线之前模拟出一批“虚拟用户”去访问你的系统看看它在不同的并发压力下表现怎么样瓶颈到底在哪。1.1 性能测试到底测的是什么东西性能测试不是简单地把系统“压垮”就完事了。我们日常工作中至少要把这几个维度拆开来看并发用户数同时有多少个用户正在对系统发起操作。这个数字不是拍脑袋定的而是根据业务预估比如日活 10 万高峰期可能有 2 万人同时在线。响应时间用户从发起请求到看到结果的时间。业界常说“2-5-10 原则”2 秒以内体验很好5 秒以内还能接受超过 10 秒基本就留不住用户了。TPS / QPS每秒系统能处理的事务数或查询数。这是衡量系统处理能力最核心的指标压测报告里绝对绕不开。资源利用率CPU、内存、磁盘 I/O、网络带宽压测的时候要盯着这些定位瓶颈是在应用代码层还是中间件还是数据库又或者是机器本身不够用了。错误率在高压之下请求失败的占比。99% 的情况下错误率飙升都意味着系统已经接近崩溃边缘。性能测试工程师干的事情就是通过工具把这些指标量化出来并且找到“为什么慢”“为什么挂”的根因。而工具选择往往决定了你整个压测过程的效率。1.2 Locust 和 JMeter 到底怎么选说实话我至今仍然认为 JMeter 是性能测试领域的老大哥尤其在国内的测试团队里普及率高得吓人。它有完整的 GUI录制脚本方便插件丰富报表也成熟。但用久了你会发现JMeter 在碰到一些复杂业务场景时反而有点“笨重”。打个比方JMeter 像是一台功能齐全的自动挡汽车你不需要懂太多原理就能开走但它给你预设好了档位和路线你想中间自己换条路走就得去翻阅用户手册找那些藏在菜单深处的配置项。而 Locust 更像一台手动挡性能车它本身没那么多花哨的图形界面核心只有一个你用 Python 代码定义用户行为剩下的并发、调度、统计交给框架去处理。具体对比下来我的感受是这样的对比维度LocustJMeter脚本开发语言Python灵活度极高能做任意复杂逻辑主要靠 GUI 配置复杂场景依赖 JSR223 脚本或插件并发模型基于协程gevent单机并发量轻松上千基于线程线程数越高资源消耗越大学习曲线需要一点 Python 基础但逻辑清晰不需要编程也能上手但深入之后概念繁杂分布式能力天生支持一个 master 多个 worker命令一行搞定支持分布式但配置相对繁琐报告与监控自带 Web UI实时图表也支持导出 CSV 做二次分析报告体系成熟GUI 监听器丰富我绝对不是劝你扔掉 JMeter而是建议你在工具链里增加一个 Locust。它的思路很对我的胃口当压测逻辑复杂到 GUI 配置已经无法描述的时候你需要的不是更多的控件而是一门编程语言。Locust 让你用写代码的方式思考压测场景其实反而是更符合程序员直觉的路径。提示团队里如果全是非技术背景的测试同学JMeter 仍然是效率最高的选择。Locust 更适合团队里有 Python 基础或者需要做高度定制化压力场景的时候使用。工具没有绝对的好与坏只看它跟你的场景匹不匹配。2. 安装 Locust 前的准备工作这些坑先帮你踩平我见过太多人在安装这一步就卡住有的报错信息长得像天书有的明明装好了却运行不起来。归根到底绝大多数问题都出在 Python 环境和依赖包版本上。2.1 版本选择比你想的更重要Locust 现在官方推荐 Python 3.8 及以上版本。我个人的建议是直接上 Python 3.10 或者 3.11别用太旧的版本。因为 Locust 一直在迭代新版本对异步支持、性能统计都在优化你没必要卡在旧版本上给自己找不痛快。这里插一个我在实际项目中踩过的坑。有一次在客户的 Windows 服务器上装 Locust那台机器上老早就装了一个 Python 3.6结果 pip install locust 之后死活提示缺少某个编译环境最后折腾了俩小时升级到 Python 3.9 之后一切顺滑。所以说如果你的环境变量里同时存在多个 Python 版本装之前一定要看准当前用的是哪个python --version如果你用 Windows那个py -V命令也能看但为了避免混乱我建议做一个干净点的虚拟环境不要直接往全局环境里塞一堆包后面项目多了依赖冲突真的会想骂人。2.2 虚拟环境是避免魔法攻击的唯一出路Python 的虚拟环境这个概念我在带新人时几乎每次都要强调一遍。你可以把它理解成一个“隔离的小房间”你在里面装的任何第三方包都只对这个房间生效不会污染全局。这样不同的项目用不同版本的 Locust互不干挠。创建虚拟环境其实就三条命令# 1. 创建虚拟环境venv 是 Python 自带模块不需要额外安装 python -m venv locust_env # 2. 激活环境 # Windows 执行 locust_env\Scripts\activate # macOS / Linux 执行 source locust_env/bin/activate # 3. 升级一下 pip防止后面安装依赖时出现版本解析告警 pip install --upgrade pip看到命令行前面出现了(locust_env)前缀就说明你已经成功进入“小房间”了。下面所有操作都在这个环境里进行哪怕后面你把环境搞坏了直接删掉文件夹重建一个五分钟又是一条好汉比在全局环境里修修补补要省心太多。2.3 正式安装以及验证安装是否成功虚拟环境准备好之后安装 Locust 其实就是一行命令的事pip install locust如果你所在网络环境拉取 PyPI 的包比较慢可以临时指定一下国内镜像源这里我习惯用清华源pip install locust -i https://pypi.tuna.tsinghua.edu.cn/simple安装过程中你会看到 pip 自动帮你拉了一堆依赖这里面有几个名字值得留意gevent、geventhttpclient、flask、requests。gevent是 Locust 实现高并发的底层基石它利用协程在单个线程内完成大量并发任务这也是 Locust 能比 JMeter一个线程占一个系统线程省资源的关键原因。安装完成后一定要做一次验证。有两条命令# 查看版本号 locust --version # 或者用 Python 方式查看 python -m locust --version只要能看到类似locust X.X.X的输出版本号恭喜你环境这关过了。如果运行之后提示“command not found”或“不是内部或外部命令”大概率是你当前终端激活的不是刚才那个虚拟环境或者 Python Scripts 目录没有加入 PATH。不要慌张回头确认一下命令行前缀是否有(locust_env)再试一次。注意在安装过程中如果看到红色背景的 ERROR 日志不要急着截图问人先把最后几行完整读一遍。90% 的情况下问题指向非常明确比如“缺少 Microsoft C Build Tools”那就去装一下对应的官方运行库。Windows 上最容易遇到的就是这个。3. 从零写一个可用的压测脚本跑通 Locust 核心流程环境装好接下来就是最关键的部分了写脚本、跑起来。很多人听说 Locust 是写代码心里就发怵。其实 Locust 的脚本结构异常简单核心就一个类、几个方法的事。3.1 第一个脚本理解 HttpUser 和 task 的关系我们先从最经典的场景出发压测一个网站的登录接口。假设被测地址是https://example.com/login请求方式是 POST参数是用户名和密码。新建一个 Python 文件比如叫locustfile.py内容如下from locust import HttpUser, task, between class WebsiteUser(HttpUser): # 模拟真实用户每两次任务之间等待 1 到 3 秒 wait_time between(1, 3) # 每个用户启动后先执行的初始化逻辑 def on_start(self): print(一个虚拟用户已启动) task def login(self): # /login 是接口地址json 是请求体auth 是 Basic Auth response self.client.post(/login, json{ username: admin, password: 123456 }) # 断言状态码不是 200 的时候把响应内容打印出来方便排查 if response.status_code ! 200: print(f登录失败: {response.text})这段脚本的逻辑其实很直白HttpUser是 Locust 里表示“一个虚拟用户”的基类。每个并发用户其实就是这个类的一个实例。task是装饰器被它修饰的方法就代表一个“用户行为”。你可以写多个task给它们传入权重值比如task(3)表示用户执行这个行为的概率权重。self.client是 HTTP 客户端它支持get、post、put、delete等常见请求方法。底层用的是requests库的语法学过 Python 的一看就懂。wait_time定义了用户在执行完一个任务之后、开始下一个任务之前的等待时间目的是模拟真实的用户思考时间不然压测就变成了毫无间隔的“全力输出”结果参考意义不大。on_start方法在每个虚拟用户启动时执行一次适合做登录态获取、token 初始化等前置操作。between(1, 3)表示在 1 到 3 秒之间随机取一个等待时间。如果你想要更真实的效果还可以用常数固定时间或者自定义一个方法。3.2 跑起来两种模式玩转 Locust脚本写完之后运行就极其简单了。在你的终端里进入locustfile.py所在目录然后执行locust -f locustfile.py --hosthttps://example.com-f指定脚本文件如果文件名正好叫locustfile.py这个参数其实可以省略。--host指定被测系统的主机地址。这个地址也可以在 Web 界面里填写。运行之后终端会显示一句Starting web interface at http://0.0.0.0:8089这说明 Locust 自带的 Web UI 已经启动了。打开浏览器访问http://localhost:8089你会看到一个简洁的填表界面Number of users (peak concurrency)要模拟的并发用户总数。Spawn rate (users started/second)每秒钟启动多少个用户也就是“加压速度”。Host这里会默认带上你在命令行指定的--host。填好之后点击“Start swarming”就能看到实时曲线了。这个界面最核心的板块包括请求总数 / 请求失败数平均响应时间 / 中位数 / 95% 分位响应时间这个比平均值更能反映真实体验每秒请求数RPS/TPS运行中的用户数另一种运行方式是无 UI 模式适合在 CI 流水线里做自动化定时压测。用--headless参数locust -f locustfile.py --hosthttps://example.com --headless -u 100 -r 10 -t 5m这里的-u 100表示总共模拟 100 个用户-r 10表示每秒启动 10 个-t 5m表示总共压测 5 分钟。压测结束后终端会输出一份汇总报告包含 RPS、平均响应时间、各分位数的响应时间以及失败率这些数据直接贴到测试报告里完全够用。3.3 Web UI 里的数据到底怎么看很多新手打开 Web UI 之后看到满屏幕跳动的数字就懵了。我给你说一个最简单的看法先看右上角的三块内容。如果“Failures”显示是 0说明系统在全过程都没报错这是个不错的信号。如果“Requests/s”一直保持在一个稳定值但“Response Timems”的中位数却在不断爬升说明系统可能是遇到瓶颈了服务器处理不过来了。如果“Users”已经到顶而“Requests/s”还在持续下降那基本可以确定服务端出现了排队现象这时候再去查 CPU、内存、慢 SQL 才有意义。有一次我压测一个订单系统的查询接口一开始看到响应时间忽高忽低头都大了。后来盯着 Web UI 看了几分钟发现某个时间点的错误率突然暴增请求全部超时。顺手去查服务器的 Tomcat 线程数发现已经打满了。其实那时候 Locust 已经用曲线图明确告诉你系统不对劲了只是你没看懂而已。4. 进阶一步让压测脚本更贴近真实业务场景很多新手用 Locust 的时候写出来的脚本就是拿到接口文档一个个 GET/POST 往里填。这样压出来的数据只能叫“接口通断测试”根本不算性能测试。真实世界的用户可不是发了请求就干等着的他们会登录、会翻页、会填表单、会一直切换页面。你需要把这些行为组合起来才能模拟出真实用户的操作轨迹。4.1 有状态用户登录 token 怎么做到全流程复用刚接触压测不久的同学走到“登录后还要带着 token 访问别的接口”这一步时经常会卡住。要是每个请求都重新调一次登录接口服务器根本扛不住这种无意义的消耗。正确的做法是把登录逻辑放在on_start里拿到的 token 存到实例属性中from locust import HttpUser, task, between class WebsiteUser(HttpUser): wait_time between(1, 3) def on_start(self): # 登录并获取 token resp self.client.post(/login, json{ username: admin, password: 123456 }) if resp.status_code 200: self.token resp.json().get(data, {}).get(token) else: self.token None print(登录失败后续请求可能无效) task def get_order_list(self): if self.token is None: return headers {Authorization: fBearer {self.token}} self.client.get(/order/list, headersheaders)这样每个虚拟用户只登录一次拿到自己的 token后续的请求都复用这个 token。这就跟真实用户一样打开 App 登一次然后在里面逛来逛去 5 分钟没再重新登录逻辑完全一致。4.2 数据集准备参数化不是让你把所有用户塞同一个账号压测时如果所有请求都用同一个账号、同一份参数那压出来的结果只能是“服务器处理同一条数据的极限”而不是真实混合业务场景。如果系统里有缓存机制比如 Redis那长期跑同一个 Key 的接口缓存命中率无限逼近 100%响应时间当然好看但那没有任何参考价值。合理做法是准备一批账号让每个虚拟用户从池子里随机取一个来用。做法很简单import random from locust import HttpUser, task USERS [ {username: user_001, password: pass_001}, {username: user_002, password: pass_002}, {username: user_003, password: pass_003}, ] class WebsiteUser(HttpUser): def on_start(self): user_info random.choice(USERS) resp self.client.post(/login, jsonuser_info) # ...如果是更复杂的业务需要从数据库或 CSV 里读真实数据也完全没问题用 Python 标准的csv、pandas库读取然后存成全局列表供所有虚拟用户使用即可。这比 JMeter 的 CSV Data Set Config 组件要灵活得多因为你可以在任何需要的地方对数据进行任意加工。4.3 断言与失败重试脚本不能只发请求不判断结果我见过不少人的压测脚本里压根没有对响应做任何判断响应 502 了、返回错误码了照样继续发下一个请求。最后报告里的“失败数”是 0看似完美实际上系统早就打挂了。这种情况如果拿去做上线依据那风险是相当高的。轻量级的断言方式就是用response.status_code和response.textwith self.client.get(/order/detail/10086, catch_responseTrue) as resp: if resp.status_code ! 200: resp.failure(f请求失败状态码: {resp.status_code}) elif expected_keyword not in resp.text: resp.failure(返回内容中缺少预期字段) else: resp.success()catch_responseTrue参数很关键它允许你在 with 块内部定义这个请求到底算成功还是失败。如果不加这个参数只要 HTTP 请求能拿到一个响应哪怕返回的是 500 内部错误Locust 就默认判定为成功这跟你想要的效果完全相反。所以在写断言时要记住状态码 200 不等于业务成功业务成功要从响应内容里验证。5. 我踩过的那些坑以及给你的实用建议工具好不好用光看文档是感受不出来的。真正让系统崩溃的往往不是被测系统而是你压测脚本自己出了问题。这里挑几个我这么多年来反复遇到的典型问题给大家排排雷。5.1 本地机器带不动并发该上分布式就要上Locust 单机模式虽然基于协程已经很省资源了但真要模拟上千个并发用户的时候本机网络、CPU、内存还是会成为瓶颈。你可能会看到 Locust 报告里 RPS 始终上不去但同时本机 CPU 已经打满说明是压测机自己先趴下了而不是被测系统有问题。Locust 的分布式方案是我见过的工具里最省心的之一。启动一个 master 节点和多个 worker 节点worker 跑压测任务master 负责汇总数据# 先启动 master-M 简写 locust -f locustfile.py --master # 再在另外几台机器或另开几个终端启动 worker-w 简写 locust -f locustfile.py --worker --master-host192.168.x.xworker 节点不需要装 Web UI启动后会自动连接到 master。之后你只需要在 master 的 Web UI 上设置并发数所有 worker 会均分这个任务。这个方案特别适合在有多台测试服务器的情况下使用能轻松把并发能力翻好几倍。注意分布式模式下要确保所有 worker 上的locustfile.py内容完全一致否则每个 worker 压测的逻辑都不一样汇总出来的数据会让所有人摸不着头脑。我见过最惨的案例一个团队跑分布式压测结果有一台 worker 忘了更新脚本还在压测旧的接口最终报告彻底没法用。5.2 压测过程中遇到连接数被拒不要只盯着代码看有一次我自己压测一个 Spring Boot 服务并发数一超过 300就开始出现大量ConnectionResetError。当时第一反应是脚本写错了反复检查没发现问题后来去服务器上一查发现操作系统级别的文件句柄数不够了。Linux 系统默认的 ulimit 参数通常比较保守你需要提前调整# 查看当前限制 ulimit -n # 临时提高限制 ulimit -n 65535如果是服务器端频繁拒绝连接还要检查应用服务器的最大连接数配置。比如 Tomcat 的maxThreadsMySQL 的max_connections等。压测不只是客户端发请求的事你要有全链路的排查思路。5.3 报告别只顾着贴平均数90% 分位才是用户体验的真话Locust 的 Web UI 和命令行报告都会输出响应时间的多个分位数比如中位数、90% 分位、95% 分位、99% 分位。很多新手写报告时只贴平均值这其实很不严谨。给你举个例子。假设平均响应时间是 500 毫秒听起来还不错。但可能 90% 的请求都在 200 毫秒以内返回剩下 10% 的请求却超过了 5 秒。这个平均值完全被那 10% 的慢请求拉高了用户的真实体感是“偶尔很卡”而不是“一般般”。所以你在做压测报告时我建议至少包含三组数字平均值给管理层看大方向有个概念。95% 分位给研发看这是大多数用户能感知到的真实体验。最大响应时间给自己看用来判断系统是否存在明显毛刺。Locust 的命令行报告会原样输出这些数据在 Web UI 的 Charts 页面也有现成的曲线直接引用就行。6. 关于 Locust 后续还能怎么玩以及我的一点个人体会工具讲到这里你已经能用 Locust 跑一个像模像样的性能测试了。其实它的能力远不止于此——你可以把压测脚本集成到 CI/CD 流水线在每次代码提交之后自动跑一个冒烟级别的压力测试也可以结合 Prometheus 和 Grafana把压测期间的服务器监控数据实时展示出来形成“压测-监控-分析”的闭环还可以通过 locust-plugins 扩展库对接 Kafka、S3 等数据源实现更复杂的消息队列压测场景。从我个人的实际经验来说Locust 解决了我在性能测试上一个很大的痛点它可以让我用写业务代码的思维去写压测代码而不是在图形界面上找来找去。一旦你开始用 Python 写压测脚本你会发现很多想法都能直接落地——动态生成测试数据、从接口读取参数、条件分支、循环嵌套甚至写点逻辑去验证响应结果的正确性这些在传统 GUI 工具里都很啰嗦的事情在 Locust 里就是几行代码的事。当然这并不代表 Locust 可以完全替代 JMeter。在做一些非常标准的协议级测试比如 JDBC 测试、JMS 消息队列测试时JMeter 的插件生态依然有优势。但如果你问我在 Web 接口、HTTP 服务的性能测试上我更愿意推荐哪一个我会毫不犹豫地指向 Locust。最后再给你一个小建议学习 Locust 最好的方式不是去背文档而是拿你手头正在做的项目开刀。写一个最简单的脚本先跑 10 个并发试试水然后慢慢加用户、加任务、加断言遇到问题就按着日志去排查。压测工具这东西用得多了感觉自然就出来了。希望这篇内容能帮你少踩几个我第一次使用时的坑。
返回列表