性能测试与调优:基准测试工具、瓶颈分析与系统调优

聊到高性能服务器,很多人第一反应是“把代码写快”。但说实话,我见过太多项目,代码写得花里胡哨,上线一压测,CPU 跑满、内存飙红、网络延迟高得离谱。嗯,性能不是“写”出来的,是“测”和“调”出来的。

这一章,我就带你走一遍我自己的调优流程。从工具到分析,再到内核参数,咱们一步步来。

基准测试工具:先摸清你的底牌

调优之前,你得知道系统现在什么水平。就像体检,先量血压再开药。我个人习惯用这三把刀:

ab(Apache Bench)

最轻量的 HTTP 压测工具。适合快速验证接口的吞吐能力。

# 100个并发,发10000个请求
ab -n 10000 -c 100 http://your-server/api

输出里重点看 Requests per secondTime per request。我在项目中遇到过,ab 测出来 QPS 很高,但换 wrk 一测就露馅——因为 ab 不支持长连接,测的是短连接场景。

注意:ab 的并发模型是阻塞式的,并发数太高时本身会成为瓶颈。别拿 ab 压 10000 并发,它自己先挂了。

wrk:现代压测利器

支持 Lua 脚本,能模拟复杂场景。我一般用它来测长连接和 Keep-Alive 下的性能。

wrk -t12 -c400 -d30s http://your-server/api

参数说明:12 个线程,400 个连接,持续 30 秒。输出里的 Latency 分布很重要——别只看平均值,看 P99 和 P999。

我的习惯:压测时同时开一个 tophtop,观察 CPU 和内存变化。如果 CPU 没跑满但 QPS 上不去,大概率是锁竞争或 IO 瓶颈。

iperf:网络带宽测试

很多时候性能瓶颈不在应用层,而在网络层。iperf 能帮你快速定位是带宽不够还是延迟太高。

# 服务端
iperf -s -p 5001

# 客户端
iperf -c server-ip -p 5001 -t 30 -i 1

输出看 BandwidthRetransmits。重传率超过 0.1% 就要警惕了——我曾经排查过一个线上问题,应用层代码优化了好几轮都没用,最后发现是网卡队列满了,丢包严重。

性能瓶颈分析:找到那个“木桶短板”

工具只是手段,分析才是核心。我一般按这个顺序排查:

性能瓶颈排查流程 1. CPU 使用率 2. 内存与缓存 3. 磁盘 IO 4. 网络延迟与丢包 top/htop 看 %CPU perf top 看热点函数 free -m 看内存占用 cachestat 看缓存命中 iostat -x 1 看 await iotop 看进程 IO netstat -s 看丢包 ss -ti 看 TCP 重传

举个例子。有一次我压测一个网关服务,CPU 只用了 30%,但 QPS 就是上不去。用 perf top 一看,热点函数居然是 __lll_lock_wait——说白了,锁竞争。换成无锁队列后,QPS 直接翻倍。

核心原则:先看 CPU 是否跑满。没跑满说明有阻塞(锁、IO、网络);跑满了再看热点函数是否合理(别把时间花在内存拷贝上)。

CPU/内存/网络调优:动手改代码

CPU 调优

  • 减少上下文切换:taskset 绑定 CPU 核心,避免进程在核间跳来跳去。我习惯把网络线程和业务线程绑在不同的核上。
  • 避免伪共享:多线程访问同一缓存行的不同变量,会导致缓存行失效。用 alignas(64)__attribute__((aligned(64))) 做缓存行对齐。
  • 用 CPU 亲和性:设置 sched_setaffinity,让关键线程独占核心。
// 缓存行对齐示例
struct alignas(64) AlignedData {
    int64_t counter;
    // 填充到 64 字节,避免伪共享
    char padding[64 - sizeof(int64_t)];
};

内存调优

  • 减少动态分配:用对象池或内存池。我写过一篇笔记,用 tcmalloc 替换 glibc malloc,小对象分配速度提升了 3 倍。
  • 利用局部性:把热数据紧凑排列。比如把频繁访问的字段放在结构体开头。
  • 大页内存:对数据库或缓存服务,开启透明大页(THP)或手动配置 2MB/1GB 大页,能减少 TLB miss。
避坑指南:我曾经在 Redis 上踩过 THP 的坑——透明大页会导致内存碎片,反而降低性能。建议对延迟敏感的服务关闭 THP:echo never > /sys/kernel/mm/transparent_hugepage/enabled

网络调优

  • 调整 socket 缓冲区:增大 tcp_rmemtcp_wmem,减少丢包重传。
  • 开启 SO_REUSEPORT:多进程监听同一端口,内核做负载均衡。我压测过一个 HTTP 服务,开启后吞吐量提升了 40%。
  • 使用 epoll 而非 select/poll:这个不用多说,C++ 网络编程的常识。

内核参数优化:让操作系统为你服务

应用层代码优化到极致后,瓶颈往往在内核。以下是我常用的参数调整:

参数 默认值 推荐值 说明
net.core.somaxconn 128 65535 监听队列长度,高并发下必须调大
net.ipv4.tcp_tw_reuse 0 1 允许重用 TIME_WAIT 状态的 socket
net.ipv4.tcp_fin_timeout 60 15 减少 FIN_WAIT2 等待时间
vm.swappiness 60 10 降低 swap 使用倾向,减少磁盘 IO
kernel.sched_migration_cost_ns 500000 5000000 减少 CPU 迁移频率,稳定缓存
# 一键应用优化参数(生产环境请逐项验证)
sysctl -w net.core.somaxconn=65535
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_fin_timeout=15
sysctl -w vm.swappiness=10
警告:内核参数不是越大越好。比如 tcp_rmem 设得太大,反而会占用过多内存,导致 OOM。我建议每次只改 1-2 个参数,压测对比后再继续。

嗯,调优这件事,说白了就是“测量-分析-修改-再测量”的循环。没有银弹,只有耐心。你想想看,一个参数调好了,QPS 涨 10%,十个参数就是翻倍——但前提是,你得知道瓶颈在哪。

我个人习惯把每次调优的参数和效果记下来,形成自己的“调优手册”。下次遇到类似场景,直接翻笔记,省时省力。

公众号:蓝海资料掘金营,微信deep3321