ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Locust分布式压测实战:从单机瓶颈到十万级并发架构解析

Locust分布式压测实战:从单机瓶颈到十万级并发架构解析 1. 分布式压测的出发点为什么单机Locust撑不起十万级并发先说结论单机跑Locust十万级并发基本是白日梦。如果你只是验证接口在几百、一两千并发下的表现单机完全够用但目标一旦拉到十万级瓶颈根本不在Locust本身而在于操作系统的文件描述符上限、网络连接数、CPU和内存的物理边界以及GIL对Python多线程的天然限制。我最早尝试压测的时候在一台8核16G的机器上起了600个模拟用户结果CPU直接被打满RPS每秒请求数却只有可怜的两三千。后来查了一圈才发现问题出在几个地方一是Python的协程切换在高并发下会产生大量调度开销二是单机TCP连接数被内核参数卡死三是Locust默认的单进程模式根本无法发挥多核优势。换句话讲十万级并发压测不是Locust功能上不支持而是单机这个“容器”装不下。分布式架构要解决的核心问题就是把“一个进程跑十万用户”拆成“多个进程每个跑几千用户”让压力均匀分布在多台机器上。这样一来每台机器的资源都被充分利用同时通过Master节点统一协调任务分配、结果汇总和实时监控调度成本被控制在一个可接受的范围。另外需要明确一个概念Locust里的“并发用户数”指的是模拟用户数并不等于QPS。一个用户可以连续发送多个请求十万个用户在压测过程中产生的实际QPS可能达到几十万甚至更高。这也意味着你不仅要考虑并发数的天花板还要考虑请求密度和持续时间对整体压力模型的影响。2. 分布式架构的原理拆解Master节点与Worker节点的职责边界2.1 主从模式谁在调度谁在干活Locust的分布式架构是典型的主从模式Master节点负责任务下发、结果汇总和Web监控页面的展示Worker节点才是真正跑压测任务的执行单位。每个Worker节点是一个独立的Python进程独立维护自己的模拟用户集合互不依赖。如果一台机器上起了多个Worker它们之间也是完全隔离的唯一的通信渠道就是Master节点。这个设计的直观类比是“包工头带施工队”Master是包工头不亲自搬砖只负责安排工作量、统计进度Worker是施工队员真正干活。Master挂了Worker还能继续跑但压测结果会失联甚至丢失Worker挂了Master会标记该节点失联其他Worker继续工作总并发数会随之下降。理解了这个边界你就能明白为什么生产环境的压测任务必须至少保证Master节点的稳定。2.2 进程间通信机制Master与Worker的交互路径Master和Worker之间的通信基于pyzmq默认通过TCP连接。每个Worker在启动时会向Master注册Master维护一个活跃Worker列表并将压测任务包括测试脚本内容、模拟用户数、孵化速率等广播给所有Worker。Worker执行过程中会定时将统计数据如请求数、失败数、响应时间、当前RPS等推送给MasterMaster汇总之后在Web界面展示。这里有一个容易被忽略的细节如果测试脚本修改了只需要重启Worker节点加载新脚本Master不需要重启。但必须确保所有Worker节点的脚本版本完全一致否则会出现部分节点跑旧逻辑、部分节点跑新逻辑的严重不一致。我在实际项目中踩过这个坑排查了半天RPS数据忽高忽低最后发现是有一台Worker没有同步最新的脚本。通信端口方面默认情况下Master监听5557端口用于接收Worker注册5558端口用于接收Worker的统计数据。这两个端口是硬性依赖防火墙必须放开否则Worker无法注册表现出来就是“Worker启动后一直等待连接没有实际压测动作”。2.3 并发用户数分配策略有多少用户怎么分分配策略的核心思路是总模拟用户数除以Worker节点数每个Worker承担一个“份儿”。比如目标十万并发、20个Worker每个Worker就是5000个用户。这要求每台Worker所在的物理机有足够的资源去支撑这个份额如果某个节点明显偏弱比如只有2核4G建议把它分到的用户数调小而不是强行均分。实际操作中我习惯把用户分配逻辑做成“按权重分布”。比如有两批机器一批16核32G一批8核16G我会让前者的Worker数更多或者给它们分配更多用户。实现方式是在脚本里通过--worker参数启动时传入不同的用户数量配置然后按比例启动不同数量的Worker实例来达到加权效果。3. 从零搭建分布式压测环境依赖安装与网络配置3.1 环境准备清单在动手之前先把整个环境需求列出来避免中途发现缺东少西Master节点一台建议至少4核8G主要负责调度和统计压力不大Worker节点若干根据目标并发数来定通常建议总CPU核心数不少于目标并发数的1/20即十万并发总核心数不低于5000核但实际上这个比例偏高按经验值每Worker核心数支持2000-5000用户比较合理Python 3.8以上版本推荐3.10或3.11Locust 2.x建议锁定一个稳定版本不要用最新版踩新坑所有节点之间网络互通Master的5557、5558端口放行关于Python版本我强烈不建议用3.7以下的版本。Locust的异步模型依赖gevent而在Python 3.9下gevent的兼容性更好旧版本容易出现奇怪的线程安全问题和内存泄漏。这里的“内存泄漏”通常会表现为Worker节点运行几小时后占用的内存越来越高最终被系统OOM杀掉。3.2 安装步骤与版本锁定每台机器上执行同样的安装操作pip install locust2.15.1装完验证版本locust --version如果你有多个Python环境记得用python -m locust --version来避免指向错误的Python解释器。这里有个小坑pip install locust会默认安装在系统级目录如果机器上有权限限制需要加--user参数或者使用虚拟环境。我习惯对每台机器都用virtualenv隔离避免污染系统环境也方便后续统一管理依赖。3.3 内核参数调优压测前的必做功课这一步是很多人容易忽略的但恰恰是能否支撑高并发的关键。Worker节点上需要调整两个核心内核参数文件描述符上限和TCP连接相关参数。# 查看当前值 ulimit -n # 永久修改编辑 /etc/security/limits.conf # 添加以下内容 * soft nofile 1048576 * hard nofile 1048576 # 调整TCP连接相关参数 # 编辑 /etc/sysctl.conf添加 net.core.somaxconn 65535 net.ipv4.tcp_max_syn_backlog 65535 net.ipv4.ip_local_port_range 1024 65535 net.ipv4.tcp_tw_reuse 1 # 生效 sysctl -p为什么要调这些参数并发用户数达到十万级时每个Worker节点需要同时保持大量TCP连接。默认的1024文件描述符上限连几千个连接都撑不住更不用说几万。tcp_tw_reuse的开启可以复用TIME_WAIT状态的连接显著降低连接耗尽的概率。这里我必须提醒一句不要在生产环境的业务服务器上照搬这些参数压测机器的优化参数和业务服务器的参数完全是两码事。压测机器追求的是“尽可能多地建立连接”业务服务器追求的是“稳定处理业务”两者目标不同参数自然不同。4. 分布式压测脚本编写核心逻辑与参数配置4.1 测试脚本基础结构下面这份脚本是一个完整的分布式压测示例包含了用户行为定义、权重分配、运行时参数配置等关键要素from locust import HttpUser, task, between import random class ApiUser(HttpUser): # 模拟用户在一个请求完成后等待0.5到3秒再发起下一个请求 wait_time between(0.5, 3) def on_start(self): # 压测开始前每个模拟用户会执行这个方法 # 可以在这里做登录、获取token等初始化操作 self.token self.get_token() self.headers {Authorization: fBearer {self.token}} def get_token(self): # 简化版登录逻辑实际项目中需要配合参数化数据 resp self.client.post(/api/login, json{username: test, password: 123456}) return resp.json().get(token) task(3) def query_order(self): # 权重为3表示在100次任务中这个任务会被选中约60次 order_id random.randint(100000, 999999) self.client.get(f/api/order/{order_id}, headersself.headers) task(1) def create_order(self): # 权重为1创建订单的操作频率较低 payload { product_id: random.randint(1, 100), quantity: random.randint(1, 5) } self.client.post(/api/order, jsonpayload, headersself.headers)task后面的数字是权重权重越大执行频率越高。在真实业务场景中不同接口的调用频率差异很大必须通过权重来模拟真实比例。如果所有任务权重一样压测结果对真实系统的参考价值就会打折扣。4.2 参数化数据与登录态处理十万级并发用户如果都共用同一个账号大概率会把登录接口先打爆而且压测的业务数据也缺乏真实性。常规做法是准备一个数据池每个模拟用户从池中取一套独立的账号和业务数据。数据池可以是一份CSV文件、一个Redis列表或者一个数据库表通过Pandas或Redis客户端按需取数。我在实际项目中习惯用Redis列表作为数据池每个Worker启动的时候从Redis中批量取出一批账号存入内存列表模拟用户注册时从内存列表中pop一条。这样做的优点是启动速度快、不依赖外部存储的实时读取缺点是如果某个Worker崩溃它持有的那批账号会暂时“丢失”需要等压测结束后重新回收。登录态处理上最稳妥的方式是在on_start里完成登录并把token存入内存后续请求统一携带。需要特别注意的是token有效期如果短于压测时长会出现大量401报错压测数据全部失真。这时候需要在on_start中预留token刷新逻辑或者把压测时长控制在token有效期之内。4.3 逐步热身与稳态压测十万级并发不是一上来就全部拉满而是需要“阶梯式加压”的。这么做有两个原因一是让系统有个适应过程避免瞬间高流量把连接池、线程池打爆出现大量超时导致压测结果失真二是便于观察系统在不同并发阶段的表现找到性能拐点。# 启动Master节点 locust --master --hosthttps://your-api.example.com --web-port8089 # 启动Worker节点逐步孵化用户 locust --worker --master-hostmaster-ip --master-port5557启动之后在Web界面上把模拟用户数设置为50000孵化速率设置为每秒500。观察RPS、响应时间和失败率的变化平台稳定后再逐步提升到目标值。我在压测一个电商系统的订单接口时就是用这种阶梯加压的方式在并发数从3万到5万的过程中发现响应时间从80ms飙升到800ms系统在4.2万并发时出现明显的性能拐点这个数据对后续容量规划非常有帮助。5. 十万级并发压测的启动与实施Master/Worker的启动顺序与参数解析5.1 完整的启动流程与参数说明直接启动所有节点之前建议先做一次“预检”确认所有Worker能正常连接Master、脚本能正常加载、数据池能正常访问。我习惯用以下命令先启动一个小规模冒烟测试# 在Master节点上先行启动按CtrlC暂停后重新配置 locust --master --hosthttps://your-api.example.com --web-port8089 --expect-workers10--expect-workers参数的作用是让Master在Web界面上一直显示“等待Worker连接”的状态直到预期数量的Worker全部注册成功后再开始压测。这个参数在大规模压测时非常实用因为如果某个Worker节点启动失败你能第一时间在界面上看出来而不是等到压测开始后才发现某个节点“消失了”。启动Worker的命令# 每台Worker机器上执行数量根据机器资源决定 locust --worker --master-host192.168.1.100 --master-port5557如果你希望在一台物理机上启动多个Worker进程最便捷的方式是写一个循环脚本#!/bin/bash # 示例在一台16核机器上启动4个Worker for i in 1 2 3 4 do locust --worker --master-host192.168.1.100 --master-port5557 done每个Worker默认使用一个CPU核心这也是为什么Worker数量最好和物理机CPU核心数匹配的原因。如果一个16核机器只启动2个Worker等于说只用了2个核其余核全部闲置反过来如果在4核机器上启动8个WorkerCPU调度器会被迫频繁切换进程上下文反而降低整体效率。5.2 Web界面操作与实时监控所有Worker启动完成后打开Master的Web界面默认端口8089你会看到当前连接的Worker数量、总的模拟用户数、RPS、响应时间分布等指标。在“Edit”标签页里可以动态调整模拟用户数和孵化速率不需要重启任何节点。实际压测过程中我最关注的几个指标是RPS趋势是否平稳增长是否有断崖式下跌响应时间P99均值容易掩盖问题P9999分位响应时间更能反映真实用户体验失败率如果失败率超过5%说明系统已经接近瓶颈需要立刻停止加压Worker节点的CPU和内存使用率如果某个Worker的CPU提前打满说明分配到该节点的用户数超出了承载能力这套监控组合拳能帮助你在压测进行中快速定位问题源头。比如有一次我看到RPS波动剧烈逐个检查后发现是某个Worker节点所在的物理机因为流量过大被云服务商限流了TCP丢包严重大量请求失败。如果不看Worker级别的监控这个问题几乎不可能被发现。5.3 压测过程中的异常处理预案高并发压测最怕的不是系统性能差而是压测工具自身先崩了。常见的异常情况包括Worker节点OOM内存持续增长导致被系统杀掉表现为Web界面上某个Worker离线。处理方式是降低该节点的模拟用户数或者给机器加内存Master节点连接数打满当Worker数量特别多时Master需要维护大量ZMQ连接偶尔也会被连接数限制卡住。遇到这种情况调大Master的ulimit -n并尽量减少Worker数量、增加每个Worker的承载用户数端口被占用多次启动Locust后5557/5558端口可能处于TIME_WAIT状态无法释放。使用ss -lntp检查端口占用情况必要时等一会儿再启动这些异常基本都是基础设施层面的问题和业务代码无关但处理不好会让整场压测白费。我现在的习惯是每轮压测启动前先跑一个5分钟的“预压测”用1000个用户走一遍全流程确认所有环节正常再拉大规模。6. 常见问题与排查技巧实录真实踩坑经验分享6.1 Worker启动后一直显示“连接中”怎么办这个问题的排查路径非常固定。首先确认Master的5557端口是否在监听ss -tlnp | grep 5557没有输出说明Master没起来或者端口被占用。有输出但Worker连不上大概率是防火墙拦截了检查两台机器之间的连通性telnet 192.168.1.100 5557如果telnet不通问题就在网络层如果通了但Worker还是“连接中”检查Worker启动日志是否有报错尤其关注“Connection refused”和“Address already in use”这两类关键信息。6.2 十万并发运行十分钟后RPS骤降问题这是我在压测一个社交App的消息推送接口时遇到的情况。刚开始两分钟RPS稳定在8万左右但到第十分钟突然降到了3万而且Master界面显示大量请求超时。排查思路和结论如下首先确认不是被压测系统的问题——检查了业务服务器的监控面板CPU和数据库负载都正常。然后看Worker节点的系统日志发现TCP连接出现了大量的connection timed out进一步检查发现是压测机器本身的问题ip_local_port_range的默认值太低导致短时间内创建的大量连接耗尽了可用的本地端口。修改端口范围并开启tcp_tw_reuse后问题解决RPS恢复到正常水平。这个问题的核心教训是高并发压测的瓶颈往往不在软件层面而在操作系统网络栈的默认配置上。任何目标超过万级并发的压测先把内核参数调整到位再开始测试能少走很多弯路。6.3 不同Worker节点负载严重不均衡有时候你会在Web界面上看到某个Worker的RPS明显高于其他Worker用户数分配完全一致但实际负载却差了好几倍。这通常不是Locust的问题而是数据分布的问题——某个Worker分配到的账号数据恰好集中访问了某一台业务服务器而业务服务器之间的性能参差不齐导致该Worker的请求都是慢请求RPS自然就偏低。解决方法是把数据池按Worker维度切分而不是所有Worker共用一个Redis列表。这样每个Worker拿到的数据是均匀分片的请求分散到不同业务节点的概率也更高。另外如果业务侧做了分库分表压测数据的分布必须覆盖全部分片否则压测结果不具备代表性。6.4 常用排查命令速查表场景排查命令预期结果Master端口是否监听ss -tlnp | grep 5557能看到master进程监听Worker是否能连Mastertelnet master-ip 5557显示Connected文件描述符是否够用cat /proc/进程pid/limitsnofile软硬限制均为1048576本地端口是否耗尽netstat -aln | grep TIME_WAIT | wc -l数值远小于可用的端口范围Worker内存是否泄漏ps -o rss,pid -p WorkerPID | tail -n 1连续观察几分钟RSS不持续增长7. 十万级并发的进阶优化思路与经验总结7.1 硬件资源规划用多少机器才能撑起十万并发这是一道数学题但没有任何标准答案完全取决于你的业务请求模型。如果每个请求都是轻量级查询比如Redis读取响应时间10ms以内单个Worker承载5000并发完全没有压力但如果涉及复杂数据库查询单个请求响应时间在数百毫秒每个Worker光靠协程切换就能把CPU打满承载能力会急剧下降。按照中等复杂度的接口响应时间50ms左右来估算每个Worker进程对应一个核承载800-1500并发比较稳妥。十万并发大约需要80-120个Worker假如每台机器16核需要5-8台物理机。这个估算结果听起来很多但相比商业压测平台比如JMeter分布式集群需要几十台机器LoadRunner更夸张Locust的资源利用率已经非常高了。7.2 从压测结果反推系统容量规划十万级并发压测的核心价值不是证明“我能压到十万并发”而是通过这个压力梯度找到系统的性能拐点。压测结束后整理一张“并发数-响应时间-失败率”对照表这份数据是你做容量规划和扩容决策时最有力的依据。我在一次电商大促前压测中通过这份对照表发现订单接口在并发4万时P99响应时间突破了1秒触发了一系列连锁超时问题。业务侧针对这个瓶颈做了缓存优化把P99降到了200ms左右整个系统的支撑能力从4万提升到了7万。这种优化效果仅凭代码走查和静态分析是发现不了的必须靠压测数据来驱动。7.3 脚本与数据准备的企业级规范压测结束后把脚本、数据池、内核参数配置、启动命令整理成一份可重复执行的文档或自动化脚本这能让你在下次压测时省掉至少一半的准备工作。我个人习惯是写一份简单的Shell脚本一键完成“内核参数调整依赖安装Master/Worker启动”的全部操作队里其他同事拿到之后不需要看我远程演示也能独立复现整个压测过程。最后想分享一个压测理念压测工具本身不是瓶颈你对系统的理解深度才是。十万级并发压测做一次不难难的是每一次都能从压测结果中读出系统的真实短板。当你发现RPS从“平台期”突然跌落先别急着改代码分清楚是工具侧的问题还是业务侧的问题再有的放矢地调整。这个排查思路比任何压测工具都值钱。
返回列表