
先说个场景你花了两周写好的PHP接口上线第二天就被运营告知“页面好卡”。查日志、看慢查询、找第三方依赖一顿操作猛如虎最后发现连“这个接口正常时能扛多少并发”这个最基本的问题都没有答案。这不是在说某个同事这说的就是几年前的我。后来我养成一个习惯每个项目迭代前先拿 wrk 把当前系统压一遍把 QPS、RT、CPU、内存这四个数字记下来作为基线。今天这篇就是带零基础的同学把这套流程完整走一遍。这套流程做一次需要多久大概三十分钟。能解决什么问题以后再有人问你“系统性能怎么样”你手上有数据而不是只能回一句“应该还行吧”。1. 压测前的三个关键认知1.1 基线数据到底是什么基线Baseline说白了就是“现在这个系统的体检报告”。你不需要先理解高深的性能理论只需要记住一个场景假如下周上线了一个新功能改了代码加了个索引你觉得网站是变快还是变慢了没有基线数据你只能靠感觉而感觉是最不可靠的东西。有基线就不一样了——改完再压一次把新的 QPS、RT、CPU、内存和旧数据摆在一起一眼就能看出变化。再说这四个指标分别是什么。QPSQueries Per Second就是系统每秒能处理的请求数也叫吞吐量RTResponse Time是一个请求从发出到收到响应的耗时单位通常用毫秒CPU 是压测时服务器的占用率反映计算压力内存就是压测时服务器吃了多少内存反映资源开销。可以拿食堂打饭做类比。QPS 是这个食堂一个小时能打出多少份饭RT 是你从排队到拿到饭等了多久CPU 是后厨师傅有多忙内存是灶台锅碗的规模。四个数放一起食堂的运行状态基本就清楚了。对系统来说这四项数据就是最核心的健康指标也是后面所有性能优化的裁判。1.2 为什么我推荐 wrk 而不是 ab 和 JMeter压测工具市面上不少ab、siege、JMeter、locust都有各自的场景。我推荐新手先用 wrk理由很简单够快、够轻、输出足够直白。wrk 是 William Glozer 写的一个基于 C 语言的高性能压测工具核心是利用了 Linux 的 epoll 和 macOS 的 kqueue 事件机制单进程就能开出上万个连接。对比一下别的工具你就明白为什么选它abApache Bench是单线程的压测时压测机自己容易先到瓶颈导致测出来的数据偏低不太适合高并发场景。JMeter 功能确实全面还有图形界面但对“只想快速拿个基线数据”来说配置成本太高了光是线程组、监听器那一堆概念就够新手喝一壶。wrk 一条命令30秒结果就在眼前。后面熟悉了还可以通过 Lua 脚本模拟 POST、登录态、随机参数覆盖面足够日常使用。工具选型这件事我个人的原则是在满足需求的前提下用最简单的那个。wrk 就是这种“刚好够用且性能极强”的工具。1.3 压测环境选择先本地通再上服务器压零基础最容易犯的错是一上来就拿线上服务器做实验。正确思路是先在本地环境把 wrk 跑通确认 PHP 服务正常、参数理解无误再去目标服务器做正式压测。本地环境建议用 Linux或者 Windows 上的 WSLWindows Subsystem for Linux。wrk 本身就是 Linux 生态下的工具在 macOS 上也能编Windows 原生环境比较折腾用 WSL 是最省事的。正式压测的时机也要注意。拿生产服务器压测时尽量选业务低峰期并且提前跟团队打个招呼。我见过有人在大促前跑压测把数据库连接池打满导致线上业务跟着遭殃的。这种事情发生一次后面再想推性能测试团队同意率直接打五折。2. 环境准备与工具落地2.1 搭建一个最简 PHP 测试接口先准备一个被测的 PHP 接口。如果是零基础第一次跑不建议直接拿生产项目来压——项目里各种框架路由、数据库连接、第三方 SDK 都会影响结果出了问题你根本分不清瓶颈在哪。正确做法是先写一个最简单的接口把干扰降到最低。比如在/var/www/html下新建一个test.php?php header(Content-Type: application/json); $data []; for ($i 0; $i 100; $i) { $data[] md5((string)$i); } echo json_encode([ status ok, count count($data), time microtime(true), ]);这个接口故意加了一点计算逻辑。如果只返回一个纯文本压测结果几乎只反映 Nginx 和 PHP-FPM 本身的基础性能不是不能用但信息量太少。加上几十次md5计算能更接近真实业务接口的开销测试结果也更有参考价值。首页先跑通这个纯计算接口后续再逐步加上数据库查询、Redis 调用一层层测下去你就能清楚知道性能瓶颈到底在哪一层。这叫“由简入繁”不丢人反而是排查问题最靠谱的路径。2.2 安装 wrk依赖、编译、验证wrk 的安装有两种方式。第一种是直接用包管理器装在 Ubuntu/Debian 上sudo apt update sudo apt install -y wrk不过部分发行版的软件源里 wrk 版本比较老我更喜欢从源码编译能确保装到最新版过程也不复杂sudo apt install -y build-essential libssl-dev git git clone https://github.com/wg/wrk.git cd wrk make sudo cp wrk /usr/local/bin/ wrk --version编译过程最常见的报错是缺少libssl-dev解决办法就是先通过 apt 把依赖装上然后再make。全程大概两三分钟装完在终端敲wrk --version能看到版本信息就说明环境 OK。这里多说一句为什么 wrk 需要 OpenSSL 依赖因为它默认支持 HTTP/1.1 的 keep-alive并且允许压测 HTTPS 接口而这些能力底层都会用到 SSL 库。如果只是压 HTTP 接口不装也能编过去但装了就能cover住更多场景建议还是装上。2.3 wrk 核心参数速览wrk 的命令参数看起来就几个但用法上有几个容易踩坑的地方。先看参数表参数作用说明-t线程数决定 wrk 启动几个事件循环线程-c连接数总共开启多少个 HTTP 连接会被分摊到各线程-d压测时长比如30s、1m--latency延迟分布额外输出 P50、P75、P99 等百分位数据-H自定义请求头比如-H Accept: application/json-sLua 脚本用于 POST 请求、登录态等复杂场景新手最容易搞混的是-t和-c。很多人的第一反应是“线程数越大越好、连接数越大越好”其实不是。线程数决定了 wrk 自己用几个事件循环一般不建议超过压测机的 CPU 核心数太多连接数才是真正压到被测系统上的并发量。把-t从 4 加到 8QPS 不会因此翻倍但把-c从 100 加到 200服务器感受到的压力才会明显上升。新手建议的起步参数是wrk -t4 -c100 -d30s --latency http://127.0.0.1:8080/test.php先跑起来理解输出格式然后慢慢加连接数观察 QPS 和 RT 的变化曲线。3. 实战第一次完整压测与基线记录3.1 压测前记录系统空闲状态正式开始压测前先花一分钟记录当前系统的空闲资源。这一步很多人会忽略但它是基线数据的一部分。如果被测服务器压测前内存就已经用了 80%那压出来的数据和一台干净服务器压出来的数据完全没有可比性。我自己习惯在压测前依次执行这几条命令并把输出原样粘贴到日志文件里# 记录系统基础信息 uname -a cat /proc/cpuinfo | grep model name | head -1 nproc free -h # 压测前的空闲资源快照 vmstat 1 3文件名我会写成baseline-20260601.log这种格式带上日期和测试目的。这个习惯帮我省过不少事。有一次我隔了两周看一份压测数据发现服务器内核版本都不一样了幸好日志里记了时间线不然那组数据就成了无效数据。3.2 启动 PHP 服务与执行 wrk本地快速验证用 PHP 内置服务器最方便php -S 0.0.0.0:8080 -t /var/www/html但要知道PHP 内置服务器是单线程模型一个请求处理完才处理下一个。wrk 一上来并发连接多了请求就会排队测出来的 RT 会非常高。这个结果能用来验证代码是否有明显的阻塞问题但不能代表生产性能。所以本地跑通之后要模拟真实环境还是要用 Nginx PHP-FPM。Nginx 和 PHP-FPM 的配置这里不展开假设你已经能通过http://127.0.0.1:8080/test.php访问到接口。接下来执行压测命令wrk -t4 -c200 -d30s --latency http://127.0.0.1:8080/test.php压测期间建议另开一个终端同时跑vmstat 1 30这条命令每秒采样一次系统状态持续 30 秒刚好覆盖整个压测窗口。这样压测结束后你能把 wrk 输出的结果和系统资源曲线对应起来哪个时刻 CPU 飙高、哪个时刻上下文切换变多一目了然。3.3 读懂 wrk 输出QPS 和 RT 从哪看跑完之后终端会输出一份结果。我拿一个典型的输出示例来逐行拆解Running 30s test http://127.0.0.1:8080/test.php 4 threads and 200 connections Thread Stats Avg Stdev Max /- Stdev Latency 80.56ms 25.43ms 187.23ms 72.34% Req/Sec 634.25 45.13 712.00 78.00% Latency Distribution 50% 73.20ms 75% 89.31ms 90% 112.07ms 99% 162.43ms 76134 requests in 30.09s, 19.30MB read Requests/sec: 2530.84 Transfer/sec: 655.42KB这里最关键的是两行。第一行是Requests/sec也就是 2530.84这就是 QPS系统每秒实际处理的请求数。第二行是Latency的Avg也就是 80.56ms这是平均 RT。注意这个值包含了网络往返时间不只是 PHP 代码的执行时间。本地回环127.0.0.1压测时网络开销很小基本可以看成应用本身的耗时如果是跨机器压测网络延迟就会被算进去所以正式基线建议在同一内网里压。延迟分布这一段也很有用。99% 162.43ms的意思是“99% 的请求延迟都小于 162.43ms”。如果一个接口平均 RT 是 80ms但 P99 到了 350ms说明存在明显的长尾请求背后可能是垃圾回收停顿、数据库慢查询或者网络抖动。这个数据比平均值更能反映真实用户体验。还有一个非常好的自检公式也就是利特尔法则的简化版平均 RT ≈ 连接数 / QPS拿示例数据验证200 连接 / 2530.84 QPS ≈ 79ms和输出里的 80.56ms 基本对得上。如果 wrk 输出的 RT 明显大于这个值说明连接有空闲等待明显小于这个值多半是连接复用导致的偏差。这个公式能帮你快速判断一次压测结果到底靠不靠谱。3.4 同时记录 CPU 与内存别只看 top 瞬时值压测期间监控 CPU 和内存工具不用多两个命令就够。第一个是vmstat后台采样vmstat 1 30 vmstat-base.log第二个是内存写成一个小循环for i in $(seq 1 30); do free -h | grep Mem mem-base.log; sleep 1; done为什么不用 toptop 默认 3 秒刷新一次展示的是瞬时状态压测结束之后再去看可能已经错过峰值。vmstat 的us用户态 CPU和sy内核态 CPU合计就是压测期间的 CPU 忙碌程度。如果sy占比特别高说明系统调用和上下文切换很多往往是连接数过高或者 PHP-FPM 进程频繁创建销毁的征兆。内存则建议看free输出里的available列它才是真实可用的内存量。只看used很容易被 page cache 误导——Linux 会把空闲内存用作缓存这一部分在used里算“被用了”但实际是可回收的。4. 数据整理、基线归档与对比方法4.1 把原始输出整理成一张基线表压测结束后把 wrk 输出、vmstat 日志、free 记录汇总到一个地方。我习惯用 Markdown 表格每行一个测试场景。表格里除了结果数据至少包含四类信息测试环境、压测参数、指标数据、备注。下面是我项目里的一个示例测试环境压测参数QPS平均RTP99 RTCPU峰值内存峰值备注本地PHP 8.1内置服务器-t4 -c200 -d30s720278ms420ms35%180MB单线程模型结果仅参考本地NginxPHP-FPM 7.4-t4 -c200 -d30s253080ms162ms68%620MB未开OPcache本地NginxPHP-FPM 7.4-t4 -c500 -d30s2980168ms310ms84%780MB并发升高后排队长尾明显这个表格看起来简单但信息量很大。比如第三行QPS 虽然涨了接近 20%但平均 RT 从 80ms 涨到 168ms、P99 从 162ms 涨到 310ms这说明增加并发之后服务开始排队了延迟被显著拉高。只看 QPS 会得出“性能变好”的错误结论把 RT 一起看才能看清真相。零基础同学可能觉得字段太多了。我的建议是宁多勿少。过两周你再回来看这份记录大概率会忘掉当时的 PHP-FPM 配置、OPcache 是否开启、压测机到服务器的网络环境。这些信息在当时的你看来是常识在未来的你眼里全是关键变量。4.2 一份可复用的基线记录模板下面是我现在每个项目都会放的基线记录模板文件放在docs/perf/base.md# 性能基线 - 2026-06-01 ## 测试目的 记录当前版本 test.php 接口的性能基线作为后续优化对比基准。 ## 环境信息 - 服务器Ubuntu 22.044核8G - PHP8.1.10FPMpm.max_children50 - OPcache开启 / 关闭 - 被测程序test.php100次md5计算 JSON返回 - 压测机同机回环 / 内网IP ## 压测参数 wrk -t4 -c200 -d30s --latency http://127.0.0.1:8080/test.php ## 结果数据 - QPS2530 - RT均值80.56ms - RT P50 / P75 / P90 / P9973 / 89 / 112 / 162 ms - CPU峰值68% - 内存峰值620MB ## 备注 - 压测期间系统无其他任务 - 网络为回环RT基本等于应用耗时保存到项目的docs/perf目录下以后每次压测都往这个文件里追加新的一节。我遇到过这样一个案例某团队优化了一个 SQL 索引压测 QPS 确实从 500 涨到 900但上线后效果不明显。后来查才发现他们的基线是在 PHP 7.4 测的上线系统是 8.1两个版本的字节码差异本身就会影响性能。这就是基线文档里记录环境变量的价值。4.3 二次压测的对比判断结果变化多少才算有效基线建好之后后续每次改代码、调参数都建议再压一次。但新手拿到两轮数据后最容易犯的错是“看到 QPS 涨了就高兴跌了就慌”。这里有几个经验单次压测结果有随机噪声误差范围一般在 5% 到 8%。QPS 从 2000 涨到 2100大概率是噪声从 2000 涨到 2600才有讨论价值。对比时至少取三次结果的中位数不要用第一次。最好每次压测固定时段、固定参数。指标要联动着看。QPS 上涨但 RT 也跟着上涨可能是靠牺牲延迟换吞吐QPS 上涨且 RT 持平甚至下降才是健康的提升。举个例子。调整 PHP-FPM 的pm.max_children从 50 调到 200 后QPS 从 2200 提升到 3400但同时 CPU 从 70% 飙到 98%内存占用从 1.2G 涨到 3.8G。这说明系统瓶颈已经从“可用子进程不足”转移到了“CPU/内存资源上限”。这个信息对后续架构决策非常重要——你是该加机器还是该优化代码数据会告诉你答案。5. 常见问题与排查技巧实录5.1 wrk 启动失败与端口耗尽压测时遇到的最常见报错是bind() to 0.0.0.0:8080 failed: Address already in use这通常是上一次压测的连接还停留在 TIME_WAIT 状态。解决办法很简单换一个端口或者等几十秒再试。如果频繁出现端口被占满说明系统允许的临时端口范围太小了。可以临时调大sudo sysctl -w net.ipv4.ip_local_port_range1024 65535这个操作只需做一次永久生效需要写入/etc/sysctl.conf。不过说实话普通开发场景下遇到端口耗尽的概率不高真遇到了优先检查是不是压测脚本里没有复用连接导致每个请求都新建 TCP 连接。5.2 为什么两次压测结果差很多压测结果波动大的原因通常有三个。第一是服务器上有其他任务在跑。我之前在一台虚拟机上压测第一次 QPS 2800第二次直接掉到 1900查了半天发现是系统在后台做软件更新。压测前先top看一眼确认当前没有明显占用资源的进程。第二是网络波动。压测机到服务器的链路如果有抖动RT 数据会非常难看。排查方法很简单先压一个最基础的静态文件或者健康检查接口如果它本身的 QPS 都波动很大那问题出在网络环境而不是应用代码。第三是 wrk 线程数设置不当。线程数太少可能导致压测机自己喂不满服务器结果偏低。可以在不改变连接数的情况下把-t从 2 加到 4、8看 QPS 是否跟着涨。如果涨了说明之前瓶颈在压测机这一侧。5.3 PHP-FPM 进程的 CPU 定位方法压测时发现 CPU 高但top里看不出是哪个进程在消耗这种情况我用两条命令解决# 查看 PHP-FPM 各 worker 的 CPU 占用 top -H -p $(pgrep -d, php-fpm) # 或者用 pidstat 精确采样 pidstat -p $(pgrep -d, php-fpm) 1 10如果 CPU 集中在一个 worker 上多半是某个请求内部发生了阻塞操作比如同步调外部接口、死循环、或者锁等待。如果 CPU 均匀分摊在所有 worker 上说明服务器整体在满负荷运行那是容量问题不是代码问题。这个区分很重要。前者是性能缺陷找到那个请求修掉就行后者是容量规划需要加资源或者做架构优化。方向错了排查半天都是白费功夫。5.4 拿到基线后的四个优化方向有基线数据之后下一步怎么走结合我自己踩过的坑给零基础同学四个方向开 OPcache。很多 PHP 项目压测前压根没开开了之后 QPS 可能提升 30% 以上。原理是 PHP 脚本每次请求都要编译成字节码OPcache 能把编译结果缓存下来跳过重复编译。调 FPM 配置。pm.max_children不是越大越好要结合内存测算单进程平均吃多少内存 × 最大进程数 ≤ 总可用内存的 70% 左右。看慢日志和数据库慢查询。RT 出现“P99 远大于 P50”的情况优先怀疑慢查询和外部调用超时。把并发从 50 加到 2000记录每个并发档位的 QPS画一条曲线找到拐点。拐点就是系统的容量边界。但所有这些动作的前提都是你已经有一套完整的基线数据。没有基线的优化就像没有称体重的减肥全靠感觉。最后说点掏心窝的话。我最开始也觉得压测是运维的事自己写业务代码用不着。后来有一次线上活动接口在高峰期被打到几百毫秒延迟排查了一整天最后才发现是某个第三方依赖的超时时间设得太长。如果当时有基线数据这个问题在压测阶段一眼就能暴露出来。从那以后我每个项目都会建一个perf目录把 wrk 命令、基线数据、压测日志都放进去。这套习惯成本很低但带来的安全感是实打实的。零基础的同学不妨从今天开始花三十分钟跑通一次压测记录下第一批数据。等下次再有人问“系统性能怎么样”你手上有数心里不慌。