
1. 项目概述从单机到十万并发的挑战做性能测试的同行估计都经历过一个阶段用JMeter或者LoadRunner吭哧吭哧配好脚本一跑高并发要么是机器先扛不住内存飙升要么是结果数据七零八落难以分析。当业务发展到需要验证十万级并发用户场景时传统单机测试工具的瓶颈就暴露无遗了。这时候一个基于代码、支持分布式、资源消耗友好的工具就成了刚需而Locust正是为此而生。我最近刚完成一个电商大促活动的全链路压测目标就是模拟超过十万真实用户同时在线操作。整个实战下来Locust的分布式架构给了我很大惊喜但也踩了不少坑。这篇文章我就结合这次实战把如何用Locust搭建分布式压测集群、一步步冲到十万并发以及其中的核心配置、监控要点和避坑经验系统地梳理一遍。无论你是刚开始接触Locust还是正在规划大规模压测相信这些从一线实战中总结出的细节都能给你提供直接的参考。2. Locust分布式架构核心设计解析2.1 为什么是Locust事件驱动与协程优势在讨论分布式之前必须先理解Locust的立身之本。与JMeter基于线程的模型不同Locust底层采用了gevent库实现的事件驱动和协程机制。这听起来有点抽象我打个比方传统的线程模型好比开一家餐厅每个顾客虚拟用户都需要一个专属的服务员线程。一万个顾客就需要一万个服务员且不说招聘和管理成本系统线程开销光是这一万人的工资内存和协调CPU上下文切换就能把餐厅测试机拖垮。而Locust的协程模型则像是一个“超级服务员”。这个服务员通过高效的“时间管理”事件循环可以在服务A顾客点菜的间隙去给B顾客上菜再去招呼C顾客入座。从宏观上看他仿佛在同时服务所有人但实际上他只有一个单线程。这就是协程的“轻量级”一个Locust进程一个系统线程内可以轻松运行成千上万个这样的“虚拟用户”协程它们之间的切换代价极低。带来的直接优势就是资源效率。在我的测试中一台16核32GB的服务器用JMeter跑5000个线程用户已经非常吃力内存接近30GB。而用Locust跑10000个用户内存占用稳定在8GB左右CPU利用率也更平滑。这意味着用更少的硬件资源你可以模拟出更高的并发量这是实现十万并发的物理基础。2.2 分布式架构Master-Worker模型详解单机能力再强也有上限。要突破十万并发必须分布式。Locust采用了经典的Master-Worker主从架构清晰且易于管理。Master节点主节点这是整个压测集群的大脑。它不模拟任何用户只负责三件事协调与分发接收来自Web UI或命令行发起的测试指令并将测试任务即用户行为和负载形状分发给所有Worker。数据聚合收集所有Worker节点发回的实时测试数据请求数、响应时间、失败率等。提供Web UI运行一个内置的Web服务器提供实时的数据图表展示和测试控制界面启动、停止、设置用户数等。正因为Master不承担生成流量的压力所以它对资源要求极低。通常一台2核4GB的虚拟机就完全足够甚至可以用一台开发笔记本临时充当。Worker节点工作节点这些是真正“干活”的肌肉。每个Worker节点都是一个独立的Locust进程它从Master节点获取测试任务然后在本机启动协程模拟大量虚拟用户执行测试脚本中定义的行为如HTTP请求。Worker节点的性能直接决定了整个集群的并发能力。你需要根据目标并发数来计算需要多少台、什么配置的Worker节点。集群通信Master和Worker之间通过TCP协议进行通信。默认端口是5557用于Worker连接Master和5558用于Master向Worker推送数据。确保这些端口在防火墙中是开放的并且节点间的网络延迟尽可能低最好在同一个内网延迟2ms否则会影响协调效率和统计数据的时间同步。2.3 实施路线图从零搭建到十万并发规划一次十万并发的压测不能一上来就蛮干。我总结了一个四阶段的实施路线图能有效降低风险步步为营第一阶段脚本与单机验证1-1000并发目标确保测试脚本逻辑正确能模拟核心业务场景如用户登录、浏览商品、下单、支付。关键动作在本地或一台测试机上用Locust单机模式运行脚本。通过Web UI观察请求成功率、响应时间是否正常。使用参数化工具如Faker避免数据冲突。这个阶段重点是功能正确性。第二阶段单Worker节点容量摸底1000-单节点上限并发目标摸清单台Worker服务器的性能天花板。关键动作选择一台规划好的Worker服务器单独部署Locust Worker并连接到Master。逐步增加并发用户数同时监控该服务器的CPU、内存、网络IO。当资源通常是CPU或端口数接近饱和且响应时间开始非线性增长或错误率上升时记录下该节点能稳定支撑的最大用户数。例如我的16核32GB服务器单个Worker稳定支撑了约8000个用户。第三阶段分布式集群联调与阶梯加压多节点至目标并发80%目标验证分布式集群的协调能力并观察系统在压力逐步上升下的表现。关键动作启动所有规划好的Worker节点比如10台连接到Master。在Web UI或使用--headless模式设计一个阶梯式负载模型缓慢增加总用户数。例如先压到5万并发稳定运行10分钟观察被测系统的各项监控指标应用服务器CPU、数据库连接数、缓存命中率等。这个阶段的目标是发现瓶颈而不是直接压垮系统。第四阶段峰值冲击与稳定性测试目标并发100%-120%目标验证系统在极限压力下的表现和恢复能力。关键动作使用负载形状LoadTestShape模拟“秒杀”场景在短时间内如2-3分钟将并发用户数从5万猛增至12万并维持峰值一段时间。记录系统是否出现雪崩错误率飙升、资源是否耗尽如数据库连接池满、以及压力停止后的自恢复时间。这是最具破坏性也是最有价值的阶段能暴露系统最脆弱的部分。3. 十万级并发实战配置与部署3.1 硬件与网络资源配置建议兵马未动粮草先行。硬件是压测的基石配置不当会导致测试结果失真甚至先把自己压垮。Worker节点配置计算型密集型CPU核心数至关重要。Locust的协程虽然在IO等待时能释放控制权但在解析响应、生成下一个任务时仍需CPU。建议选择高频多核的CPU。我的经验公式是单核可稳定支撑约500-800个简单HTTP请求的虚拟用户。对于16核的机器支撑8000-12000用户是合理的预期。内存每个Locust协程用户占用内存很小通常1-2KB。1万个用户也就20MB左右。主要内存消耗在Python运行时和请求响应数据上。32GB内存对于单节点万级并发绰绰有余留有充足缓冲。网络节点间通信带宽和被测系统出口带宽是关键。如果Worker与被测系统不在同一机房网络延迟和带宽可能成为瓶颈。建议Worker集群与被测系统处于同一内网或可用区。节点间Master-Worker通信流量不大但要求低延迟、稳定。系统限制Linux系统默认的单个进程可打开文件数ulimit -n和本地端口范围可能限制高并发连接。务必提前调整。# 修改系统限制需sudo权限或root用户 echo * soft nofile 65535 /etc/security/limits.conf echo * hard nofile 65535 /etc/security/limits.conf # 扩大本地端口范围 sysctl -w net.ipv4.ip_local_port_range1024 65000 # 生效需要重启或重新登录Master节点配置管理型资源需求低。2核4GB足够。确保其网络能与所有Worker节点互通。3.2 分布式集群启动与配置详解配置好环境后启动集群就几条命令但细节决定成败。1. 启动Master节点假设Master节点的IP是192.168.1.100测试脚本是stress_test.py。# 在Master节点上执行 locust -f stress_test.py --master --hosthttp://your-target-system.com --web-host0.0.0.0 --web-port8089--master: 指定该节点为Master。--host: 指定被测系统的基地址。--web-host和--web-port: 指定Web UI监听的地址和端口0.0.0.0表示允许任何IP访问。--expect-workers10: 这是一个非常有用的参数告诉Master你期望有10个Worker连接。当所有Worker都连接上后Web UI上会显示就绪可以避免Worker还没启动完就误开始测试。2. 启动Worker节点在每一台Worker服务器上执行。# 在Worker节点上执行 locust -f stress_test.py --worker --master-host192.168.1.100--worker: 指定该节点为Worker。--master-host: 指定Master节点的IP地址或主机名。--master-port: 如果Master修改了默认通信端口5557则需要用此参数指定。注意所有Worker节点上的stress_test.py脚本必须完全一致。最好通过版本控制工具如Git进行管理确保同时部署同一版本。脚本中引用的任何资源文件如CSV数据文件也需要在各Worker节点相同路径下存在。3. 验证集群状态打开浏览器访问http://192.168.1.100:8089。在Locust的Web UI中你应该能看到“已就绪的Worker数量”与你启动的Worker数一致。这时你就可以在UI上设置总用户数、每秒启动速率并开始压测了。3.3 高级负载模型设计模拟真实流量曲线在UI上手动设置用户数太粗糙了。对于复杂的压测场景我们需要用代码定义负载模型。Locust的LoadTestShape类是我们的利器。例如要模拟一个“双十一”零点秒杀的流量洪峰from locust import LoadTestShape class SpikeLoadShape(LoadTestShape): 模拟秒杀场景的负载形状 1. 预热期缓慢增长预热系统。 2. 飙升期瞬间达到峰值模拟零点抢购。 3. 峰值保持期维持高压一段时间。 4. 衰退期缓慢下降。 time_limit 1800 # 总测试时间30分钟 stages [ {duration: 300, users: 5000, spawn_rate: 100}, # 第0-5分钟缓慢增长到5000用户 {duration: 60, users: 100000, spawn_rate: 5000}, # 第5-6分钟瞬间飙升至10万用户关键 {duration: 300, users: 100000, spawn_rate: 1000},# 第6-11分钟保持10万用户压力 {duration: 600, users: 20000, spawn_rate: 500}, # 第11-21分钟下降至2万用户 {duration: 240, users: 0, spawn_rate: 100}, # 第21-25分钟用户退出测试结束 ] def tick(self): run_time self.get_run_time() for stage in self.stages: if run_time stage[duration]: return (stage[users], stage[spawn_rate]) run_time - stage[duration] return None在启动Master时使用--shape-class参数指定这个类locust -f stress_test.py --master --shape-classSpikeLoadShape这个模型能精准地测试系统在流量陡增时的弹性能力比如自动扩容是否及时、缓存是否被击穿、数据库连接池是否够用。4. 性能监控与瓶颈定位三维体系压测不只是发请求更重要的是观测。你需要建立一个从宏观业务指标到微观系统资源的立体监控体系。4.1 第一维度核心性能指标监控这是Locust Web UI直接提供的也是我们最关注的业务层指标。吞吐量RPS Requests Per Second每秒完成的请求数。这是衡量系统处理能力的核心。在压测过程中你需要观察RPS曲线是否随着并发用户数增长而平稳上升。如果用户数翻倍但RPS几乎不变甚至下降说明系统遇到了瓶颈。响应时间Response Time要特别关注分位数而不仅仅是平均值。50%分位中位数代表大多数用户的体验。它主要受网络延迟和应用基础处理时间影响。95%分位与99%分位P95 P99这是黄金指标。它们反映了尾部用户的体验。P95/P99响应时间飙升通常意味着系统存在资源竞争或阻塞。例如P95升高可能指向应用服务器线程池排队、数据库慢查询增多。P99飙升可能指向个别极端情况如数据库死锁、某台服务器故障、缓存穿透导致直接访问数据库。错误率Failure Rate任何非2xx/3xx的HTTP状态码或未捕获的异常都会被记为失败。压测中错误率需要密切监控。一个健康的系统在稳态压力下错误率应接近于0。错误率上升是系统过载或存在缺陷的明确信号。实操心得不要只盯着总览图。Locust UI可以下载每次请求的CSV明细数据。将其导入到Grafana或甚至用Excel/Python分析你可以绘制出响应时间分布直方图更精确地定位是哪些接口、在什么时间点出现了性能劣化。4.2 第二维度系统资源监控当业务指标出现异常时我们需要向下钻取查看服务器本身的资源状态。这里推荐使用node_exporterPrometheusGrafana这套组合。CPU使用率使用vmstat 1或top命令。重点看us用户态和sy内核态CPU。如果sy占比过高可能意味着系统调用频繁存在大量IO等待或上下文切换。内存使用使用free -h。关注available内存。如果内存使用率持续增长且不释放可能存在内存泄漏。对于Locust Worker本身可以用ps aux | grep locust查看其RSS常驻内存集大小。磁盘IO使用iostat -x 2。关注%util利用率和await平均等待时间。如果%util持续接近100%说明磁盘已是瓶颈。对于写日志频繁的应用这很常见。网络IO使用iftop或nethogs。观察网络带宽是否打满以及是否有大量的重传Retr这暗示网络不稳定。建立监控仪表盘将上述指标和Locust的RPS、响应时间整合在同一个Grafana看板上可以非常直观地看到当并发用户数上升时CPU先达到瓶颈还是数据库连接数先满或是网络带宽先占满。这种关联性分析是定位瓶颈的关键。4.3 第三维度被测应用与中间件监控这是最深的一层需要被测系统具备相应的监控能力。应用服务器如Tomcat, Spring Boot Actuator监控线程池活跃线程数、队列大小。如果队列满了请求就会被拒绝或等待。数据库如MySQL监控活跃连接数、慢查询数量、锁等待情况。SHOW PROCESSLIST;和SHOW ENGINE INNODB STATUS;是救命命令。缓存如Redis监控连接数、内存使用率、命中率、每秒操作数。连接数耗尽是压测中常见问题。消息队列如Kafka, RabbitMQ监控消息堆积数、生产/消费速率。外部依赖API如果系统调用第三方服务必须监控其响应时间和可用性。一个慢速的外部API可以拖垮整个系统。瓶颈定位流程当P95响应时间变长时遵循以下路径排查看应用错误日志是否有大量异常抛出。看应用服务器监控线程池是否健康。看数据库监控是否有慢查询或锁等待。看缓存监控命中率是否骤降。看系统资源监控CPU/内存/IO是否饱和。5. 实战案例电商秒杀场景压测与调优理论说再多不如一个实战案例。我们模拟一个“秒杀1000件商品”的场景目标是验证系统在10万并发抢购下的表现。5.1 测试脚本设计要点from locust import HttpUser, task, between from faker import Faker import random fake Faker() class QuickBuyUser(HttpUser): wait_time between(1, 3) # 用户任务间等待1-3秒更真实 def on_start(self): 用户初始化登录并获取令牌 # 使用参数化数据避免所有用户用同一账号 self.username fake.user_name() self.email fake.email() login_resp self.client.post(/api/login, json{username: self.username, password: default_pwd}) if login_resp.status_code 200: self.token login_resp.json()[token] self.headers {Authorization: fBearer {self.token}} else: # 登录失败此用户后续任务不会执行 self.stop() task(3) # 权重为3执行频率更高 def browse_product(self): 浏览商品详情页 product_id random.randint(1, 100) with self.client.get(f/api/product/{product_id}, headersself.headers, catch_responseTrue) as resp: if resp.status_code ! 200: resp.failure(fBrowse failed: {resp.status_code}) task(1) def quick_buy(self): 核心秒杀下单 # 1. 进入秒杀接口获取秒杀资格和令牌 seckill_resp self.client.post(/api/seckill/1001/enter, headersself.headers) if seckill_resp.status_code ! 200: return verify_token seckill_resp.json().get(verifyToken) # 2. 提交秒杀请求 payload { productId: 1001, verifyToken: verify_token, addressId: random.randint(1, 10) } with self.client.post(/api/order/seckill, jsonpayload, headersself.headers, catch_responseTrue) as resp: # 对结果进行精细化判断 if resp.status_code 200: order_data resp.json() if order_data.get(code) SUCCESS: resp.success() elif order_data.get(code) SOLD_OUT: resp.success() # 商品售罄是业务正常结果不算失败 else: resp.failure(fOrder failed with code: {order_data.get(code)}) elif resp.status_code 429: # Too Many Requests 限流 resp.success() # 被限流也是系统设计的正常表现 else: resp.failure(fHTTP error: {resp.status_code})脚本关键点参数化使用Faker库为每个虚拟用户生成唯一的用户名、邮箱避免数据库唯一约束冲突和缓存热点。状态保持在on_start中登录并保存token模拟有状态会话。任务权重通过task(weight)设置不同任务执行的概率模拟用户行为比例浏览多下单少。精细化响应校验使用catch_responseTrue和手动调用success()/failure()区分HTTP成功但业务失败如售罄、库存不足和真正的系统错误如5xx。这对于计算真实的系统错误率至关重要。5.2 压测过程与发现的问题我们按照“阶梯加压”模型最终在15分钟内将并发用户数提升至10万。压测仪表盘显示RPS在8万并发时达到峰值12,000 RPS之后不再增长。P99响应时间在并发超过6万后从200ms飙升至2s以上。错误率在峰值时达到0.5%主要是HTTP 500和少量超时。结合系统监控我们发现了以下瓶颈Redis连接池耗尽应用服务器配置的Redis连接池最大连接数为5000。当并发用户激增时大量协程同时尝试获取连接导致连接池瞬间被占满后续请求等待超时或失败。数据库慢查询订单创建后有一个更新用户积分历史的操作关联查询了一张大表未使用索引。在高压下该SQL执行时间从10ms恶化到800ms阻塞了数据库连接。应用服务器线程池排队虽然我们用了异步非阻塞框架但某个外部HTTP客户端调用是同步的且未配置合理的超时和熔断导致部分请求线程被长时间挂起。5.3 优化措施与效果验证针对以上问题我们协同开发团队进行了紧急优化动态连接池调整将Redis连接池最大连接数扩大到8000并设置了合理的空闲连接超时时间。同时在应用代码中增加了连接获取失败时的快速失败和降级逻辑如直接返回“活动太火爆”。数据库优化为积分历史表的user_id和create_time字段添加了联合索引使该慢查询速度恢复到20ms以内。同时在代码层面将该操作改为异步处理不阻塞主下单流程。同步调用改造将那个外部HTTP客户端调用改为异步非阻塞方式并设置了200ms的超时和熔断器防止一个慢速依赖拖垮整个服务。优化后复测结果RPS在10万并发下稳定在15,000 RPS。P99响应时间维持在500ms以内。错误率降至0.02%以下主要是正常的售罄和限流。6. 分布式压测七大避坑指南踩过的坑都是宝贵的经验。以下是我在多次大规模分布式压测中总结出的七个关键陷阱陷阱一时钟不同步现象Master和Worker服务器时间不一致导致聚合的统计数据时间戳错乱响应时间计算不准甚至触发一些基于时间的断言失败。解决方案所有压测集群节点包括Master和Worker必须使用NTP网络时间协议同步到同一时间源。时间偏差应控制在50毫秒以内。在Linux上可以使用chronyd或ntpd服务。陷阱二网络分区与延迟现象Worker节点偶尔失联Web UI显示Worker数波动或者测试数据上报延迟导致实时图表“跳变”。解决方案确保所有节点处于同一低延迟内网。跨机房、跨公网的部署方案不可取。使用ping和mtr命令检查节点间网络质量延迟应稳定在2ms以下无丢包。如果必须跨网络考虑在目标网络区域内部署一套完整的Master-Worker子集群然后通过Locust的第三方扩展如使用消息队列进行数据聚合但这会引入复杂度。陷阱三测试数据污染与热点现象大量用户使用相同的账号、商品ID进行请求导致数据库锁竞争激烈缓存命中个别Key无法模拟真实分散的用户行为。解决方案坚决使用参数化工厂。像上面的例子一样用Faker、自定义ID生成器等方式为每个虚拟用户生成唯一或随机的测试数据。对于需要提前准备的测试数据如用户账号可以预生成一个大的CSV或JSON文件让每个Worker读取并使用其中不同的部分。陷阱四资源泄漏端口、内存、连接现象压测运行一段时间后Worker节点内存持续增长或者出现“Cannot assign requested address”错误端口耗尽。解决方案端口耗尽调整系统net.ipv4.ip_local_port_range扩大可用端口范围。确保Locust的HttpUser使用了requests.Session的连接池默认就是并且合理设置连接池大小和超时。内存泄漏编写Locust脚本时避免在任务方法中不断创建全局对象或大对象。定期对Worker进程进行内存分析如使用memory_profiler。连接未关闭虽然requests库会自动管理连接但在异常情况下仍需注意。确保所有请求都在with语句或try-finally块中或在teardown方法中清理资源。陷阱五Master节点单点故障现象Master节点宕机整个压测停止且实时数据丢失。解决方案Locust原生Master有单点风险。对于生产级长期压测可以考虑使用--master的--expect-slaves参数并编写脚本监控Worker连接状态实现简单的存活检测。采用第三方扩展如locust-swarm或boomerGo语言编写的Worker它们支持更健壮的集群管理。最重要的定期通过API导出测试数据。Locust Master提供了/stats/requests/csv等API端点可以定时将数据导出到外部时序数据库如InfluxDB进行持久化这样即使Master重启历史数据仍在。陷阱六忽略系统监控只关注Locust UI现象Locust UI显示一切正常但被测系统实际已濒临崩溃如数据库CPU 100%。解决方案建立端到端的监控视角。压测工程师的屏幕上至少应该同时打开1) Locust Web UI 2) 被测系统应用监控如APM 3) 服务器资源监控如Grafana 4) 中间件监控如Redis MySQL。任何一处的异常波动都需要关联分析。陷阱七一次压测时间过长或过短现象压测运行几分钟就结束发现不了内存泄漏或连接池缓慢增长的问题或者一次压测运行几小时结果数据过于庞杂难以分析。解决方案设计组合式压测场景。稳定性测试中低压力如30%最大并发长时间运行如8-12小时检查系统是否有内存泄漏、性能是否缓慢退化。峰值压力测试高压力短时间运行如30分钟使用LoadTestShape模拟尖峰检验系统弹性。疲劳测试在峰值压力后迅速降至中等压力并维持观察系统恢复情况。 每次压测目标明确时间控制在1-2小时内完成数据收集和分析效率更高。最后我想说的是十万并发压测不是一个简单的工具执行过程而是一个系统的工程实践。它要求测试人员不仅熟悉Locust工具本身更要深入理解被测系统的架构、业务逻辑和依赖组件。从精准的脚本编写到合理的集群规划再到立体的监控分析和有效的瓶颈定位每一步都需要严谨和耐心。当你看到系统在精心设计的压力下暴露出问题并通过优化使其变得更强健时那种成就感正是性能测试工作的魅力所在。