容量评估是每个面向公网的服务都绕不开的话题。代码写得再优雅,如果说不清楚系统在多少并发量下会出现性能拐点,上线之后就只能被动救火。本文围绕Python并发系统的压测流程展开,从工具选型、脚本编写、指标解读到容量推算,完整走一遍可落地的操作路径。

一、压测前的准备:明确目标与压测模型
很多人一上来就装工具、跑脚本,压完拿一堆数字却不知道怎么用。正确的做法是先回答三个问题:压什么、怎么压、压到什么程度算结束。压什么指的是确定被测链路,是只压某个核心接口,还是压完整业务流程;怎么压指的是加压模型,常见的有固定并发压测(比如恒定200个虚拟用户持续请求10分钟)和阶梯加压(并发数从50开始,每分钟递增50,直到错误率超标);压到什么程度算结束,则需要提前定义停止条件,例如错误率超过1%或P99响应时间超过500毫秒。
还需要特别关注数据准备和环境隔离。压测流量打到生产库上是很危险的事,轻则污染数据,重则触发报警甚至影响真实用户。理想的做法是搭建一套与生产配置一致(或按比例缩放)的独立环境,同时准备好测试账号、测试数据集。如果必须压生产环境,务必使用流量标记(比如请求头带上压测标识),让服务端能识别并路由到影子库。
另外,压测客户端本身的性能也要提前评估。如果用一台普通的开发机去压一台高配服务器,很可能客户端先到达CPU瓶颈,测出来的QPS是客户端的上限而不是服务端的上限。经验做法是压测过程中持续观察客户端机器的CPU和内存占用,一旦客户端CPU超过70%,就应该考虑增加压测机数量或者使用更高效的压测方案。
二、使用Locust编写压测脚本
Locust是Python生态里最主流的压测框架,用代码定义用户行为,灵活度远超JMeter这类图形化工具。安装很简单,pip install locust即可。下面是一个针对典型Web接口的压测脚本示例,模拟了登录后查询列表的混合场景:
from locust import HttpUser, task, between
class ApiUser(HttpUser):
# 每个虚拟用户在两次请求之间等待1到3秒,模拟真实用户行为
wait_time = between(1, 3)
def on_start(self):
# 每个虚拟用户启动时先登录,拿到token
resp = self.client.post("/api/login", json={
"username": "test_user",
"password": "test_pass"
})
self.token = resp.json().get("token")
@task(3)
def query_list(self):
# 权重为3,查询列表是高频操作
self.client.get(
"/api/items?page=1&size=20",
headers={"Authorization": self.token}
)
@task(1)
def create_item(self):
# 权重为1,创建操作频率较低
self.client.post(
"/api/items",
json={"name": "loadtest_item"},
headers={"Authorization": self.token}
)
这个脚本里有几个关键点值得展开。一是wait_time,加上思考时间后测出来的才是真实用户行为下的吞吐量,去掉它则接近系统极限吞吐,两种模式各有用途,看你要回答什么问题。二是@task装饰器上的数字代表权重,上例中查询和创建的比例是3比1,贴近真实业务分布。三是on_start里做的登录只执行一次,token会保存在实例属性中复用,避免了每个请求都走一遍登录的失真场景。
启动压测使用命令行方式:locust -f locustfile.py --headless -u 200 -r 20 -t 10m --csv=report。参数含义分别是:200个总用户数、每秒启动20个用户、持续10分钟、结果输出为CSV文件。headless模式适合在服务器上无人值守跑长时压测,CSV文件则方便后续用pandas做数据分析,绘制QPS和响应时间随时间变化的曲线。
三、指标解读与容量拐点分析
压测跑完只是开始,真正的价值在数据解读。核心指标有四个:QPS(每秒请求数)、响应时间(重点看P50、P95、P99分位数)、错误率、并发数。很多人只看平均响应时间,这是容量分析里最常见的坑。平均值很容易被少数极慢的请求拉高或掩盖问题,一个P50为50毫秒但P99为3秒的系统,用户体验往往是灾难性的。评估容量时应该以P99(或P95)响应时间达到阈值作为拐点信号,而不是平均值。
容量拐点的典型特征是:随着并发数增加,QPS先线性上升,达到某个点后增速放缓,再往后并发继续增加,QPS不升反降,同时响应时间陡峭上升。这个由升转平再转降的点,就是系统的最大吞吐容量。以一个实际的阶梯压测结果为例,可以整理成下面的表格:
| 并发用户数 | QPS | P99响应时间(ms) | 错误率 | 结论 |
|---|---|---|---|---|
| 50 | 1200 | 85 | 0% | 余量充足 |
| 100 | 2350 | 120 | 0% | 接近线性扩展 |
| 200 | 3900 | 260 | 0% | 增速放缓,出现拐点迹象 |
| 400 | 4100 | 890 | 0.3% | 吞吐见顶 |
| 800 | 3600 | 2300 | 2.1% | 过载,性能下降 |
从表格可以看出,该系统的最大吞吐在4000 QPS左右,安全并发容量则应该取拐点之前的区间,比如按70%原则取2800 QPS作为日常可承接的流量上限。推算线上容量时还需要把QPS换算成业务语言:如果2800 QPS对应的接口是首页请求,按每次会话平均请求20个页面计算,大约可支撑每秒140个活跃用户,再结合峰值系数(日常峰值的3到5倍)规划扩容策略。
定位瓶颈同样重要。压测时要同步观察服务端的CPU、内存、磁盘IO、网络以及数据库的慢查询日志。Python服务最典型的瓶颈是CPU打满后上下文切换开销剧增,尤其是同步阻塞模型下线程数一多性能断崖式下跌。如果发现CPU没满但QPS上不去,通常是IO等待问题,考虑数据库连接池太小、下游依赖超时等因素。把压测数据和监控曲线放在同一时间轴上对照看,瓶颈往往一眼就能看出来。
四、突破客户端瓶颈:异步脚本与分布式压测
当被测系统性能较强时,单台Locust进程可能先撑不住。Locust默认使用gevent协程模型,单进程跑几千并发问题不大,但要模拟上万并发就得动用多进程加分布式。多进程方案是在一台机器上启动多个worker:
locust -f locustfile.py --headless -u 10000 -r 100 \
--master --expect-workers 4
# 另开4个终端分别执行
locust -f locustfile.py --worker --master-host=127.0.0.1
master节点负责任务分发和结果汇总,worker节点真正产生压力,四进程可以让单机压测能力提升接近四倍。如果还不够,把worker部署到多台机器上组成分布式压测集群即可,master-host参数指向master所在机器的内网IP。注意所有节点的脚本文件必须保持一致,否则会出现任务分发异常。
另一个思路是自己写异步压测脚本,用aiohttp配合asyncio实现,适合需要精细控制请求逻辑的场景:
import asyncio
import aiohttp
import time
async def worker(session, url, results, duration):
end_time = time.time() + duration
while time.time() < end_time:
start = time.time()
try:
async with session.get(url) as resp:
await resp.read()
ok = resp.status == 200
except Exception:
ok = False
results.append((time.time() - start, ok))
async def main():
url = "http://192.168.0.10:8000/api/items"
concurrency = 500
duration = 60
results = []
async with aiohttp.ClientSession() as session:
tasks = [worker(session, url, results, duration)
for _ in range(concurrency)]
await asyncio.gather(*tasks)
total = len(results)
errors = sum(1 for _, ok in results if not ok)
latencies = sorted(t for t, _ in results)
print(f"QPS: {total / duration:.1f}")
print(f"错误率: {errors / total:.2%}")
print(f"P99: {latencies[int(total * 0.99)] * 1000:.0f}ms")
asyncio.run(main())
自写脚本的优势是可控性极强,可以自由实现加权随机、请求签名、复杂事务等Locust里写起来别扭的逻辑;劣势是少了Web界面和现成的报告,适合有平台化压测需求的团队在其基础上二次开发。无论用哪种方式,压测结果都建议固化成报告存档,包含环境配置、压测模型、指标数据、瓶颈分析和容量结论,这样下次扩容或架构变更后就有了对比基线,容量评估才真正形成了闭环。