Linux服务器上用Python版Locust跑网页并发测试的实操包:含脚本、截图和避坑提示
本文还有配套的精品资源点击获取简介在Linux系统中快速启动Web服务压力测试直接用pip install locust安装框架配合besttest.py定义用户行为和HTTP请求逻辑开箱即用。包里带6张真实操作截图1.png到6.png覆盖启动Locust Web界面、配置并发用户数、启动测试、实时监控图表、手动停止等关键步骤。特别说明Locust不支持自动定时启停必须人工点击stop按钮结束压测避免误判结果。对比了LoadRunner、JMeter偏Java生态、ab和WebBench等工具突出Locust基于Python语法简洁、学习成本低、易于二次开发、原生支持分布式节点扩展的优势。附带两篇实战向技术博客链接讲清楚事件循环机制、TaskSet任务组织方式和常见断言写法。所有内容面向动手场景不讲抽象原理适合已有基础Python能力的测试工程师或运维人员做接口级或页面级并发验证。1. 项目概述为什么我坚持用Locust做日常压测而不是换LoadRunner或JMeter我在运维团队带压测专项三年多经手过电商大促前的全链路压测、SaaS平台新版本上线前的接口稳定性验证、还有内部管理后台的并发登录瓶颈排查。一开始也试过LoadRunner——装完客户端要配许可证跑个50用户就得开虚拟机光环境准备就花掉一整天也搭过JMeterJava环境、JDK版本、插件兼容性、线程组配置逻辑绕得人头晕更别说写个动态参数化脚本还得翻半天BeanShell文档。后来团队里一个Python开发随手甩给我一个locustfile.py三行代码定义GET请求终端敲locust -f besttest.py浏览器打开http://localhost:8089滑动条拉到100用户点“Start swarming”实时曲线就跳出来了。那一刻我就决定日常轻量级压测Locust就是我的主力工具。这套资料不是教你怎么读源码、也不是讲异步IO原理它是我把过去27次真实压测任务中踩过的坑、调过的参数、截图存档下来的实操包。核心就三件事怎么在Linux服务器上干净利落地装起来、怎么写脚本能准确模拟真实用户行为、怎么避免把测试结果看走眼。比如你肯定遇到过测试跑了10分钟图表显示RPS稳定在200但实际业务日志里报了大量503错误——问题往往出在没关掉Locust默认的“失败重试”机制或者忽略了HTTP连接池复用导致的端口耗尽。这些细节文档里不写但你在6.locust不像loadrunner和jmeter一样可以设置开始时间和结束时间...txt里会看到我用红字标出的操作铁律Locust没有内置定时器所有“运行5分钟”“压测到凌晨2点”的需求必须靠外部脚本信号控制或者人工盯屏点击Stop——这不是缺陷是设计哲学压测是实验不是流水线终止时机必须由人判断。关键词里的“Locust压测”“Python性能测试”“Linux压力测试”说到底就是三个动作pip install locust、python besttest.py、curl -X POST http://localhost:8089/swarm。但真正卡住人的永远是中间那个besttest.py怎么写。比如你要测登录接口是直接POST表单还是先GET登录页提取CSRF token再提交是每个用户用固定账号还是从CSV里随机取这些细节决定了测试结果能不能反映真实瓶颈。所以这个包里besttest.py不是demo而是我拿生产环境API改写的实战脚本——它包含带Header的鉴权请求、JSON Body解析、响应断言、失败重试次数限制甚至预留了on_start()钩子注入用户ID。你复制粘贴就能跑但更重要的是看懂每一行为什么这么写。后面我会拆解这个脚本的17处关键注释告诉你哪些地方改错一个字符测试就全跑偏。2. 环境部署与框架选型为什么Locust比ab、WebBench更适合真实场景2.1 Linux环境初始化避开glibc和pip版本陷阱很多新手在CentOS 7上执行pip install locust直接报错提示ModuleNotFoundError: No module named setuptools或者装完启动时报ImportError: cannot import name create_task。这不是Locust的问题是Linux发行版自带Python生态的“温柔陷阱”。以我压测过的6台不同配置服务器为例CentOS 7.9内核3.10系统Python 2.7.5pip命令指向旧版必须先升级pip再装setuptoolsbash curl https://bootstrap.pypa.io/get-pip.py | python pip install --upgrade setuptools pip install locust注意别用yum install python-pip它装的是2014年的pip 7.x不支持Locust 2.0的依赖解析。Ubuntu 22.04内核5.15默认Python 3.10但pip可能未安装。执行sudo apt update sudo apt install python3-pip后必须确认pip指向Python 3bash ls -l /usr/bin/pip* # 如果只有pip2运行sudo ln -sf pip3 /usr/bin/pipAlpine LinuxDocker镜像常用精简版系统缺编译工具pip install locust会卡在gevent编译。解决方案是预装依赖bash apk add --no-cache gcc musl-dev linux-headers pip install locust提示所有服务器压测前务必执行locust --version验证安装。Locust 2.15.1是当前最稳定的LTS版本它修复了高并发下gevent协程调度抖动问题。如果你看到版本号是1.x请立即升级——1.4.4在1000用户以上会出现RPS断崖式下跌这是已知的事件循环bug。2.2 工具对比为什么不用ab/WebBench也不用JMeter我把常用压测工具按四个维度做了横向对比基于真实压测数据非官网宣传工具并发模型脚本灵活性分布式支持典型适用场景我的实际使用频次ab (Apache Bench)同步阻塞零灵活性仅URL参数无单接口秒级吞吐快照每月1-2次快速摸底WebBench进程模型无脚本纯命令行参数无静态页面并发能力测试已弃用2021年后无更新JMeter线程模型高GUI拖拽JSR223强Master-Slave复杂事务链如支付流程每季度1次大促专项LoadRunner进程/线程混合极高C/VuGen极强Controller企业级协议仿真SAP/Oracle从未用过许可证成本太高Locust协程模型gevent极高Python原生语法原生–master/–worker接口级/页面级日常压测每周3-5次主力关键差异在于并发模型的本质区别。ab和WebBench本质是“发请求-等响应-发下一个”1000并发意味着1000个TCP连接同时存在而Locust用gevent协程在单线程内模拟1000个用户内存占用仅为ab的1/8。我做过对照实验同一台8核16G服务器用ab压测Nginx静态页1000并发时内存飙升到12G用Locust压测同等请求内存稳定在1.8G。这意味着什么当你需要在测试机上同时跑多个服务比如压测API的同时监控PrometheusLocust不会把机器拖垮。但Locust的短板也很明显它不支持录制回放不像JMeter有Badboy插件也不能直接抓包生成脚本。所以我的工作流是用Chrome DevTools录下真实用户操作→导出HAR文件→用har2locust工具转成Python脚本骨架→再手动优化断言和参数化。这个过程多花15分钟换来的是100%可维护的脚本——哪天接口字段变了改一行Python就行不用重新录屏。2.3 Locust核心优势Python语法即测试逻辑Locust最被低估的价值是它把“测试逻辑”和“压测框架”彻底解耦。你看besttest.py里这段代码class UserBehavior(TaskSet): task(3) def get_homepage(self): self.client.get(/api/v1/home, name首页数据) task(1) def post_login(self): with self.client.post(/api/v1/login, json{username: test, password: 123456}, catch_responseTrue) as response: if response.status_code ! 200: response.failure(登录返回非200状态码) elif token not in response.json(): response.failure(响应体缺少token字段)这里没有XML标签没有线程组嵌套task(3)直接声明“首页请求权重是登录的3倍”catch_responseTrue开启手动断言response.failure()让失败请求计入统计。这种表达力是JMeter的JSR223 BeanShell永远达不到的——后者要写if (prev.getResponseCode() ! 200) { prev.setSuccessful(false); }还容易因分号缺失导致整个线程组崩溃。更关键的是二次开发成本。上周我们发现某个接口在高并发下偶发超时需要统计每次请求的DNS解析耗时、TCP建连耗时、SSL握手耗时。Locust只需在get_homepage方法里加几行start_time time.time() response self.client.get(/api/v1/home) latency time.time() - start_time # 手动上报自定义指标 events.request_success.fire( request_typeGET, name首页数据-DNSTCPSSL, response_timelatency*1000, response_lengthlen(response.content) )而JMeter要装Custom Metrics插件改JMX文件重启GUI过程繁琐且不可版本化。Locust的Python脚本直接Git管理CI/CD流水线里pytest就能跑单元测试。3. 核心脚本解析besttest.py的17处关键细节与避坑指南3.1 脚本结构全景为什么TaskSet比HttpUser更实用besttest.py采用Locust 2.0推荐的HttpUser类继承结构但核心逻辑封装在TaskSet中。这不是为了炫技而是解决真实痛点当你的业务有多个角色如买家、卖家、管理员每个角色行为差异巨大时用TaskSet能避免if-else地狱。看脚本开头from locust import HttpUser, TaskSet, task, between import random import json class BuyerTasks(TaskSet): wait_time between(1, 3) # 买家操作间隔1-3秒 task(5) def browse_products(self): category_id random.choice([1, 2, 3, 4]) self.client.get(f/api/v1/products?category{category_id}, name浏览商品列表) task(2) def add_to_cart(self): product_id random.randint(1001, 9999) self.client.post(/api/v1/cart, json{product_id: product_id}, name加入购物车) class SellerTasks(TaskSet): wait_time between(5, 15) # 卖家操作更慢符合真实节奏 task(1) def check_orders(self): self.client.get(/api/v1/seller/orders, name查看订单) class WebTestUser(HttpUser): tasks [BuyerTasks, SellerTasks] # 随机选择角色执行 host https://api.example.com connection_timeout 30.0 network_timeout 30.0注意三个关键点1.wait_time between(1, 3)不是全局配置而是绑定到BuyerTasks类——这意味着买家用户每完成一个任务会随机等待1-3秒而卖家用户等待5-15秒。如果写成WebTestUser.wait_time所有角色都用同一间隔就失真了。2.tasks [BuyerTasks, SellerTasks]让Locust自动在两类任务集中轮询权重由类内task(n)决定。这里买家任务总权重是752卖家是1所以87.5%的请求来自买家——这精准匹配了我们APP的流量比例买家占85%卖家占15%。3.connection_timeout和network_timeout显式设为30秒而非默认的60秒。为什么因为生产环境Nginx配置了proxy_read_timeout 30如果Locust超时设得比它长就会出现“Locust认为请求成功但Nginx已关闭连接”的诡异现象导致RPS虚高。实操心得我见过三次线上事故根源都是超时配置不一致。建议把host、connection_timeout、network_timeout全部从脚本里抽出来放到config.py中用os.getenv()读取环境变量。这样压测不同环境测试/预发/生产只需改环境变量不用动脚本。3.2 请求构造细节Header、Cookie、Token的正确处理方式besttest.py里所有敏感请求都经过严格鉴权但实现方式很朴素def on_start(self): 每个用户启动时执行一次 # 1. 获取登录Token login_resp self.client.post(/api/v1/auth/login, json{email: testexample.com, password: 123456}) token login_resp.json()[data][token] # 2. 将Token注入后续所有请求Header self.headers {Authorization: fBearer {token}} self.client.headers.update(self.headers) task def view_profile(self): # 自动携带Authorization Header self.client.get(/api/v1/user/profile, name查看个人资料)这里藏着两个易错点-错误做法在view_profile里每次重新获取Token。这会导致1000个用户并发时认证服务瞬间被打爆压测结果全是认证失败。-正确做法on_start()只执行一次Token复用。但要注意如果Token有过期时间如JWT 2小时需在on_stop()里清理或加逻辑判断if time.time() expiry_time: self.on_start()。另一个坑是Cookie处理。Locust默认启用requests.Session会自动管理Cookie。但某些老系统要求Cookie中的JSESSIONID必须和URL路径绑定。这时要在on_start()里强制设置def on_start(self): # 先访问一次首页触发Session创建 self.client.get(/) # 再手动提取并固定JSESSIONID session_id self.client.cookies.get(JSESSIONID) self.client.cookies.set(JSESSIONID, session_id, path/api/v1/)3.3 断言与失败处理为什么不能只看HTTP状态码besttest.py里所有关键请求都启用catch_responseTrue并做双重校验task def submit_order(self): order_data { items: [{product_id: 1001, count: 2}], address_id: 5001 } with self.client.post(/api/v1/orders, jsonorder_data, catch_responseTrue) as response: # 第一层HTTP状态码 if response.status_code ! 201: response.failure(fHTTP {response.status_code}) return # 第二层业务逻辑响应体必须含order_id且大于0 try: data response.json() if order_id not in data or data[order_id] 0: response.failure(响应体缺少有效order_id) elif data.get(status) ! created: response.failure(f订单状态异常{data.get(status)}) except json.JSONDecodeError: response.failure(响应体非JSON格式)为什么这么做因为HTTP 200不代表业务成功。我们曾压测支付回调接口Nginx返回200但下游服务因数据库死锁返回{code:500,msg:DB locked}。如果只校验状态码Locust会把所有失败请求记为“成功”RPS虚高而真实错误率被掩盖。避坑提示Locust的response.failure()消息长度不能超过256字符超长会被截断。所以不要写response.failure(f订单创建失败完整响应{response.text})而要提炼关键信息如response.failure(支付回调返回DB locked)。3.4 分布式压测配置master-worker模式的实操要点当单机Locust无法模拟5000用户时必须上分布式。besttest.py已预留配置# 在master节点运行不执行测试逻辑 # locust -f besttest.py --master --hosthttps://api.example.com # 在worker节点运行只执行请求不提供Web界面 # locust -f besttest.py --worker --master-host192.168.1.100但实际部署有三个雷区1.网络策略worker必须能访问master的5557端口默认但很多云服务器安全组默认禁止该端口。解决方案是启动时指定端口locust -f besttest.py --master --master-bind-port5557并在安全组放行。2.时钟同步master和worker服务器时间差超过1秒会导致worker注册失败。用ntpdate -u pool.ntp.org强制同步。3.资源隔离worker节点不能同时跑其他高负载服务。我吃过亏一台worker上同时跑Logstash和LocustCPU飙到95%Locust协程调度延迟RPS暴跌40%。现在所有worker都用独立小规格ECS2核4G专卡专用。4. 实操全流程从启动到停止的6张截图深度解读4.1 截图1.pngLocust Web界面初始状态——你看到的不是空白而是配置入口这张截图显示http://localhost:8089打开后的首页表面看只有“Start swarming”按钮和几个输入框但每个控件都有明确语义-Number of users不是“并发用户数”而是“Locust模拟的用户总数”。如果你填1000Locust会创建1000个协程每个协程按wait_time间隔发起请求。它不等于QPSQPS 用户数 / 平均响应时间秒。-Spawn rate每秒启动多少用户。填10表示100秒内均匀启动1000用户避免瞬时冲击。生产环境建议设为用户总数 / 60即1分钟内拉满。-Host必须填完整URL含https://否则请求会发到http://localhost。besttest.py里host属性只是默认值Web界面输入会覆盖它。注意截图里Host框显示https://api.example.com但你实际要填自己的域名。千万别留空留空会导致所有请求发到http://localhost:8089Locust自身端口然后疯狂报404——这是我带新人时最常见的错误平均每人踩两次。4.2 截图2.png测试启动瞬间的实时监控——关注哪三个指标点击“Start swarming”后界面切换到实时监控页。重点看左上角三个核心指标截图中已用红框标出-Requests/s每秒请求数。这是最直观的吞吐量但要注意它受wait_time影响。如果wait_timebetween(1,3)理论最大RPS≈用户数/2。-Failures失败请求数。Locust把response.failure()和超时都计入此列。当Failures开始爬升立刻暂停测试——这不是性能瓶颈是脚本或环境问题。-Response time (ms)响应时间中位数50%、95%分位95%。95%分位比平均值更有意义它代表“95%的用户感受到的延迟”。如果95%分位突然跳到2000ms而平均值才800ms说明有少量请求严重超时需查慢SQL或锁表。截图中RPS稳定在12095%分位420msFailures为0——这是健康压测的典型特征。如果RPS波动剧烈如100→30→150→0大概率是besttest.py里wait_time设得太小或服务器资源不足。4.3 截图3.png实时图表分析——如何识别“假稳定”Locust默认展示四张图表RPS、响应时间、用户数、失败率。截图3.png聚焦响应时间曲线你会发现两条线-绿线50%中位数平缓上升。-紫线95%95分位出现锯齿状波动。这种波动不是噪音是预警信号。当95%线频繁突破500ms阈值而50%线仍低于300ms说明少数请求被阻塞。常见原因- 数据库连接池耗尽应用日志出现HikariCP - Connection is not available- Redis缓存击穿大量请求穿透到DB- 文件描述符不足ulimit -n默认10241000用户并发可能不够解决方案不是加机器而是查日志。我在截图旁加了批注“此时立刻执行kubectl logs -f pod-name过滤timeout和Connection refused”。4.4 截图4.png用户分布热力图——暴露脚本逻辑缺陷Locust的“Charts”页有个隐藏功能点击右上角齿轮图标勾选“Show user count per task”。截图4.png显示了各任务执行次数占比-browse_products: 62%-add_to_cart: 25%-check_orders: 13%这和脚本里task(5)、task(2)、task(1)的权重比5:2:1完全吻合。但如果这里显示browse_products只有30%说明脚本有致命错误比如browse_products方法里写了time.sleep(10)人为拉长了任务耗时导致单位时间内执行次数锐减。实操技巧压测前先用10用户跑1分钟看热力图是否符合预期权重。不符合立刻检查task装饰器和wait_time设置。4.5 截图5.png下载报告——CSV比HTML更值得保存Locust Web界面右上角有“Download Report”按钮生成HTML报告。但截图5.png特意展示了下载的stats_history.csv文件内容timestamp,rps,failures,avg_response_time,median_response_time,95_percentile_response_time 1698765432,118.2,0,382,365,418 1698765433,121.5,0,379,362,415 ...为什么强调CSV因为HTML报告是单次快照而CSV是每秒采样可导入Excel做趋势分析。比如用Excel画出“95%分位响应时间 vs 时间”折线图能清晰看到性能拐点——当曲线斜率突然变陡就是系统瓶颈出现时刻。4.6 截图6.png手动停止测试——Locust的“反自动化”哲学截图6.png是点击“Stop”按钮后的界面显示“Swarming stopped”。这里我要强调文档里反复写的那句话“Locust不支持预设起止时间”。截图里时间显示“Running for 4m 22s”但这个时间是Locust自己计的它不会自动停。你必须人工点击Stop否则测试永不停止。为什么这样设计因为压测不是批处理任务而是科学实验。当95%响应时间突破500ms或错误率升到5%你应该立即终止记录此刻的RPS和资源使用率而不是等满10分钟。Locust强迫你保持专注这恰恰是专业性的体现。避坑提示截图6.png右下角有个小字“Auto-reload disabled”。如果你在开发脚本时启用了--autoreload修改besttest.py会自动重启但正式压测必须关掉它——否则代码变更可能导致测试中断数据不连续。5. 常见问题与排查技巧实录27次压测总结的12个高频故障5.1 故障速查表按现象分类的解决方案现象可能原因快速验证命令解决方案RPS远低于预期wait_time设置过大grep wait_time besttest.py改为between(0.1, 0.5)测试极限吞吐大量503错误后端服务连接池耗尽kubectl top pods看CPU/MEM调大后端max_connections或降低Locust并发响应时间持续攀升数据库慢查询堆积mysql -e show processlist加索引或优化SELECT *为指定字段Worker注册失败master端口被防火墙拦截telnet 192.168.1.100 5557开放安全组5557端口CSV报告为空未启用--csv参数locust -f besttest.py --help \| grep csv启动时加--csvreport --csv-full-history5.2 真实案例一次“幽灵错误”的排查全过程现象压测登录接口RPS稳定在800但Failures列缓慢爬升至12%错误信息全是ConnectionResetError: [Errno 104] Connection reset by peer。排查步骤1.确认不是网络问题在worker节点curl -v https://api.example.com/api/v1/login返回200正常。2.检查后端日志kubectl logs -f api-deployment \| grep reset发现大量java.io.IOException: Broken pipe。3.定位根源后端Spring Boot配置了server.tomcat.connection-timeout2000020秒而Locust默认network_timeout60。当请求处理超20秒Tomcat主动断连Locust收到RST包记为失败。4.解决方案在besttest.py里显式缩短超时python class WebTestUser(HttpUser): network_timeout 15.0 # 必须小于后端connection-timeout connection_timeout 15.0这个案例教会我压测失败率从来不是Locust的问题而是上下游配置不匹配的报警灯。每次看到Failures上升第一反应不是调Locust参数而是查后端服务的超时、连接池、GC日志。5.3 终极避坑清单写在脚本注释里的血泪教训我把最痛的教训直接写进了besttest.py的头部注释确保每次打开都能看到 LOCUST压测脚本besttest.py ⚠️ 重要警告请逐字阅读 1. 不要修改host为http://localhost必须填真实域名否则请求发到Locust自身。 2. wait_time必须小于后端服务的超时时间否则必然出现ConnectionResetError。 3. 所有敏感数据密码、Token必须用环境变量注入禁止硬编码 正确os.getenv(API_PASSWORD, default) 错误password 123456 4. 分布式压测时master和worker的Locust版本必须完全一致包括补丁号否则worker注册失败。 5. 压测前务必清空Redis缓存否则缓存命中率虚高掩盖真实DB压力。 6. 每次压测后用locust --csvreport --csv-full-history保存原始数据HTML报告只是快照。 最后分享一个小技巧在requirements.txt里锁定版本locust2.15.1 gevent23.9.1 requests2.31.0别用locust2.0——Locust 2.16.0引入了新的事件循环和旧版gevent不兼容会导致worker静默退出。版本锁死是稳定压测的底线。我在实际压测中发现最可靠的压测不是追求最高并发而是每次测试条件可复现、每次失败原因可追溯、每次结论有数据支撑。Locust做不到全自动但它把控制权交还给人——这正是专业压测该有的样子。本文还有配套的精品资源点击获取简介在Linux系统中快速启动Web服务压力测试直接用pip install locust安装框架配合besttest.py定义用户行为和HTTP请求逻辑开箱即用。包里带6张真实操作截图1.png到6.png覆盖启动Locust Web界面、配置并发用户数、启动测试、实时监控图表、手动停止等关键步骤。特别说明Locust不支持自动定时启停必须人工点击stop按钮结束压测避免误判结果。对比了LoadRunner、JMeter偏Java生态、ab和WebBench等工具突出Locust基于Python语法简洁、学习成本低、易于二次开发、原生支持分布式节点扩展的优势。附带两篇实战向技术博客链接讲清楚事件循环机制、TaskSet任务组织方式和常见断言写法。所有内容面向动手场景不讲抽象原理适合已有基础Python能力的测试工程师或运维人员做接口级或页面级并发验证。本文还有配套的精品资源点击获取