上个月帮朋友看一个电商后台,阿里云 16 vCPU 32GB 通用型,系统盘 ESSD PL1,MySQL 8.0.36 跑 CentOS 7.9,Nginx 1.24 做反代。促销前压测,用 sysbench 0.4.12 oltp_read_write --tables=20 --table-size=5000000 --threads=128 跑,QPS 只有 4380。办公室那台二手 Dell R730,E5-2620 v4 双路 8核16线程,64GB DDR4 2133,SATA SSD,同样版本 MySQL,QPS 5720。云主机贵一倍,慢 23%。不是云厂商虚假宣传,是基础软件层有一堆默认值在收税。
先别调 MySQL,先看 CPU 频率。云主机 vCPU 可能被限频,或者 governor 是 powersave。命令:cpupower frequency-info、cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor。如果显示 powersave,临时 cpupower frequency-set -g performance,永久写 systemd service 或 /etc/default/cpupower。我们那台云主机 governor 是 powersave,但 CPU MHz 显示 2.5GHz 固定,实际不是频率问题。接着 lscpu 看 NUMA:云主机 16 vCPU 被拆成 2 个 NUMA node,每个 8 vCPU,MySQL 默认没绑核,跨 node 访问内存延迟从 80ns 涨到 140ns。用 numactl --hardware 确认。解决:numactl --cpunodebind=0 --membind=0 mysqld 或者 systemd 里 CPUAffinity=0-7、NUMAPolicy=bind、NUMAMask=0。注意别把 Nginx 和 MySQL 绑同一个 node,会抢核。
内存这块最容易被忽略的是 THP 和 swappiness。CentOS 7 默认 transparent_hugepage=always,MySQL 8.0 在 THP 下可能产生 2MB 页分配延迟,尤其是 InnoDB buffer pool 大页。检查 cat /sys/kernel/mm/transparent_hugepage/enabled。建议 madvise,不是 never:echo madvise > /sys/kernel/mm/transparent_hugepage/enabled,然后 MySQL 配 innodb_buffer_pool_size=12G,留 4G 给系统页缓存和连接。vm.swappiness=1,不是 0,0 在 cgroup v2 下可能触发 OOM 而不是 swap。vm.overcommit_memory=1 只对 Redis fork 有意义,MySQL 别乱设。还有 vm.dirty_ratio=10、vm.dirty_background_ratio=5,ESSD 延迟低,别让脏页攒到 20%。我们改完,QPS 从 4380 到 4890,提升 11.6%,但还没到物理机。
IO 调度器是第二个分水岭。云盘 ESSD PL1 标称 5万 IOPS,但 fio 随机写 4k 测出来只有 1.8万。命令:fio -filename=/data/test -direct=1 -iodepth=64 -rw=randwrite -ioengine=libaio -bs=4k -size=10G -numjobs=4 -runtime=60 -group_reporting。看 iostat -x 1,%util 99%,await 1.8ms,aqu-sz 8.2。CentOS 7 默认 cfq 对云盘不友好,改成 none 或 mq-deadline:echo none > /sys/block/vda/queue/scheduler。MySQL 加 innodb_io_capacity=2000、innodb_io_capacity_max=4000、innodb_flush_neighbors=0、innodb_flush_method=O_DIRECT。如果云盘支持 io_uring,MySQL 8.0.36 在 Linux 5.10+ 用 innodb_use_native_aio=ON 走 libaio,不是 io_uring;别被半吊子教程带偏。改完 QPS 5320,接近物理机。最终把 Nginx 的 worker_processes auto; worker_connections 10240; keepalive_timeout 65; 和 reuseport 打开,P99 从 180ms 降到 96ms。
新观点:基础软件调优不是背参数,而是画一条 syscall 到硬件的路径图。路径是:应用 -> glibc malloc -> 内核 VFS -> IO 调度 -> 块设备 -> 云盘后端。任何一层默认值错配,都会让 16 核看起来像 4 核。glibc 2.17(CentOS 7)的 malloc 用 arena,128 线程时可能创建 8 个 arena,每个 arena 有锁和 64MB 虚拟内存碎片。用 MALLOC_ARENA_MAX=4 启动 MySQL 或 Nginx?MySQL 自己管内存,Nginx 可以用。但别乱设 MALLOC_ARENA_MAX=1,会锁竞争。验证:strace -c -p <pid> 看 futex 调用。如果 futex 占 30% 以上,先查连接池和锁,不是调 malloc。eBPF 用 biolatency-bpfcc、syscount-bpfcc 看真实瓶颈,比 top 有用。
还有一个隐藏税:cgroup v2 和容器。如果你用 Docker 跑 MySQL,docker run --cpuset-cpus=0-7 --memory=16g --memory-swap=16g --ulimit nofile=65535:65535。默认 --cpus 共享会漂移,--cpuset-cpus 才能真绑核。cgroup v2 下 memory.max 到顶先回收页缓存,不是直接 OOM;但 memory.high 设太低会 throttle。检查 cat /sys/fs/cgroup/memory.max、memory.current。Kubernetes 里 cpu_manager_policy=static 和 topology_manager_policy=single-numa-node 对 MySQL 这种延迟敏感型基础软件有用。但如果是 Nginx 无状态,别开 static,浪费核。结论:不是所有基础软件都要调优,先问它怕什么。MySQL 怕 IO 延迟和 NUMA 跨 node,Redis 怕 fork 和 swap,Nginx 怕连接数和文件描述符,Java 怕 GC 和容器内存限制。
给一份排查顺序,别抄参数:1) lscpu、numactl --hardware、cpupower frequency-info;2) sysctl vm.swappiness vm.dirty_ratio vm.overcommit_memory;3) cat /sys/kernel/mm/transparent_hugepage/enabled;4) iostat -x 1 + fio 随机写 4k;5) pidstat -w -u -d 1、strace -c -p;6) docker stats 或 kubectl top pod 看 cgroup 限制。参数只是结果,瓶颈才是原因。那台云主机最后 QPS 5720,和物理机打平,但 P99 还差 12ms,因为云盘后端多了一跳。能接受吗?看业务。