
说实话做并发测试这五个字看起来没什么门槛但真正要把万级并发这个需求落地很多人上来就翻车。我见过不止一次说好的万级并发本地压测最后变成压测机器先死机也见过压测报告报表漂漂亮亮上线后被业务一压就挂的尴尬。今天这篇东西我尽量把压测过程中总结出来的完整方案讲清楚从万级并发到底在测什么到操作系统需要调哪些参数再到压测工具选型、脚本设计、单机跑满一万并发、分布式压测兜底以及我实际踩过的坑。读完不敢说你能直接成为性能测试专家但至少不会再犯拿一万个线程硬怼这种最低级的错误。1. 为什么要做万级并发测试先搞清楚你要的到底是并发还是吞吐1.1 老板嘴里的一万人在线和工程师眼里的一万并发不是一回事这句话我几乎在每次项目启动会上都要重复一遍。老板说的一万人在线在大多数业务场景里指的是有一万个用户挂着APP或者网页他们可能在看首页、在发呆、在滑动屏幕但并不会每时每刻都往服务端发请求。这些用户对服务端造成的压力是在线连接数的压力不是请求处理的压力。而工程师谈的并发测试通常又分两种一种是并发用户数也就是同时保持会话状态的虚拟用户数量另一种是并发请求数也就是在同一个瞬间到达服务端的请求数量。这俩差了十万八千里。举个例子一万个用户在线每个用户平均每10秒才发一个请求那一瞬间到达的并发请求可能只有几百反过来如果某个业务接口平均响应时间要1秒系统每秒接收2000个请求那这个接口同时处于处理中的请求数就是2000左右。数学上有个很朴素的关系并发数约等于每秒请求数乘以平均响应时间也就是并发 TPS × RT。所以我每次接需求都先追问一句你说的万级并发具体是指哪一层是同时在线会话数还是每秒请求数还是同一时刻正在处理的请求数这三个目标对应的测试方案完全不同压测脚本写法也大不一样。1.2 万级并发下系统瓶颈的分布网络、线程、连接池、数据库各有各的账万级并发测试之所以难不是因为发一万个请求这个动作难而是因为请求到达后整个链路每个环节都有自己的上限。拿一个典型的Web服务来说请求先经过网关网关要维持连接然后到应用服务器应用服务器线程池要处理任务应用要去查数据库数据库连接池要分配连接如果中间有缓存缓存还要扛得住高并发下的读写。链路里每个环节都可能是一堵墙。我做过一个真实的压测应用服务器逻辑很简单就是查一次Redis再查一次MySQL接口平均响应时间原本只要50毫秒。可一旦并发线程数从500涨到5000响应时间直接飙到800毫秒。一开始我以为是业务代码慢后来才发现是数据库连接池只有20个连接5000个请求全在排队等连接。这就像餐厅只有20个服务员门口排队5000个客人——不是菜做得慢是压根没人接待。这类问题在低并发下完全测不出来因为20个连接足以应付500并发。只有把并发拉到万级连接池耗尽、线程切换开销、内存GC压力、网络带宽瓶颈这些隐藏雷才会一个个爆出来。这就是为什么万级并发测试的价值不在于证明系统能扛住一万而在于找出系统在哪个环节先扛不住。1.3 万级并发测试能发现什么从线程泄漏到缓存击穿我在做过的多轮万级压测中最常抓到的问题有这么几类第一类是资源泄漏。连接池、线程池、HTTP Client在低并发下泄漏几个资源根本看不出来但压到万级时每个请求泄漏一个连接几分钟就能把连接数吃干净系统直接hang住。第二类是缓存击穿和穿透。万级并发下如果有一批请求同时访问一个不存在的Key所有请求都会穿透到数据库。数据库瞬间收到上万条SQLCPU直接被打满。这个问题用1秒10个请求的常规测试根本不可能暴露。第三类是GC问题。Java应用在万级并发下对象创建和销毁速度极快Young GC变成常态严重时Old区也快速膨胀Full GC一来响应时间出现几秒的尖刺。压测报告上看平均响应时间可能还行但看P99分位线就会发现惨不忍睹。第四类是线程池拒绝和队列堆积。线程池满了之后新任务不是被拒绝就是排队等待表现为响应时间线性增长、错误率从0.1%开始往上跳。这类问题要配合线程池监控指标才能定位光看服务端CPU往往看不出异常。万级并发测试本质上是一次压力下的系统体检不光是测性能更是测稳定性、测资源管理、测代码里那些平时不会被触发的边界逻辑。2. 万级并发的前置条件先把操作系统和机器的姿势摆正2.1 资源估算文件描述符、临时端口、内存到底要多少跑万级并发之前我习惯先把账算清楚。这步不做脚本写得再好压测机也会莫名其妙挂掉。先说文件描述符。在Linux里一切皆文件TCP连接也占用文件描述符。客户端要发起一万个并发连接至少需要一万个文件描述符。而系统默认的ulimit -n往往是1024这也就是为什么很多人一跑高并发就报Too many open files错。压测机和被测服务器的文件描述符上限都得调建议直接跳过65535按压测峰值的两倍以上设置。然后是临时端口。客户端发起TCP连接时需要从本机端口范围里挑一个空闲端口。Linux默认的临时端口范围是32768到60999只有不到3万个端口。如果压测机在短时间内向同一个目标IP发起大量短连接端口会被占满之后的连接全部失败报错大多是Cannot assign requested address。更麻烦的是TIME_WAIT状态。TCP四次挥手结束后主动关闭连接的一方会进入TIME_WAIT状态默认要等2MSL也就是约60秒这个连接才能从系统里彻底消失。也就是说快速反复建立、短连接、关闭这一套动作每秒钟哪怕只产生1000个连接60秒内也有6万个连接卡在TIME_WAIT里占着端口。万级并发短连接压测两分钟就能把端口耗尽。内存方面倒不用太担心。每个TCP连接在内核里占用的内存大约是几十KB一万个连接也就是几百MB级别压测工具自身的内存另算Java系工具会重得多Go系的压测工具轻很多后面讲工具选型时细说。2.2 Linux内核参数调优清单照着抄基本不出事这里给一份我实测过、在普通8G内存的Linux服务器上就能支撑万级并发连接的关键配置# 文件描述符上限 ulimit -n 1000000 # 临时端口范围尽量拉大 sysctl -w net.ipv4.ip_local_port_range1024 65535 # 开启TIME_WAIT端口复用这行关键 sysctl -w net.ipv4.tcp_tw_reuse1 # 缩短TIME_WAIT时间 sysctl -w net.ipv4.tcp_fin_timeout15 # 增大TCP连接队列 sysctl -w net.core.somaxconn65535 sysctl -w net.ipv4.tcp_max_syn_backlog65535特别注意tcp_tw_reuse只在客户端主动发起连接时有效它允许内核在安全条件下复用处于TIME_WAIT状态的连接。压测机本身就是连接发起方所以这个参数对压测机意义巨大。被测服务器如果是NGINX这类反向代理tcp_fin_timeout也有助于加速处理大量短连接。还有一个老生常谈的tcp_tw_recycle千万别开。这个参数在新版内核里已经被移除旧版内核开它可能导致NAT场景下连接随机失败纯属添乱。服务端应用层的连接队列也要注意NGINX的listen指令里的backlog值和上面的somaxconn是对应的Tomcat的acceptCount、Netty的somaxconn类似如果应用层设置太小即使内核队列开大了连接还是会堆积在应用层外面。2.3 压测机的硬件要求说到底还是别让客户端抢了服务端的资源万级并发压测时压测机本身也在消耗CPU、内存和网络带宽。一个常见的错误是把压测工具和被压服务部署在同一台机器上。一万个并发请求进来压测工具先把CPU打满服务端的请求没被处理几个测试结果毫无意义。单机压测时选型就很重要。拿我常用的k6来说它基于Go编写事件驱动模型用一万个虚拟用户压测时压测机CPU占用大约在2到4核左右普通云主机就能扛。如果换用JMeter一万个线程跑起来压测机光线程调度就能吃到6到8个核跑出来的数据还夹杂着大量压测机自身的GC抖动。如果压测机配置一般我建议降低单机虚拟用户数把压测任务拆到多台机器上并行执行。具体怎么拆后面的分布式压测部分会详细讲。3. 工具选择单机万级并发的压测工具实测对比3.1 为什么JMeter在万级并发上总被人嫌弃先说JMeter它在Web接口测试领域很普及但在万级并发场景确实有心无力。最根本的原因在于JMeter的并发模型每个虚拟用户对应一个Java线程。线程是重量级资源每个线程默认栈大小1MB启动一万个线程意味着光线程栈就要占近10GB内存压测机内存直接告急。就算机器内存够大一万个线程的上下文切换开销也相当可观。我曾经在一台16核64GB的机器上用JMeter跑8000线程压测机CPU先冲到80%以上指标波动严重根本没法判断是服务端慢还是压测机自己在拖后腿。后来我把同样的场景切到k6压测机CPU掉到30%左右数据干净了很多。我并不是说JMeter完全不能用如果你坚持用它一定套分布式压测方案把负载分摊到多台JMeter从机上每台从机控制在2000线程以内是最稳妥的。但这样一来又要维护Agent集群、又要汇总结果复杂度飙升。为了省事我这里不推荐在万级并发场景下用JMeter。3.2 k6、wrk2、locust、ghz各自适合干什么我实际用过的压测工具里最推荐组合是k6加wrk2。k6是Go写的脚本用JavaScript编写核心引擎是事件循环模型一个进程可以轻松管理数万个虚拟用户内存占用很低。k6自带阈值判断、指标统计、CI集成是综合能力最均衡的选择。我单机用它跑过12000虚拟用户稳定性和数据准确度都令人满意。wrk2走的是另一条路。它是C写的基于epoll事件驱动单机就能压出几十万QPS性能测试领域的老手很爱用。缺点是不太适合复杂业务场景它的脚本能力很弱只能做简单的路径和header控制没法优雅地构造复杂请求体。所以我的用法是先拿wrk2快速摸一下服务的极限QPS在哪里再用k6做业务级、场景级的万级并发测试。locust是Python用户会优先想到的工具。早期版本用的是协程单机并发能力还可以但它跑高并发时Python解释器和GIL会限制压测机的吞吐量实际表现容易被k6甩开。P.S. locust适合那种需要大量脚本逻辑、和Python测试体系深度绑定的团队否则不建议在万级场景单机使用。ghz就是专门grpc压测的工具。如果你的服务是gRPC协议用HTTP压测工具是看不清真实性能的直接用ghz用法极简还支持LB、认证、元数据等配置。3.3 选型思路别只看最大并发要看你的指标靠不靠谱工具选型时我有个判断标准压测工具本身不能成为被测系统的一部分。也就是说压测机要在你设定的并发数下依然有富余的CPU和内存否则一切指标都是假的。拿一张表简单对比下几个工具的适用性工具语言并发模型单机万级并发表现脚本能力适合场景k6Go事件驱动goroutine强较强业务压测、CI集成、全链路场景wrk2Cepoll异步很强弱极速摸底、单接口极限压测JMeterJava线程弱强小型并发、复杂断言、分布式压测locustPython协程中强偏向脚本逻辑的压测ghzGogoroutine强无gRPC接口的专项压测选型定了之后还要想清楚脚本怎么写才算有效。下一页我会用一个完整的k6例子把从目标设定到指标判读的整个流程串起来。4. 从理论到实践单机跑通一万并发压测的完整过程4.1 先定目标再写脚本把TPS、响应时间、错误率钉死在纸面上写脚本之前第一件事是把测试目标量化。没有目标就开压等于没有导航就上路。我通常按这种格式定义目标并发用户数10000虚拟用户持续稳定运行10分钟业务目标核心接口TPS不低于3000响应时间P95小于500毫秒P99小于1秒错误率HTTP 5xx比例低于0.1%注意TPS目标和并发目标不能拍脑袋各定各的它俩通过平均响应时间挂钩。按照并发 TPS × RT的公式如果你想在10000并发下做到5000 TPS那平均响应时间就必须控制在2秒以内。如果接口平均响应时间是500毫秒那10000并发下理论最大TPS是20000但这是理想值实际上因为链路资源竞争会打折所以目标定得保守一点比如3000 TPS才是可实现的。场景设计时还要考虑业务比例。不要每并发都刷同一个接口那样测的是单接口极限不是系统真实承载能力。我习惯按真实业务流量配比来设计场景比如首页查询占30%、详情接口占25%、登录接口占5%、下单接口占5%剩余35%均匀撒到其他读接口上。这样压出来的数据才有业务参考价值。4.2 k6压测脚本详解一万个虚拟用户是怎么跑起来的下面这份脚本是我实际用过的简化版注释里写了关键思路import http from k6/http; import { check, sleep } from k6; import { randomString } from https://jslib.k6.io/k6-utils/1.4.0/index.js; // 生成一批真实可用的测试数据前缀 const userIdPrefix randomString(6, abcdefghijklmnopqrstuvwxyz); export const options { scenarios: { // constant-vus 用于稳定分摊一段固定并发的压力 load: { executor: constant-vus, vus: 10000, duration: 10m, }, }, thresholds: { // 错误率超过0.1%直接判定失败 http_req_failed: [rate0.001], // P95响应时间超过500ms判定失败 http_req_duration: [p(95)500], }, }; export default function () { // 每个虚拟用户携带不同的身份标识避免命中服务端的用户级缓存 const userId ${userIdPrefix}-${__VU}; const payload JSON.stringify({ userId: userId, itemId: Math.floor(Math.random() * 100000), traceId: randomString(16), }); const params { headers: { Content-Type: application/json, X-User-Id: userId, }, // 这行非常关键timeout设置要大于接口平均响应时间否则容易误判超时 timeout: 2s, }; const res http.post(http://target-server/api/order/query, payload, params); check(res, { status is 200: (r) r.status 200, response time 500ms: (r) r.timings.duration 500, }); // think time模拟用户思考停顿避免所有请求像机关枪一样连射 sleep(1); }脚本里有几个细节值得说一是vus: 10000配constant-vus表示全程保持10000个虚拟用户存在。每个虚拟用户跑完一次迭代后sleep 1秒再跑下一次所以实际TPS不完全等于VU数还要看响应时间。比如响应时间0.5秒sleep 1秒那每个VU每秒跑大约0.67次迭代10000个VU就是约6700 TPS这是合理范围。二是X-User-Id的构造。我特意给每个VU拼接了前缀和VU编号这样每个虚拟用户带的是不同用户身份。因为很多系统会对同一用户做缓存、做幂等校验如果不做区分服务器只会处理少量用户数据压出来的结果偏乐观。三是think time。很多新手跑压测喜欢去掉sleep让请求全速连射这种做法测的是接口极限压力不是业务场景压力。你真实业务里哪有用户每秒不喘气地连续点按钮带sleep才能体现业务并发下的真实压力分布。四是thresholds。k6跑完之后如果阈值不达标命令会返回非0退出码这个特性非常适合嵌到CI流水线里。4.3 跑完之后的指标判读平均响应时间是最大的谎言压测跑完k6会输出一张很长的摘要表。很多新手只盯http_req_duration里的avg这是最危险的读法。平均响应时间会被极端值拉偏一万个请求里只要有一个慢了10秒avg就可能被拉高几百毫秒而真正的性能抖动反而藏在分位线里。我读指标的顺序是这样先看http_req_failed的错误率如果高于阈值直接判失败不用往下看了。再看http_req_duration的p(95)和p(99)两个分位线这代表95%和99%的请求在多少毫秒内完成。P95抖动比avg大说明系统存在间歇性的慢请求常见原因是GC停顿、连接池排队、网络重传。接着看http_req_blocked这个指标表示请求在发起前等待连接的时间。如果它持续偏高说明连接数到了上限请求在排队等可用连接优先排查服务端的backlog和保持连接。最后看iterations总数除以运行时间算出实际TPS和设定的目标比对。下面是我某次压测的摘要片段http_req_duration..............: avg178.2ms min12.3ms med143.7ms max3852.1ms p(90)312.6ms p(95)451.2ms p(99)989.4ms http_req_failed................: 0.00% ≈ 0 http_req_blocked...............: avg1.5ms p(95)4.1ms iterations.....................: 602803 1004.67 per second这个数据的核心问题在p(99)虽然avg只有178毫秒P95也才451毫秒但P99已经接近1秒而且max到了3852毫秒。这说明有1%的请求被拖到了1秒上下典型的表现是服务端在高峰期出现了短暂排队。这种质量在低并发测试里根本不会出现恰恰是万级并发才能暴露的。另外跑万级并发时千万不要中途跑去看实时指标就急着下结论。第一个30秒到2分钟里系统可能还在经历JIT编译预热、线程池初始化、缓存填充指标会虚高。我一般等3分钟后再取稳定段数据做结论最好用--duration 10m长跑中间看趋势结束后再看汇总。5. 单机跑不动一万并发分布式压测方案落地5.1 判断瓶颈在客户端还是服务端先看压测机自己的资源水位单机压测一万并发如果你的压测机CPU已经持续85%以上内存吃紧或者报错Cannot assign requested address、Too many open files说明瓶颈出在压测机自己身上不是被测服务不行。这时候不要盲目降指标先确认是哪种资源不够。排查顺序是先看CPU再看内存然后看端口和文件描述符。CPU打满就降低单机VU数或者换更高效的压测工具端口耗尽就检查TIME_WAIT和tcp_tw_reuse设置文件描述符报错就调ulimit。这些都不行才轮到上分布式。分布式压测的核心思路很简单把一台压测机的负载拆给多台机器。每台机器分到3000到5000个VU跑完再把指标汇总到一处。5.2 k6分布式压测的正确姿势从脚本隔离到数据隔离k6本身不带中心化的集群管理但分布式压测完全可以手工实现而且够用。第一步准备压测脚本让每个压测节点拿到不同的用户数据分片。假设我准备用4台机器跑12000个VU每台机器3000个VU那我可以在脚本里按节点序号做数据隔离// 通过环境变量区分当前节点 const nodeId __ENV.NODE_ID || 0; const targetVUs __ENV.VUS || 3000;这个数据隔离极其重要。万级并发如果所有机器都用同一批测试账号服务端的用户缓存、登录互踢、分布式锁都会变成潜在干扰源。比如同一个手机号在两台压测机上同时登录服务端检测到异地登录就把前一个踢下线压测结果里就会出现大量未预期的401错误。我吃过一次大亏之后所有分布式脚本里都强制要求节点级数据分片。第二步各节点并行启动压测# 节点A NODE_ID0 VUS3000 k6 run --vus 3000 --duration 10m --summary-export node0.json script.js # 节点B NODE_ID1 VUS3000 k6 run --vus 3000 --duration 10m --summary-export node1.json script.js第三步汇总结果。最简单的做法是每台机器导出JSON最后合并更专业的做法是把各节点的指标推送到InfluxDB或者Prometheus再通过Grafana统一看板。k6还专门提供了一套云能力但那是商业方案这里不展开。团队内部用自建的k6各节点加InfluxDB的方案成本低且可控推荐优先考虑。5.3 分布式压测最常见的几个坑时钟不一致和数据倾斜分布式压测看着简单实际跑起来有不少坑。第一个坑是各节点启动时间不一致。四台机器同时敲命令总有快慢服务端的连接数曲线会像锯齿一样一会儿4300并发一会儿8500并发根本无法评估系统是否稳定支撑了12000并发。我建议所有节点统一用CI流水线调度或者至少先跑一个5秒的预热阶段等所有节点全部到位再进入正式压测阶段时间轴对齐。第二个坑是每台压测机的配置不一致。有的机器是8核有的是4核各自分到的VU数要结合机器规格来分配别平均分。比如8核机器分4000 VU4核机器分2000 VU肌肉多的多扛一点。第三个坑是结果汇总时口径不一致。k6各节点导出的摘要字段相同但汇总时别直接加总http_req_duration的avg值。正确的做法是保留原始数据在Grafana里按聚合查询计算全局分位数。如果只是简单把四个节点的avg加起来除以4数学上是错的你得到的那个平均响应时间毫无意义。另外还要警惕网络带宽。压测机和服务端之间的交换机、内网带宽有个上限。一万并发跑起来如果单个请求体是2KB那每秒可能产生几十MB的流量很多办公室内网的千兆交换机就已经顶上格了。一旦网络带宽饱和响应时间全部虚高你会误判为服务端性能不行。遇到这种情况检查压测机网卡的流量统计把带宽占用和响应时间关联起来看。6. 万级并发测试最容易翻车的细节实测复盘6.1 连接复用带来的假指标HTTP压测时k6默认会复用TCP连接也就是Keep-Alive。这符合实际生产环境的大部分情况——浏览器和应用客户端都会复用连接。但如果你测的接口在真实业务里是短连接场景比如每次请求都新建连接的第三方回调、老版本客户端等那么默认复用连接会掩盖建连开销测出来的数据会偏好看。我之前压测过一个网关服务用Keep-Alive跑P95只有180毫秒看起来非常棒。后来我改成每次请求断开连接数据立刻变成P95 620毫秒错误率也开始上升。原因就是网关在短连接场景下要高频处理TCP握手和内存中连接状态的创建销毁这个开销在长连接测试里完全没体现。所以压测前要明确一个选择你到底在测长连接场景还是短连接场景按业务实际情况来。如果是长短连接都存在的混合场景最好压两组数据做对比。6.2 测试数据不隔离不预热压出来的全是垃圾数据这个问题我排到最坑的细节之一。很多人拿一套固定的测试账号、固定的商品ID去压测服务端对这些数据做了缓存之后后面的请求基本都在打缓存TPS高得离谱一旦上线换成真实数据性能立刻现原形。我的做法是测试数据量至少是并发数的10倍以上。一万并发就给十万条不同的用户ID、订单ID、商品ID。压测前先做一轮2到3分钟的预热请求把缓存和数据表页填充满。不加预热直接上压测机系统要一边填充缓存一边应对全量请求容易在启动阶段缓存穿透把数据库打垮。业务数据要尽量模拟真实分布。比如电商场景针对热销商品和冷门商品的访问比例要设置梯度别所有用户都抢同一个商品。6.3 监控盲区把GC、线程池队列、锁等待全部纳入监控压测过程中如果只看服务端的CPU、内存、网络你可能会漏掉最致命的几个瓶颈。拿Java应用举例CPU曲线平稳不代表系统健康。GC线程频繁执行Full GC时CPU也会升高但业务线程实际上已经卡顿。我压测时遇到的经典现象是TPS每隔30秒周期性下跌一次持续时间2到3秒接着恢复。查监控发现每次TPS下跌都和Full GC时间吻合。这种情况下光调大JVM堆内存没用先看代码里有没有在请求链里做大量对象分配比如循环拼接大字符串、频繁创建大对象数组。线程池队列长度也是一样的道理它是高并发下最重要的信号之一。如果应用的线程池满了新增任务全部排队队列长度持续增长响应时间也会线性增长。有时候你看到响应时间从200毫秒爬到800毫秒不是代码变慢了而是任务在队列里等着呢。所以压测时一定要把这几路监控数据和服务端的性能指标放在一个时间轴上对比JVM GC次数和耗时Young GC / Full GC线程池活跃线程数、队列长度、拒绝任务数数据库连接池活跃连接数、等待时间Redis等中间件的慢查询和连接数服务端TCP连接状态分布ESTABLISHED / TIME_WAIT / SYN_RECV少了任何一路你都可能在错误的方向上排查半天。6.4 不要用轮询脚本压测接口缓存和限流会骗人有些团队压测下单接口用一个脚本固定每500毫秒请求一次。这种做法在万级并发下容易产生两个失真问题一个是轮询节奏太整齐所有请求按相同频率发出服务端接收到的请求时间分布极不均匀会有周期性波峰波谷。真实业务的请求到达服从随机分布不是这么规律的。我建议在脚本里给请求间隔加随机抖动比如100到300毫秒之间的随机sleep模拟真实用户的随机性。另一个是很多系统对同一账号、同一手机号做了限流和防刷。用固定账号轮询压不了多久就会被限流拦截错误率暴增数据完全失真。这个问题的解法就是前面提到的数据隔离和大规模测试数据池。6.5 压测中途不盯log只盯报表可能把错误全漏了万级并发下日志是另一个容易被忽略的压力点。很多应用在请求日志、异常堆栈上做了大量同步写盘操作。并发一高磁盘IO就被日志写爆业务线程阻塞在日志上响应时间大幅上升。我在压测前一定会做两件事一是把日志级别临时调到WARN以上避免高并发阶段的INFO日志泛滥二是把日志写入方式改成异步或者干脆压测期间输出到临时目录避开磁盘竞争。别心疼这些日志数据压测阶段的日志价值远低于一个干净的性能报告。7. 从压测结果到性能调优我的复盘思路7.1 把压测报告拉成一张动态清单按优先级排雷压测结束后的第一件事不是写报告而是整理问题清单。我习惯把问题按严重程度×发生概率划四个等级P0错误率超标、系统宕机、数据错乱。这类问题先讨论方案修不好系统不能上线。P1P99响应时间超标、缓存穿透导致数据库压力陡增。这类问题影响用户体验和系统稳定性排期修复。P2线程池队列积压、GC频繁。这类问题短期不致命但不解决流量再涨就会转级为P0。P3资源使用率偏高、告警阈值设置不合理。这类问题记录在案后续优化。整理完清单对照压测前的目标逐项打勾。是达标了还是超了超的比例是多少每一项都要有数据支撑不要在报告里写性能较优这种模糊词。7.2 优先处理哪些瓶颈从成本最低的开始优化顺序上我个人的经验是先做成本最低、收益最明显的第一排查连接池配置。把数据库连接池、HTTP连接池、Redis连接池的最大值拉到合理范围很多响应时间飙升的问题直接消失。注意连接池不是越大越好太大会拖垮数据库自身要根据实测结果逐步调整。第二加缓存。如果压测发现同样的数据被反复查询检查是不是缺少缓存或者缓存过期策略太激进。万级并发下缓存命中率每提升10%数据库压力可能下降一个数量级。第三做代码级优化。万级并发会把代码里所有低效的循环、不必要的序列化、重复的对象创建无限放大。用APM工具找出最耗时的调用链优先优化占比最高的几个方法。最后一招才是加机器横向扩展。扩容是必要的但扩完一定要重新压测验证因为提高并发上限的同时系统对其他中间件数据库、缓存、消息队列的压力也会同步增大瓶颈可能在扩容后转移了位置。7.3 压测不是什么一次性工作我自己做了这么多年性能压测最大的心得是并发测试不能只在项目上线前做一次。业务流量在涨代码在改中间件在换版本每一次变更都可能让原有的性能水平发生退步。所以我现在把压测集成到了日常开发流程里核心接口每两周跑一轮压力回归发布前跑一轮全链路万级并发测试线上大促前再跑一轮全场景压测。测试脚本和压测环境都统一沉淀在代码仓库里谁改谁负责评审记录清楚留档。这样压测从一次性活动变成长期投入系统性能的底线才能守得住。最后说一句实在话万级并发只是手段不是目标。你要做的不是在报告上写出支撑了12000并发这种漂亮的PPT数据而是在极限压力下找到系统最脆弱的那块板然后把它补上。真正上线时流量是逐渐涨起来的系统能否平稳地挨过流量尖峰、在局部故障时优雅降级这才是压测最值得关注的东西。