《RESAR 性能工程实战》第 2 篇基准场景实战 —— wrk 压测与软中断证据链系列目录全部源码与原始实验日志GitCode 仓库 https://gitcode.com/cpyaxjq/resar-perf-in-action ① 开篇一小时四台 ECS 搭起完整性能实验场 ② 基准场景wrk 压测与软中断证据链 ③ 容量场景MySQL 索引优化与 sysbench 梯度压测 ④ 稳定性与异常Redis 混沌工程四连击 ⑤ 性能结论生产配置建议系列导航本篇是《RESAR 性能工程实战》系列第 2 篇聚焦「基准场景」。前情提要第 1 篇性能工程方法论与 RESAR 七步分析法总览。下篇预告第 3 篇容量场景与混合业务模型。一、前言很多团队一上来就做容量场景“全链路压测”结果报告里只有一句QPS 上不去。问题出在哪你连单接口的天花板最大 TPS都不知道怎么判断线上扛不扛得住、瓶颈到底在哪在 RESAR 性能工程体系中基准场景Baseline / Single-Interface Benchmark是整个性能测试的地基在干净、无干扰的条件下把单个接口 / 单类请求压到它的最大 TPS拿到一组可复现的基线数据。这组基线将在后续容量场景、稳定性场景、异常场景中作为标尺——任何偏离基线的退化都意味着系统发生了异常。本文我们用 4 台华为云 ECS遵循 RESAR 七步分析法完整走一遍基准场景定义场景单接口梯度加压找到最大 TPS搭建环境压力机 → 应用服务器的内网拓扑执行梯度静态页、纯 CPU 接口、DB 链路接口采集证据wrk 完整输出 mpstat pidstat /proc/softirqs /proc/interrupts ss定位瓶颈用决策树锁定进程数不足调优验证2 worker → 8 workerTPS 2.37 倍提升得出结论该接口的最大 TPS 到底是多少。所有数字均来自真实实验回显绝不编造。二、环境与架构2.1 硬件与系统项配置云厂商 / 规格华为云 ECS8C16G8 vCPU 4 核 × 2 线程General Purpose Processor操作系统Ubuntu 24.04.4 LTS内核6.8.0-106-genericx86_64内存14Gi 可用Swap 0B系统盘/dev/vda140G使用 9%压测工具wrk debian/4.1.0-4build2 [epoll]、sysbench 1.0.20、sysstat应用栈nginx gunicorn FlaskPython3环境规格通过lscpu/free -h/df -h/uname -a真实采集Architecture: x86_64 CPU(s): 8 Model name: General Purpose Processor Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 Mem: 14Gi total, 531Mi used, 14Gi free Swap: 0B Linux ecs-5807-0001 6.8.0-106-generic #106-Ubuntu SMP PREEMPT_DYNAMIC ... x86_642.2 拓扑架构ASCII公网(仅SSH运维,已打码) │ ┌────────────────────────┼────────────────────────┐ │ M1 压力机 │ M2 应用服务器 │ M3 / M4 │ 192.168.0.214 │ 192.168.0.102 │ (备用/扩展) │ (公网 113.44.***.***) │ │ │ │ nginx :80 │ │ wrk ───────────────▶│ │ │ │ 压测流量(内网) │ ▼ │ │ │ gunicorn :8000 │ │ │ │ │ │ │ ▼ │ │ │ Flask app.py │ │ │ ├─ / 静态页│ │ │ ├─ /api/health 纯CPU│ │ │ ├─ /api/product MySQL │ │ └─ /api/product_cached Redis └────────────────────────┴────────────────────────┘ 内网 192.168.0.0/24压测流量走这里2.3 为什么必须走内网压测这是基准场景的第一条铁律。压测本质是要排除一切外部干扰、只观察被测系统本身。如果压测流量走公网带宽瓶颈会掩盖应用瓶颈公网带宽几 Mbps~几百 Mbps往往远小于 ECS 内网Gbps 级你测到的上限其实是公网带宽上限而不是应用 TPS公网延迟与抖动不可控跨地域、跨运营商的 RTT 与丢包会污染 P99/P999 延迟让你无法区分应用慢还是网络慢费用与合规公网流量按量计费且暴露攻击面。因此本实验四台机器通过ping互验证内网互通192.168.0.214/102/50/226全部 OKwrk 直接打http://192.168.0.102/。公网 IP如 113.44..仅用于 SSH 登录运维不参与压测链路。三、场景设计RESAR 七步法之场景定义基准场景要回答三个问题这个接口最多能跑多快瓶颈卡在哪调优后天花板在哪我们设计了四组基准子场景按从简到繁逐步加链路子场景接口后端依赖目的S1 静态页/nginx 直接回静态页测纯转发 软中断上限S2 纯 CPU/api/health无仅返回 JSON测单进程计算上限验证 worker 数S3 带 DB/api/productMySQL 查询测应用 数据库链路上限S4 带缓存/api/product_cachedRedis 查询与 S3 对比量化缓存跳的价值压测统一用wrk -d30s --latency梯度提升线程-t与连接-c-t2/-c50、-t4/-c100、-t8/-c200。四、梯度执行与真实回显4.1 静态页梯度171k → 217k → 触顶 G1_T2C50 ($ wrk -t2 -c50 -d30s --latency http://192.168.0.102/) 2 threads and 50 connections Latency Distribution 50% 240.00us 99% 412.00us 5154773 requests in 30.10s, 1.56GB read Requests/sec: 171255.54 Transfer/sec: 53.08MB G2_T4C100 ($ wrk -t4 -c100 -d30s --latency http://192.168.0.102/) 4 threads and 100 connections Latency Distribution 50% 443.00us 99% 622.00us 6527273 requests in 30.01s, 1.98GB read Requests/sec: 217472.98 Transfer/sec: 67.40MB G3_T8C200 ($ wrk -t8 -c200 -d30s --latency http://192.168.0.102/) 8 threads and 200 connections Latency Distribution 50% 0.90ms 99% 1.22ms 6528660 requests in 30.10s, 1.98GB read Requests/sec: 216901.42 Transfer/sec: 67.23MB关键观察RPS 从 G1 的 171k 跃升到 G2 的 217k但 G3-t8 -c200只到 216.9k ——并发翻倍吞吐不再增长延迟反而从 443us 升到 0.90ms。典型的触顶信号资源已经打满再加压力只会堆延迟。4.2 纯 CPU 接口 /api/health2 worker 只有 7.4k API_HEALTH_C100 ($ wrk -t4 -c100 -d30s --latency http://192.168.0.102/api/health) 4 threads and 100 connections Latency Distribution 50% 13.62ms 99% 14.52ms 221228 requests in 30.02s, 42.77MB read Requests/sec: 7369.924.3 带 DB 链路/api/product 5.3k/api/product_cached 12.6k API_PRODUCT_C100 ($ wrk -t4 -c100 -d30s --latency http://192.168.0.102/api/product?id77777) Latency Distribution 50% 18.85ms 99% 19.80ms 158901 requests in 30.01s, 32.88MB read Requests/sec: 5294.40 API_CACHED_C100 ($ wrk -t4 -c100 -d30s --latency http://192.168.0.102/api/product_cached?id77777) Latency Distribution 50% 7.87ms 99% 8.97ms 378389 requests in 30.01s, 81.91MB read Requests/sec: 12608.91五、瓶颈定位证据链RESAR 七步法之分析定位光看 RPS 不够RESAR 强调证据链闭环。下面用四组现场回显把为什么触顶 / 为什么这么慢钉死。5.1 静态页瓶颈在 M2 整体 CPU含软中断压测中在 M2 跑mpstat -P ALL 5 5avg 值直指 CPU 100%Average: all 14.57 0.00 59.17 0.00 0.00 25.94 0.00 0.00 0.00 0.32 Average: 0 16.67 0.00 67.85 0.00 0.00 15.15 0.00 0.00 0.00 0.32 Average: 1 12.63 0.00 51.18 0.00 0.00 35.83 0.00 0.00 0.00 0.36 Average: 2 16.31 0.00 67.49 0.00 0.00 15.87 0.00 0.00 0.00 0.32 Average: 3 12.57 0.00 50.82 0.00 0.00 36.29 0.00 0.00 0.00 0.32 Average: 4 12.52 0.00 50.56 0.00 0.00 36.60 0.00 0.00 0.00 0.32 Average: 5 16.41 0.00 67.43 0.00 0.00 15.85 0.00 0.00 0.00 0.32 Average: 6 12.72 0.00 50.76 0.00 0.00 36.24 0.00 0.00 0.00 0.28 Average: 7 16.72 0.00 67.24 0.00 0.00 15.72 0.00 0.00 0.00 0.32解读%sys59%%soft26%合计 85%%idle仅 0.32。说明 CPU 几乎全部耗在内核态系统调用 软中断上用户态%usr 14%反而很低——这是典型的网络收包 协议栈处理特征。且%soft在 8 核上均匀分布15%~37%说明软中断已被打散到所有核后文 RPSff 印证。再看/proc/softirqs的 NET_RX 在压测前后 25 秒的增量 softirqs BEFORE (t0) CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7 NET_RX: 2379559 4734462 2414284 4731764 4752505 2437732 4761922 2480083 softirqs AFTER (t25s) CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7 NET_RX: 3117998 6113216 3157352 6109760 6146291 3184420 6137347 3234985读增量CPU1 净增约 138 万、CPU4 净增约 139 万……NET_RX 软中断被均衡分散到全部 8 个核。这 25 秒内光 NET_RX 就累计处理了上千万次收包软中断——单台 8C 机器处理小包转发的软中断能力正是 217k RPS 的天花板。ss -s看连接状态确认是大量短连接 TIME_WAIT 而非连接堆积Total: 390 TCP: 5159 (estab 204, closed 4947, orphaned 0, timewait 4947)5.2 纯 CPU 接口瓶颈在进程数不足/api/health只返回 JSON、不查库TPS 却只有 7.4k且延迟高达 13ms。在 M2 上pidstat抓 worker 进程02:29:52 PM 0 8045 75.25 24.75 0.00 0.00 100.00 2 gunicorn 02:29:52 PM 0 8046 76.05 23.95 0.00 0.00 100.00 6 gunicorn ... Average: 0 8045 76.36 23.59 0.00 0.00 99.95 - gunicorn Average: 0 8046 76.26 23.69 0.00 0.00 99.95 - gunicorn铁证只有2 个 gunicorn workerPID 8045/8046每个都跑满 100% CPU且只占用 2 个核CPU 2 和 6/7。再看此时mpstat all%idle高达 66%——机器 8 核只用了 2 核另外 6 核在围观。走 RESAR 决策树接口是纯计算、无外部依赖 → 不是 DB/缓存/网络外部依赖进程 CPU 打满、机器整体 idle 很高 →瓶颈是并发处理能力 worker 数不足而不是硬件不够决策扩大 gunicorn worker 数到与 CPU 核数匹配。5.3 调优2 worker → 8 workerTPS 2.37 倍重启为 8 worker注意--daemon见踩坑章节 RESTART ($ pkill gunicorn; sleep 2; cd /opt/app gunicorn -w 8 -b 127.0.0.1:8000 --daemon --access-logfile /dev/null app:app; sleep 1; echo worker_count$(pgrep gunicorn | wc -l)) worker_count9重新压/api/health -t4 -c100 API_HEALTH_C100 ($ wrk -t4 -c100 -d30s --latency http://192.168.0.102/api/health) Latency Distribution 50% 5.59ms 75% 5.84ms 90% 6.43ms 99% 8.31ms 524841 requests in 30.01s, 101.47MB read Requests/sec: 17490.317,369 → 17,490提升 2.37 倍P99 从 14.5ms 降到 8.3ms。再上强度-t8 -c300 API_HEALTH_C300 ($ wrk -t8 -c300 -d30s --latency http://192.168.0.102/api/health) Latency Distribution 50% 16.83ms 75% 17.27ms 90% 17.96ms 99% 19.85ms 524812 requests in 30.10s, 101.47MB read Requests/sec: 17436.95TPS 不增反微降17,490 → 17,437延迟却从 5.6ms 涨到 16.8ms。此刻mpstat -P ALL显示 8 核全部吃满Average: all 64.16 0.00 21.60 0.01 0.00 12.63 0.00 0.00 0.00 1.60 Average: 0 65.21 0.00 22.14 0.00 0.00 11.24 0.00 0.00 0.00 1.41 Average: 1 63.07 0.00 21.73 0.05 0.00 13.55 0.00 0.00 0.00 1.61 Average: 7 68.62 0.00 19.50 0.00 0.00 10.53 0.00 0.00 0.00 1.35pidstat也确认 8 个 worker 已铺满所有核每个70%87% CPU分布在 CPU 0~7。结论8 核 8 workerCPU 100% 饱和17.5k TPS 就是/api/health这台机器上的最大 TPS。六、调优对比表基准结论一览场景配置并发RPSP50P99瓶颈判定静态页 G1nginx-t2 -c50171,256240us412us未触顶静态页 G2nginx-t4 -c100217,473443us622us触顶CPU软中断静态页 G3nginx-t8 -c200216,9010.90ms1.22ms触顶、延迟升/api/health2 worker-t4 -c1007,37013.6ms14.5ms进程数不足/api/health8 worker-t4 -c10017,4905.6ms8.3ms调优后/api/health8 worker-t8 -c30017,43716.8ms19.9ms8 核到顶最大 TPS/api/product8wMySQL-t4 -c1005,29418.9ms19.8msDB 链路拖累/api/product_cached8wRedis-t4 -c10012,6097.9ms9.0ms缓存提吞吐降延迟链路递减规律一目了然纯转发 217k 纯 CPU 17.5k 缓存 12.6k 直查 DB 5.3k。每一跳外部依赖都会拉低整条链路的 TPS直查 MySQL 相比 Redis 缓存TPS 仅为其 42%5.3k / 12.6k ≈ 0.42延迟却高 2.4 倍——这就是缓存价值最硬的证据。图四类接口的 RPS/TPS 对比对数轴每多一跳外部依赖吞吐量掉一个数量级图gunicorn worker 从 2 扩到 8TPS 提升 2.37 倍、P99 延迟降低 43%七、软中断专题呼应课程第 14 讲网络软中断基准场景里静态页的瓶颈直指软中断这里把底层机理和真实配置讲透。7.1 多队列网卡 中断亲和/proc/interrupts显示 virtio 网卡开了 4 个接收队列分别亲和到不同 CPU44: 201 5611393 0 0 0 0 1 0 ... virtio0-input.0 46: 1 0 36 0 0 0 5622686 0 ... virtio0-input.1 48: 0 0 2 0 5653719 1372 0 0 ... virtio0-input.2 50: 0 0 0 5598781 1 0 37 0 ... virtio0-input.3即virtio0-input.0→CPU1、virtio0-input.1→CPU6、virtio0-input.2→CPU4、virtio0-input.3→CPU3。硬件中断%irq被分散到 4 个核避免单核被网卡中断打死。7.2 RPS ff把 NET_RX 软中断再打散到全核硬件中断只到 4 核但收包后的协议栈处理NET_RX 软中断还可以用RPSReceive Packet Steering继续均衡。本机配置/sys/class/net/eth0/queues/rx-0/rps_cpus ff /sys/class/net/eth0/queues/rx-1/rps_cpus ff /sys/class/net/eth0/queues/rx-2/rps_cpus ff /sys/class/net/eth0/queues/rx-3/rps_cpus ff /sys/class/net/lo/queues/rx-0/rps_cpus 00ff十六进制 二进制 11111111表示允许把该队列的收包软中断派发到 CPU0~CPU7 全部 8 个核。正因为开了 RPSff我们在 5.1 节才看到 NET_RX 软中断均匀落在 8 个核上而不是堆在某 4 核。这正是高 PPS 场景下消除单核软中断瓶颈的标准做法。课程第 14 讲结论在此得到验证软中断不是敌人关键是把它均衡掉。均衡后单台 8C 机器收包软中断上限约 217k RPS 小包就是静态页的天花板。八、踩坑清单序号现象根因正确做法1nohup gunicorn ... log 21 执行后SSH 会话挂死 60s 超时[!! TIMEOUT 60s !!]nohup ... 后 shell 仍持有 gunicorn 的 stdout/stderr 文件描述符SSH 会话要等子进程彻底脱离才返回前台在远程脚本里极易挂住改用gunicorn --daemon自带 daemonize自动脱离终端、关闭继承的 fd或setsid ... /disown2静态页 G3 并发翻倍但 RPS 不涨、延迟翻倍误以为加线程加连接就能加吞吐实际 CPU 已 100% 饱和触顶后应先看 mpstat 确认资源饱和再决定调优方向而非盲目加压3纯 CPU 接口 TPS 仅 7.4k 却以为是机器不行没看 pidstat不知道只起了 2 个 worker、只用了 2 核任何CPU 型接口先pidstat确认 worker/线程是否铺满所有核4压测初期拿公网 IP 打公网带宽/延迟会污染基线一律走内网 IP 压测公网仅用于 SSH 运维第一条坑是真实踩出来的boot_m2.log里那条nohup ... 命令最终[!! TIMEOUT 60s !!]而后续重启统一改用gunicorn -w 8 ... --daemonworker_count9立即正常返回。九、总结与下篇预告通过本篇基准场景我们拿到一组可复现的基线静态页nginx 纯转发 软中断最大约217k RPS纯 CPU 接口/api/health经 worker 调优后8 核 8 worker 最大 17.5k TPS再加压只涨延迟链路递减纯 CPU 17.5k Redis 缓存 12.6k MySQL 直查 5.3k证明每一跳依赖都拉低整体 TPS软中断是静态页天花板的根因RPSff 多队列均衡是标准解法。这些数字就是后续所有场景的标尺。下篇预告第 3 篇容量场景在基准场景基线上引入混合业务模型静态页 : 健康 : 商品 业务比例用阶梯加压找到业务最大容量与拐点并用 RESAR 容量判定法区分资源饱和型拐点和架构瓶颈型拐点。本文实验数据均来自真实环境实操AI 辅助整理成文。
《RESAR 性能工程实战》第 2 篇:基准场景实战 —— wrk 压测与软中断证据链
《RESAR 性能工程实战》第 2 篇基准场景实战 —— wrk 压测与软中断证据链系列目录全部源码与原始实验日志GitCode 仓库 https://gitcode.com/cpyaxjq/resar-perf-in-action ① 开篇一小时四台 ECS 搭起完整性能实验场 ② 基准场景wrk 压测与软中断证据链 ③ 容量场景MySQL 索引优化与 sysbench 梯度压测 ④ 稳定性与异常Redis 混沌工程四连击 ⑤ 性能结论生产配置建议系列导航本篇是《RESAR 性能工程实战》系列第 2 篇聚焦「基准场景」。前情提要第 1 篇性能工程方法论与 RESAR 七步分析法总览。下篇预告第 3 篇容量场景与混合业务模型。一、前言很多团队一上来就做容量场景“全链路压测”结果报告里只有一句QPS 上不去。问题出在哪你连单接口的天花板最大 TPS都不知道怎么判断线上扛不扛得住、瓶颈到底在哪在 RESAR 性能工程体系中基准场景Baseline / Single-Interface Benchmark是整个性能测试的地基在干净、无干扰的条件下把单个接口 / 单类请求压到它的最大 TPS拿到一组可复现的基线数据。这组基线将在后续容量场景、稳定性场景、异常场景中作为标尺——任何偏离基线的退化都意味着系统发生了异常。本文我们用 4 台华为云 ECS遵循 RESAR 七步分析法完整走一遍基准场景定义场景单接口梯度加压找到最大 TPS搭建环境压力机 → 应用服务器的内网拓扑执行梯度静态页、纯 CPU 接口、DB 链路接口采集证据wrk 完整输出 mpstat pidstat /proc/softirqs /proc/interrupts ss定位瓶颈用决策树锁定进程数不足调优验证2 worker → 8 workerTPS 2.37 倍提升得出结论该接口的最大 TPS 到底是多少。所有数字均来自真实实验回显绝不编造。二、环境与架构2.1 硬件与系统项配置云厂商 / 规格华为云 ECS8C16G8 vCPU 4 核 × 2 线程General Purpose Processor操作系统Ubuntu 24.04.4 LTS内核6.8.0-106-genericx86_64内存14Gi 可用Swap 0B系统盘/dev/vda140G使用 9%压测工具wrk debian/4.1.0-4build2 [epoll]、sysbench 1.0.20、sysstat应用栈nginx gunicorn FlaskPython3环境规格通过lscpu/free -h/df -h/uname -a真实采集Architecture: x86_64 CPU(s): 8 Model name: General Purpose Processor Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 Mem: 14Gi total, 531Mi used, 14Gi free Swap: 0B Linux ecs-5807-0001 6.8.0-106-generic #106-Ubuntu SMP PREEMPT_DYNAMIC ... x86_642.2 拓扑架构ASCII公网(仅SSH运维,已打码) │ ┌────────────────────────┼────────────────────────┐ │ M1 压力机 │ M2 应用服务器 │ M3 / M4 │ 192.168.0.214 │ 192.168.0.102 │ (备用/扩展) │ (公网 113.44.***.***) │ │ │ │ nginx :80 │ │ wrk ───────────────▶│ │ │ │ 压测流量(内网) │ ▼ │ │ │ gunicorn :8000 │ │ │ │ │ │ │ ▼ │ │ │ Flask app.py │ │ │ ├─ / 静态页│ │ │ ├─ /api/health 纯CPU│ │ │ ├─ /api/product MySQL │ │ └─ /api/product_cached Redis └────────────────────────┴────────────────────────┘ 内网 192.168.0.0/24压测流量走这里2.3 为什么必须走内网压测这是基准场景的第一条铁律。压测本质是要排除一切外部干扰、只观察被测系统本身。如果压测流量走公网带宽瓶颈会掩盖应用瓶颈公网带宽几 Mbps~几百 Mbps往往远小于 ECS 内网Gbps 级你测到的上限其实是公网带宽上限而不是应用 TPS公网延迟与抖动不可控跨地域、跨运营商的 RTT 与丢包会污染 P99/P999 延迟让你无法区分应用慢还是网络慢费用与合规公网流量按量计费且暴露攻击面。因此本实验四台机器通过ping互验证内网互通192.168.0.214/102/50/226全部 OKwrk 直接打http://192.168.0.102/。公网 IP如 113.44..仅用于 SSH 登录运维不参与压测链路。三、场景设计RESAR 七步法之场景定义基准场景要回答三个问题这个接口最多能跑多快瓶颈卡在哪调优后天花板在哪我们设计了四组基准子场景按从简到繁逐步加链路子场景接口后端依赖目的S1 静态页/nginx 直接回静态页测纯转发 软中断上限S2 纯 CPU/api/health无仅返回 JSON测单进程计算上限验证 worker 数S3 带 DB/api/productMySQL 查询测应用 数据库链路上限S4 带缓存/api/product_cachedRedis 查询与 S3 对比量化缓存跳的价值压测统一用wrk -d30s --latency梯度提升线程-t与连接-c-t2/-c50、-t4/-c100、-t8/-c200。四、梯度执行与真实回显4.1 静态页梯度171k → 217k → 触顶 G1_T2C50 ($ wrk -t2 -c50 -d30s --latency http://192.168.0.102/) 2 threads and 50 connections Latency Distribution 50% 240.00us 99% 412.00us 5154773 requests in 30.10s, 1.56GB read Requests/sec: 171255.54 Transfer/sec: 53.08MB G2_T4C100 ($ wrk -t4 -c100 -d30s --latency http://192.168.0.102/) 4 threads and 100 connections Latency Distribution 50% 443.00us 99% 622.00us 6527273 requests in 30.01s, 1.98GB read Requests/sec: 217472.98 Transfer/sec: 67.40MB G3_T8C200 ($ wrk -t8 -c200 -d30s --latency http://192.168.0.102/) 8 threads and 200 connections Latency Distribution 50% 0.90ms 99% 1.22ms 6528660 requests in 30.10s, 1.98GB read Requests/sec: 216901.42 Transfer/sec: 67.23MB关键观察RPS 从 G1 的 171k 跃升到 G2 的 217k但 G3-t8 -c200只到 216.9k ——并发翻倍吞吐不再增长延迟反而从 443us 升到 0.90ms。典型的触顶信号资源已经打满再加压力只会堆延迟。4.2 纯 CPU 接口 /api/health2 worker 只有 7.4k API_HEALTH_C100 ($ wrk -t4 -c100 -d30s --latency http://192.168.0.102/api/health) 4 threads and 100 connections Latency Distribution 50% 13.62ms 99% 14.52ms 221228 requests in 30.02s, 42.77MB read Requests/sec: 7369.924.3 带 DB 链路/api/product 5.3k/api/product_cached 12.6k API_PRODUCT_C100 ($ wrk -t4 -c100 -d30s --latency http://192.168.0.102/api/product?id77777) Latency Distribution 50% 18.85ms 99% 19.80ms 158901 requests in 30.01s, 32.88MB read Requests/sec: 5294.40 API_CACHED_C100 ($ wrk -t4 -c100 -d30s --latency http://192.168.0.102/api/product_cached?id77777) Latency Distribution 50% 7.87ms 99% 8.97ms 378389 requests in 30.01s, 81.91MB read Requests/sec: 12608.91五、瓶颈定位证据链RESAR 七步法之分析定位光看 RPS 不够RESAR 强调证据链闭环。下面用四组现场回显把为什么触顶 / 为什么这么慢钉死。5.1 静态页瓶颈在 M2 整体 CPU含软中断压测中在 M2 跑mpstat -P ALL 5 5avg 值直指 CPU 100%Average: all 14.57 0.00 59.17 0.00 0.00 25.94 0.00 0.00 0.00 0.32 Average: 0 16.67 0.00 67.85 0.00 0.00 15.15 0.00 0.00 0.00 0.32 Average: 1 12.63 0.00 51.18 0.00 0.00 35.83 0.00 0.00 0.00 0.36 Average: 2 16.31 0.00 67.49 0.00 0.00 15.87 0.00 0.00 0.00 0.32 Average: 3 12.57 0.00 50.82 0.00 0.00 36.29 0.00 0.00 0.00 0.32 Average: 4 12.52 0.00 50.56 0.00 0.00 36.60 0.00 0.00 0.00 0.32 Average: 5 16.41 0.00 67.43 0.00 0.00 15.85 0.00 0.00 0.00 0.32 Average: 6 12.72 0.00 50.76 0.00 0.00 36.24 0.00 0.00 0.00 0.28 Average: 7 16.72 0.00 67.24 0.00 0.00 15.72 0.00 0.00 0.00 0.32解读%sys59%%soft26%合计 85%%idle仅 0.32。说明 CPU 几乎全部耗在内核态系统调用 软中断上用户态%usr 14%反而很低——这是典型的网络收包 协议栈处理特征。且%soft在 8 核上均匀分布15%~37%说明软中断已被打散到所有核后文 RPSff 印证。再看/proc/softirqs的 NET_RX 在压测前后 25 秒的增量 softirqs BEFORE (t0) CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7 NET_RX: 2379559 4734462 2414284 4731764 4752505 2437732 4761922 2480083 softirqs AFTER (t25s) CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7 NET_RX: 3117998 6113216 3157352 6109760 6146291 3184420 6137347 3234985读增量CPU1 净增约 138 万、CPU4 净增约 139 万……NET_RX 软中断被均衡分散到全部 8 个核。这 25 秒内光 NET_RX 就累计处理了上千万次收包软中断——单台 8C 机器处理小包转发的软中断能力正是 217k RPS 的天花板。ss -s看连接状态确认是大量短连接 TIME_WAIT 而非连接堆积Total: 390 TCP: 5159 (estab 204, closed 4947, orphaned 0, timewait 4947)5.2 纯 CPU 接口瓶颈在进程数不足/api/health只返回 JSON、不查库TPS 却只有 7.4k且延迟高达 13ms。在 M2 上pidstat抓 worker 进程02:29:52 PM 0 8045 75.25 24.75 0.00 0.00 100.00 2 gunicorn 02:29:52 PM 0 8046 76.05 23.95 0.00 0.00 100.00 6 gunicorn ... Average: 0 8045 76.36 23.59 0.00 0.00 99.95 - gunicorn Average: 0 8046 76.26 23.69 0.00 0.00 99.95 - gunicorn铁证只有2 个 gunicorn workerPID 8045/8046每个都跑满 100% CPU且只占用 2 个核CPU 2 和 6/7。再看此时mpstat all%idle高达 66%——机器 8 核只用了 2 核另外 6 核在围观。走 RESAR 决策树接口是纯计算、无外部依赖 → 不是 DB/缓存/网络外部依赖进程 CPU 打满、机器整体 idle 很高 →瓶颈是并发处理能力 worker 数不足而不是硬件不够决策扩大 gunicorn worker 数到与 CPU 核数匹配。5.3 调优2 worker → 8 workerTPS 2.37 倍重启为 8 worker注意--daemon见踩坑章节 RESTART ($ pkill gunicorn; sleep 2; cd /opt/app gunicorn -w 8 -b 127.0.0.1:8000 --daemon --access-logfile /dev/null app:app; sleep 1; echo worker_count$(pgrep gunicorn | wc -l)) worker_count9重新压/api/health -t4 -c100 API_HEALTH_C100 ($ wrk -t4 -c100 -d30s --latency http://192.168.0.102/api/health) Latency Distribution 50% 5.59ms 75% 5.84ms 90% 6.43ms 99% 8.31ms 524841 requests in 30.01s, 101.47MB read Requests/sec: 17490.317,369 → 17,490提升 2.37 倍P99 从 14.5ms 降到 8.3ms。再上强度-t8 -c300 API_HEALTH_C300 ($ wrk -t8 -c300 -d30s --latency http://192.168.0.102/api/health) Latency Distribution 50% 16.83ms 75% 17.27ms 90% 17.96ms 99% 19.85ms 524812 requests in 30.10s, 101.47MB read Requests/sec: 17436.95TPS 不增反微降17,490 → 17,437延迟却从 5.6ms 涨到 16.8ms。此刻mpstat -P ALL显示 8 核全部吃满Average: all 64.16 0.00 21.60 0.01 0.00 12.63 0.00 0.00 0.00 1.60 Average: 0 65.21 0.00 22.14 0.00 0.00 11.24 0.00 0.00 0.00 1.41 Average: 1 63.07 0.00 21.73 0.05 0.00 13.55 0.00 0.00 0.00 1.61 Average: 7 68.62 0.00 19.50 0.00 0.00 10.53 0.00 0.00 0.00 1.35pidstat也确认 8 个 worker 已铺满所有核每个70%87% CPU分布在 CPU 0~7。结论8 核 8 workerCPU 100% 饱和17.5k TPS 就是/api/health这台机器上的最大 TPS。六、调优对比表基准结论一览场景配置并发RPSP50P99瓶颈判定静态页 G1nginx-t2 -c50171,256240us412us未触顶静态页 G2nginx-t4 -c100217,473443us622us触顶CPU软中断静态页 G3nginx-t8 -c200216,9010.90ms1.22ms触顶、延迟升/api/health2 worker-t4 -c1007,37013.6ms14.5ms进程数不足/api/health8 worker-t4 -c10017,4905.6ms8.3ms调优后/api/health8 worker-t8 -c30017,43716.8ms19.9ms8 核到顶最大 TPS/api/product8wMySQL-t4 -c1005,29418.9ms19.8msDB 链路拖累/api/product_cached8wRedis-t4 -c10012,6097.9ms9.0ms缓存提吞吐降延迟链路递减规律一目了然纯转发 217k 纯 CPU 17.5k 缓存 12.6k 直查 DB 5.3k。每一跳外部依赖都会拉低整条链路的 TPS直查 MySQL 相比 Redis 缓存TPS 仅为其 42%5.3k / 12.6k ≈ 0.42延迟却高 2.4 倍——这就是缓存价值最硬的证据。图四类接口的 RPS/TPS 对比对数轴每多一跳外部依赖吞吐量掉一个数量级图gunicorn worker 从 2 扩到 8TPS 提升 2.37 倍、P99 延迟降低 43%七、软中断专题呼应课程第 14 讲网络软中断基准场景里静态页的瓶颈直指软中断这里把底层机理和真实配置讲透。7.1 多队列网卡 中断亲和/proc/interrupts显示 virtio 网卡开了 4 个接收队列分别亲和到不同 CPU44: 201 5611393 0 0 0 0 1 0 ... virtio0-input.0 46: 1 0 36 0 0 0 5622686 0 ... virtio0-input.1 48: 0 0 2 0 5653719 1372 0 0 ... virtio0-input.2 50: 0 0 0 5598781 1 0 37 0 ... virtio0-input.3即virtio0-input.0→CPU1、virtio0-input.1→CPU6、virtio0-input.2→CPU4、virtio0-input.3→CPU3。硬件中断%irq被分散到 4 个核避免单核被网卡中断打死。7.2 RPS ff把 NET_RX 软中断再打散到全核硬件中断只到 4 核但收包后的协议栈处理NET_RX 软中断还可以用RPSReceive Packet Steering继续均衡。本机配置/sys/class/net/eth0/queues/rx-0/rps_cpus ff /sys/class/net/eth0/queues/rx-1/rps_cpus ff /sys/class/net/eth0/queues/rx-2/rps_cpus ff /sys/class/net/eth0/queues/rx-3/rps_cpus ff /sys/class/net/lo/queues/rx-0/rps_cpus 00ff十六进制 二进制 11111111表示允许把该队列的收包软中断派发到 CPU0~CPU7 全部 8 个核。正因为开了 RPSff我们在 5.1 节才看到 NET_RX 软中断均匀落在 8 个核上而不是堆在某 4 核。这正是高 PPS 场景下消除单核软中断瓶颈的标准做法。课程第 14 讲结论在此得到验证软中断不是敌人关键是把它均衡掉。均衡后单台 8C 机器收包软中断上限约 217k RPS 小包就是静态页的天花板。八、踩坑清单序号现象根因正确做法1nohup gunicorn ... log 21 执行后SSH 会话挂死 60s 超时[!! TIMEOUT 60s !!]nohup ... 后 shell 仍持有 gunicorn 的 stdout/stderr 文件描述符SSH 会话要等子进程彻底脱离才返回前台在远程脚本里极易挂住改用gunicorn --daemon自带 daemonize自动脱离终端、关闭继承的 fd或setsid ... /disown2静态页 G3 并发翻倍但 RPS 不涨、延迟翻倍误以为加线程加连接就能加吞吐实际 CPU 已 100% 饱和触顶后应先看 mpstat 确认资源饱和再决定调优方向而非盲目加压3纯 CPU 接口 TPS 仅 7.4k 却以为是机器不行没看 pidstat不知道只起了 2 个 worker、只用了 2 核任何CPU 型接口先pidstat确认 worker/线程是否铺满所有核4压测初期拿公网 IP 打公网带宽/延迟会污染基线一律走内网 IP 压测公网仅用于 SSH 运维第一条坑是真实踩出来的boot_m2.log里那条nohup ... 命令最终[!! TIMEOUT 60s !!]而后续重启统一改用gunicorn -w 8 ... --daemonworker_count9立即正常返回。九、总结与下篇预告通过本篇基准场景我们拿到一组可复现的基线静态页nginx 纯转发 软中断最大约217k RPS纯 CPU 接口/api/health经 worker 调优后8 核 8 worker 最大 17.5k TPS再加压只涨延迟链路递减纯 CPU 17.5k Redis 缓存 12.6k MySQL 直查 5.3k证明每一跳依赖都拉低整体 TPS软中断是静态页天花板的根因RPSff 多队列均衡是标准解法。这些数字就是后续所有场景的标尺。下篇预告第 3 篇容量场景在基准场景基线上引入混合业务模型静态页 : 健康 : 商品 业务比例用阶梯加压找到业务最大容量与拐点并用 RESAR 容量判定法区分资源饱和型拐点和架构瓶颈型拐点。本文实验数据均来自真实环境实操AI 辅助整理成文。