2026/10/10 19:23:09

压力测试与性能调优实战:从指标计算到MySQL瓶颈排查

压力测试与性能调优实战:从指标计算到MySQL瓶颈排查 压测这件事说破了就是“在出事之前制造一场事故”。我见过太多团队把压力测试当成发版前一天走形式的流程压完拿一张报告糊墙全红也没人管。最夸张的一次是给一个日活才两百多的内部系统压了百倍于日常的流量所有指标爆红大家围着屏幕沉默了半天除了证明“系统扛不住”什么问题都没定位出来。后来带团队把压力测试与性能调优的流程真正梳理落地我才体会到压测不是为了得到一个“能扛多少并发”的数字而是为了在真实流量冲过来之前把系统的弱点摸透把该调的参数调好把该修的代码修掉。这篇指南就结合我最近给核心业务系统做压力测试与性能调优的完整过程从方案设计、指标计算、工具选型到瓶颈定位和MySQL调优一条线讲清楚。适合还在“凭感觉压测”的开发、测试、运维同学也适合打算把压测纳入常态化流程的小团队直接抄作业。1. 压测之前先搞清楚你为什么要压1.1 压测不是走形式是在给系统做体检我先说一个经常被忽略的事实压测的最终产物不是压力测试报告是一张“系统弱点清单”。就像体检报告上写“血压偏高”没意义医生得告诉你少吃盐、多运动、必要时吃药压测报告里写了“P99超时严重”也没意义你得知道瓶颈在数据库连接还是应用线程池然后给出可执行的优化方案。我见过不少团队压测失败都不是技术问题是目标没定对。有人一上来就想压出“系统最大并发”结果用不合理的高并发把系统压垮得到的只是崩溃点不是优化方向。有人压测前不搭环境直接在生产库上造数据把线上搞得一团糟最后只能紧急回滚。还有人做了压测但没做监控压完之后只能看到TPS曲线看不到慢SQL、GC、锁等待这些关键证据想定位都无从下手。所以我的习惯是压测前先花半小时回答四个问题第一这次压测要验证什么结论第二用什么标准判断压测通过第三瓶颈证据从哪些监控数据里找第四结果给谁看是给老板做容量评估还是给开发定位问题。这四个问题答案不同压测方案完全不一样。给老板看重点是容量预估和资源冗余给开发看重点是分层监控和异常日志关联。1.2 压测前必须想明白的三件事第一件事定SLO。没有SLO的压测就是耍流氓。SLO不是“响应时间越快越好”而是具体可量化的目标比如“核心下单接口P99响应时间低于800ms错误率低于0.1%在每秒1000次请求的负载下”。有了这个基线压测结果才有通过与否的判定标准而不是看完报告各自解读。第二件事定基线模型。你要压的对象是单接口、核心链路还是全链路。单接口压测适合快速验证某个接口的容量上限比如用wrk压并发GET请求。核心链路压测适合验证一个完整业务流比如“用户登录→查商品→下单→支付”这条链路。全链路压测最重适合大促前验证整个系统但成本也最高。小团队我建议先做单接口和核心链路不要一上来就搞全链路否则环境复杂度可能比压测本身还大。第三件事定施压策略。施压策略决定了你在测什么是测容量拐点还是测稳定性。容量拐点要用阶梯加压比如从100并发起步每30秒加100观察TPS和响应时间的变化曲线。稳定性测试则要恒定在中高负载跑一段时间比如压到容量的70%持续30分钟观察内存、连接池、GC是否有缓慢恶化。很多人把这两个混在一起压出来的数据要么看不出拐点要么跑一会儿就崩谁也看不懂。1.3 工具选型从JMeter到在线压测平台工具选型取决于场景复杂度、团队熟悉度和是否要进CI。我自己的选择习惯是这样的单接口快速验证用wrk。wrk非常轻量一条命令就能压一个接口能迅速看出QPS上限和延迟分布。缺点是C语言脚本定制能力有限复杂业务场景基本无能为力。复杂业务场景用JMeter。JMeter生态成熟支持分布式施压配合插件可以做到很多事比如参数化、断言、事务控制、并发组等。缺点是资源消耗大单机施压上限不高需要部署施压机集群。做接口自动化或CI集成的团队建议看k6。k6脚本是用JavaScript写的可以嵌入GitLab CI或Jenkins流水线压测代码可以和业务代码一起做版本管理。不想维护施压机的团队可以用在线压测平台也就是很多人说的“信息压力测试网页版”。这种网页版压测工具的最大价值是省掉环境搭建注册后直接填URL、配并发参数平台帮你起施压节点适合没有独立压测环境的团队快速验证。缺点是大规模压测要收费以及数据出不来的话远程排查比较被动。如果你在coze上开发AI应用还有一条更省事的路coze平台自带压力测试模块不需要自己搭施压环境直接在模块里创建压测任务配置并发数和测试时长就能对Bot的对话链路做并发验证同时输出响应成功率、平均延迟、token消耗这些AI应用特有的指标。这类平台内置的压测模块本质就是把压测环境、施压节点、指标报表一起打包好把压测门槛降到了“填个表单”的级别对中小团队非常友好。2. 用数据说话指标和场景是压测的地基2.1 这些性能指标压测报告里必须有压测报告里最常见的错误是只贴TPS和平均响应时间这两个指标其实都不够。平均响应时间被少数慢请求拉高是常态TPS高也不代表用户体验好。我固定关注的指标有五类缺一不可第一类是吞吐量核心指标是TPS/QPS。注意TPS是事务数每秒QPS是请求数每秒一个事务可能包含多个请求。如果事务内串行调用3个接口TPS是QPS的三分之一这个差异直接决定压测数据怎么读。第二类是响应时间必须看百分位。P50、P95、P99。P99的意思是99%的请求响应时间不超过这个值它比平均值更能反映用户真实体验因为最能损害口碑的往往就是那1%的慢请求。比如一个接口平均RT只有150ms但P99到了2秒说明有大量长尾请求很难受这种问题平均值完全看不出来。第三类是错误率。错误率要看两层一层是HTTP层5xx错误、超时、连接拒绝另一层是业务层错误码。有些系统在过载时HTTP状态码还是200但业务Code已经在报错比如“库存查询失败”这种隐藏错误更危险。第四类是资源占用包括CPU、内存、磁盘IO、网络带宽。资源指标用来回答“瓶颈在硬件层还是软件层”。比如TPS上不去但CPU只有30%那问题大概率不是算力不够而是集群里某些线程在阻塞等待。第五类是连接数。包括数据库连接数、Redis连接数、Tomcat线程池活跃线程数。很多过载事故都是从连接数打满开始的。数据库连接打满表现为接口RT快速上升、报“Too many connections”线程池打满表现为任务排队RT线性增长。这两类现象出现时TPS早就停了增长。2.2 场景分级从基准压测到尖峰压测压测场景不是一上来就高压压死而是分级递进。我习惯按五类场景来做基准压测低并发比如10~20个并发跑5分钟验证功能正确性和基础性能数据。这一步主要是确认脚本没配错、断言没问题、响应结果符合预期。如果这一步跑出来P99就很高那后面压测全没意义得先排查环境或代码问题。负载测试阶梯加压每级保持一段时间比如100并发保持5分钟然后加100到200再保持5分钟直到预期负载。这一步的目的不是压垮系统而是找到TPS增长变缓的拐点也就是系统容量极限附近。压力测试继续增加并发超过系统容量找到崩溃点和恢复能力。重点看系统崩溃后是快速失败还是雪崩以及恢复后是否还能回到正常水位。尖峰测试模拟突刺流量。比如平时50并发瞬间拉到1000并发30秒再回到50观察系统是否会被突刺流量打懵。电商抢购、秒杀场景必须要做。稳定性测试在容量70%左右的负载下持续跑1小时以上观察内存泄漏、连接池耗尽、GC时间增长等慢性问题。很多系统能扛过5分钟压测但扛不过30分钟就是因为某些资源池没做好回收。值得注意的是场景之间的执行顺序也很重要先基准再负载再压力。直接跳到最后一步哪怕最终测出了最大并发你也分不清是哪个环节导致的瓶颈。2.3 数据准备和隔离决定了压测结果真不真压测数据不真实结果就没有参考价值。我踩过一个典型坑压测数据库里只有几百条商品数据结果商品查询接口走的是全表扫描QPS反而特别高因为表太小了数据全在内存缓冲池里。一上生产几千万条数据同一个接口直接响应超时。所以铺底数据一定要尽量接近线上真实规模至少要到线上数据量的80%。除了数据量数据分布也要模拟真实情况。比如订单状态如果线上有80%是已完成订单10%待付款压测库就不能全是已完成订单。否则SQL走索引的性能特征完全不同。我常用的做法是写脚本从生产脱敏抽样再按比例灌入压测库。数据隔离方面压测不能写生产库是铁律。我自己做压测时会用独立的压测环境从数据库到缓存到消息队列全部隔离。如果是全链路压测必须连生产那就要做数据染色给压测流量打上特殊标记在存储层和数据同步层把压测数据拦截掉。还要关掉对下游的写操作或改成Mock否则压测会把消息队列打爆误伤真实业务。3. 一次完整的压测执行实录3.1 先把环境基础打牢从连接数到带宽我开始压测前会先检查三件事哪件不过关都不开工。第一件是压测机本身的资源。压测机CPU被打满会直接影响施压精度导致压出来的TPS低于系统真实容量。我的经验是一台压测机上限制并发不要超过5000超过就加施压节点保证施压机本身不成为瓶颈。JMeter压测时还要关闭GUI模式GUI模式自带绘图组件非常耗资源改用命令行跑用jmeter -n -t test.jmx -l result.jtl方式执行。第二件是系统连接数限制。默认的ulimit -n可能只有1024压测一上并发就会报“Too many open files”。我执行压测前会调大到65535或更高临时设置用ulimit -n 65535永久写在/etc/security/limits.conf。同时检查被压系统的TCP连接队列如果出现“connection reset by peer”还要看net.ipv4.tcp_max_syn_backlog和somaxconn的参数。第三件是网络带宽估算。一次压测请求平均2KB响应体1000并发打5分钟产生的流量约是2KB×1000×300秒≈600MB这个量级没问题。但如果是大Payload接口比如一次返回200KB的列表接口1000并发打5分钟就是30多GB流量压测机和被压机的网卡都可能先撑不住。所以压大响应体接口前先把带宽算一笔账别让网络设备替你限了流。3.2 脚本配置和参数化让压测请求变得像真实用户以JMeter为例线程组配置是压测的核心。线程数、Ramp-Up时间、循环次数三个参数决定施压模型。比如我要模拟1000并发Ramp-Up设成100秒意思是每秒新增10个线程这样施压曲线是平滑爬坡而不是1000个线程瞬间全发避免施压机自己先把自己打崩。循环次数我通常设成“永远”配合Scheduler的Duration来控制压测时长这样脚本可以统一用“运行多少秒”来控制而不是去数循环次数。参数化是脚本里最容易偷懒、也最容易出错的地方。如果1000个并发用户都请求同一个商品ID缓存命中率和数据库查询路径都与真实场景完全不符。我常用的参数化手段有三种用CSV Data Set Config从外部文件读多组参数比如用户ID、商品ID用UUID函数或者__time函数生成唯一值模拟订单号、时间戳用JMeter的随机函数比如${__Random(1,1000000)}模拟随机ID。参数化之后还要把请求体里的固定值全部替换成变量这样才能逼近真实流量。断言配置也要适度。压测时不需要把接口返回的每个字段都断言断言越多消耗资源越大。我一般只做两种断言响应码断言比如期望200response duration assertion比如期望响应小于3000ms把明显超时的请求标出来。这样既不影响压测性能又能快速筛查异常。压测执行期间我还会打开三个监听器聚合报告Aggregate Report、响应时间百分位用Backend Listener或者PerfMon插件。但监听器本身也吃资源所以执行大批量压测时结果通过命令行输出再用InfluxDBGrafana展示不让JMeter在同一台机器上边压边绘制表格。3.3 压测进行中盯什么数据才有用压测执行过程中你最应该盯的其实不是压测工具界面上的响应时间波动而是被压系统的实时状态。我压测时至少同时开四个监控视角应用视角看应用进程CPU、内存、堆内内存、GC频率和GC停顿时间。用jstat -gcutil pid 1000每秒打印一次如果GC频繁且Full GC后老年代还涨不回去说明有内存泄漏或对象大量堆积。用jstack看线程状态如果大量线程在WAITING或者BLOCKED说明有锁竞争或者线程池打满。数据库视角看活跃连接数、慢查询数量、InnoDB锁等待、临时表数量。MySQL里执行SHOW ENGINE INNODB STATUS\G重点看LATEST DETECTED DEADLOCK和TRANSACTIONS段落。如果活跃连接数持续逼近max_connections那基本可以断定数据库是下一个要崩的点。中间件视角Redis命中率、延时、内存MQ生产者堆积量。如果压测链路里带RedisRedis的命中率从95%掉到60%要立刻检查是不是压测数据预热不够还是缓存过期策略有问题。业务日志视角压测过程中实时tail业务日志用grep ERROR统计错误量。错误率突然上升往往比响应时间变长更值得关注因为错误代表着请求处理失败用户端的感受是直接失败而不是变慢。我见过很多没用apprentice视角盯数据的压测结束后只拿到一张TPS折线图CPU、内存、GC日志、慢SQL全都没采集最后只能重新压一遍。所以强烈建议把压测期间的各种监控数据落盘哪怕暂时看不懂也要存下来排查问题时的回放价值极高。3.4 AI应用压测的特别之处如果你压测的对象不是传统Web接口而是AI应用比如coze上的Bot场景会有很大差异。传统接口压测关心响应时间和错误率就够了但AI应用压测要额外考虑几个变量一个是流式输出Bot回复是流式推送的不能用普通HTTP响应时间来看整体延迟另一个是LLM推理的不稳定性同样的Prompt在不同时间点调用响应延迟可能相差好几倍还有token消耗压测产生的token费用也是成本的一部分。coze压力测试模块这类平台内置的压测工具正好解决了一部分AI应用压测的复杂度。它直接把Bot对话作为压测对象不需要你自己去Mock LLM接口也不需要处理流式SSE的解析。配置好并发数、对话轮数、查询语句集平台会生成真实的对话压力然后输出响应成功率、平均响应时长、token消耗趋势等指标。这类模块适合做AI应用的容量验证和效果回归。但AI压测有个要注意的点LLM响应本身就是高度非线性的同样的并发下每一次压测结果可能差异很大。所以AI应用的压测结论不能只看一次结果要多跑几轮取趋势。而且因为LLM服务是外部供应商提供的你所做的应用层压测其实考验的是服务商的能力上限结合自身系统容量和供应商限流来定SLO比单纯追求高并发更现实。4. 从压测数据到MySQL调优4.1 分层定位瓶颈到底在哪一层压测数据拿到后调优才刚开始。我的习惯是从外到内逐层定位一层层排除先看客户端和网关层有没有限流或者超时设置过小再看应用层线程模型和代码热点最后才是存储层。这里举一个我最近遇到的实际案例。压测一个订单查询接口目标是1000 QPS下P99小于500ms结果只到300 QPS时P99就飙到2.5秒。第一步看应用CPU只有40%排除计算密集型瓶颈第二步看堆内存和GCYoung GC频繁但每次停顿只有几十毫秒排除GC停顿第三步看数据库发现活跃连接数冲到300慢查询日志里出现一条高频SQL单次执行3秒扫描行数12万。到这里瓶颈已经清晰数据库慢查询拖垮了接口。这个案例说明调优顺序不能乱。如果你先调应用代码调半天发现慢SQL还在等于白调。反过来如果你先看慢SQL可能发现只是缺了一个联合索引应用层根本不用动。所以压测后的第一步永远是拿监控数据做分层画像确认瓶颈在哪个环节再动手优化。4.2 MySQL性能调优的实操细节MySQL是存储层中最常成为瓶颈的环节我把常见调优点整理成了五个方向方向一连接数。max_connections设置太小并发一高直接报“Too many connections”。但这个参数不能盲目调大每一条连接都需要线程和内存。连接数设置需要结合应用连接池大小来估算比如应用有20个实例每个实例连接池50个连接那MySQL的max_connections至少得大于1000才够用。同时开启thread pool或者proxy中间层做连接复用比单纯调大max_connections更稳妥。方向二慢查询日志。压测期间打开慢查询日志long_query_time设为1秒配合mysqldumpslow分析前几条SQL几乎每次都能命中性能问题的源头。我压测时的习惯是压测结束立刻把慢查询日志导出压测现场没有定位到的问题靠慢查询日志回放也基本能补上。方向三索引优化。对慢SQL执行EXPLAIN看type字段。如果出现ALL全表扫描或者key为NULL说明索引没建对或没走索引。上面提到的订单查询案例慢SQL的WHERE条件是store_id和order_statustable扫描12万行给这两个字段建了联合索引后扫描行数从12万降到200执行时间从3秒降到30毫秒接口P99直接从2.5秒降到400ms。注意联合索引的字段顺序要遵循最左前缀原则区分度高的字段放前面。方向四InnoDB缓冲池。innodb_buffer_pool_size一般建议设置为物理内存的60%~75%如果这个值太小InnoDB频繁从磁盘读页TPS会被磁盘IO锁死。压测时观察磁盘读IOPS和缓冲池命中率命中率低于95%就要考虑调大缓冲池或者优化SQL减少扫盘范围。方向五锁等待和长事务。如果压测数据里出现大量UPDATE语句互相阻塞观察information_schema.innodb_trx和innodb_lock_waits两张表。长事务会持有锁不放导致其他事务排队。压测前要确保应用层的事务范围最小化不要在一个事务里查一堆数据再慢慢更新也不要在事务里调用外部HTTP接口不然每条请求都长时间占用连接和锁。4.3 常见问题速查与避坑经验最后整理一个压测和调优过程中最常见的现象-原因-排查手段速查表都是现场实操中高频遇到的压测现象可能原因排查手段TPS怎么加并发都不涨线程池打满或队列堆积jstack看线程状态查看RejectedExecutionException日志接口偶发超时GC停顿、慢SQL、网络抖动开启GC日志、慢查询日志对比超时时间点CPU一直100%死循环、正则回溯、索引失效全表扫描arthas或async-profiler抓热点方法数据库连接耗尽连接池配置太小、连接泄漏、慢SQL占用连接查活跃连接数曲线配合慢查询定位Redis缓存命中率骤降压测数据未预热、缓存过期策略问题查看命中率监控和过期key统计压测机本身CPU打满施压节点资源不足横向增加压测节点或降低单机并发压测结果每次差异很大数据倾斜、场景随机性、外部依赖抖动多次压测取中位数固定随机种子还有几个避坑经验值得单独说。第一压测时一定要固定随机数种子或者参数集否则两次压测结果对比没有意义。第二压测环境的机器配置要和生产一致云上小规格机器压出来的数据没有参考价值。第三压测结束要记得恢复所有改过的配置比如调大的连接数、关闭的慢查询开关否则这些“测试配置”带到上线就是隐患。我做压测这几年最大的感受是压测和调优不是一锤子买卖而是应该沉淀成一套可重复执行的流程。压测报告不只是贴给老板看更应该把每次压测的监控数据、慢SQL、优化措施写进知识库下次出现类似问题直接对照排查。把压测纳入版本发布流程里重大变更必须过压测这关这比临时抱佛脚抢救事故要省心得多。