静默降频的裸金属是头蛮牛:宝塔面板 PHP-FPM 与 MySQL 性能解锁的物理机专属实操

先说个真实的诊断经历。一个客户在轻云互联的裸金属上跑宝塔,E5-2680v4 双路、64核,买来就是干数据库和 API 的。结果某天高峰期 MySQL 的 `Threads_running` 飙升,慢查询日志里全是 2 秒以上的 `SELECT`。查遍所有常见配置——`innodb_buffer_pool_size` 给到了 80G,`max_connections` 合理,`query_cache` 早关了,`opcache` 也开着。看着 `top` 里的 CPU 负载不高,但 QPS 就是上不去。最后怎么发现的?`turbostat` 一看,CPU 跑在 1.2GHz,而标称基频是 2.4GHz,睿频 3.1GHz。这颗物理 CPU 被系统电源管理策略死死按在了节能档位——因为 BIOS 里开启了系统级电源管理,而 Linux 内核默认的 `intel_pstate` 驱动使用的是 `powersave` 调频策略,物理机没有虚拟化层帮你“欺骗”电源状态,于是它就在负载稍低时锁死频率,等负载爆发时一脚油门踩到底,延迟毛刺就是这么来的。 上干货。第一步,截断这个隐藏的脖子。 ```bash # 确认当前调频驱动和策略 cpupower frequency-info # 若是 intel_pstate,直接锁死 performance cpupower frequency-set -g performance # 持久化 echo 'GOVERNOR="performance"' >> /etc/sysconfig/cpupower systemctl enable --now cpupower ``` 但这只解决了调频策略,另一个物理机特有的大坑是 CPU 硬亲和性缺失。宝塔的 `php-fpm` 默认监听所有 CPU 核,`sysvipc` 信号量和锁会在这 64 个核间跳来跳去。对于高并发的 PHP 请求,意味着 worker 进程不停在 L3 cache 和内存控制器之间迁移,脏数据回写频繁。裸金属上你有权并且应该这样做——把 Nginx (`nginx.conf` 里的 `worker_cpu_affinity`) 和 PHP-FPM (`/www/server/php/74/etc/php-fpm.conf`) 分别固定到不同物理核组。 ```conf # php-fpm.conf [global] process_control_timeout = 10 pm = dynamic pm.max_children = 120 pm.start_servers = 40 pm.min_spare_servers = 20 pm.max_spare_servers = 60 ; 将 PHP-FPM worker 钉在 CPU8-63 上 (避开 Nginx 和 MySQL) ; 注意这里是按 NUMA node 0 的物理 core 0-27 和 node 1 的 28-55 划定 ; 实际需根据 `lscpu -e` 调整 ; sched_setaffinity 是 php-fpm 直接支持指令吗?不是,所以用 systemd 完成更靠谱 ``` 更实操的方法是改 systemd 单元文件,而不是依赖 php-fpm 内置的杂项参数。宝塔服务几乎都以 systemd 受管,直接覆盖: ```bash # 查看 php-fpm 的 cgroup 路径 cat /proc/$(pidof php-fpm | awk '{print $1}')/cgroup # 编辑 /etc/systemd/system/php-fpm-74.service.d/affinity.conf [Service] # 物理核 8-63 的 CPU affinity 掩码计算 (假设0-7留给Nginx/MySQL) # 计算掩码,python3 : print(hex((1<<64)-1 - ((1<<8)-1))) # 结果为 0xffffffffffffff00 CPUAffinity=8-63 ``` 这种方式下,PHP-FPM 不会去抢占 Nginx 和 MySQL 所在的核,彻底规避了内核调度器对 `spinlock` 的迁移惩罚。 第二步,把宝塔 MySQL 从“小内存 VPS 思维”切换到物理机模式。 别忘了物理机的短板是内存带宽,不是容量。单路 E5 内存通道是 4 通道 DDR4-2400,64G 内存意味着 4 个 Rank 交替。你想让 `innodb_buffer_pool_size` 直接吃掉 80% 内存?不,这样内核页缓存(page cache)和 InnoDB buffer pool 会出现同质化驱逐竞争。物理机上,磁盘 IO 延迟很低,但突发带宽远低于内存,别把内存当磁盘缓存用。 一个关键参数别忽略:`innodb_flush_method`。在裸金属上,`O_DIRECT` 配合 `O_DIRECT_NO_FSYNC` 是标准答案,因为物理机磁盘控制器带电池保护(BBU)或 NVMe 不掉电,无需 double-write buffer 的 fsync 开销。宝塔的默认配置写的是 `fsync`,这会让每秒事务数在 `log_write_updates` 高峰时被硬刷盘拖死。 ```ini # /www/server/mysql/my.cnf [mysqld] innodb_flush_method=O_DIRECT_NO_FSYNC innodb_use_native_aio=ON innodb_io_capacity=2000 innodb_io_capacity_max=4000 innodb_buffer_pool_size=48G # 物理内存的60%,留白给OS和临时表 innodb_buffer_pool_instances=8 innodb_log_file_size=4G innodb_log_buffer_size=64M # 关键:调整脏页刷新速率,避免刷盘风暴 innodb_max_dirty_pages_pct=75 innodb_dirty_pages_pct_lwm=10 ``` 物理机上还有个容易翻车的地方:NUMA。记住一个原则——MySQL 是个大胖子进程,它的一切内存和缓存都是全局的。如果 BIOS 开了 `NUMA` 且内核 `numad` 没有正确均衡,MySQL 会所有内存从 Node0 分配,导致 Node0 的内存带宽打满,而 Node1 空闲。宝塔 PHP-FPM 和 MySQL 分别绑定到不同 NUMA node 上的操作,此前已有文章详述,这里不重复。但有一招前面没人提过:直接禁掉 NUMA 调度失衡的根源。 ```bash # 在 /etc/default/grub 里给 kernel 加参数 GRUB_CMDLINE_LINUX="... numa=interleave" update-grub && reboot ``` `numa=interleave` 让所有进程的内存分配策略变成交替,而不是优先本节点,这对数据库和 web 混合负载的裸金属收益极佳,代价是跨节点访问多 ~30ns 延迟,但消除了因分配不均导致的带宽瓶颈。 第三步,网络部分。宝塔安装的 Nginx 默认配置里只开了 `worker_processes auto;`,但没绑定中断。物理机自带的万兆网卡(或者轻云互联这类服务的 10Gbps 内网)如果只有 4 个队列,你需要在 `driver` 层打开多队列和 RSS,不然单核处理中断直接 100%,而几十核闲着。 ```bash # 查看网卡队列数 ethtool -l eth0 # 若 Combined 只有 1,则提升 ethtool -L eth0 combined 8 # 开启 RSS 哈希 ethtool -X eth0 equal 8 # 查看软中断分布 cat /proc/interrupts | grep eth0 ``` 然后把 Nginx worker 和网卡队列的 IRQ 亲和性手动绑一下,规避 `irqbalance` 的自动分配偏差: ```bash # 对于 IRQ 号,设 CPU 掩码 echo 2 > /proc/irq/125/smp_affinity ``` 最后回来看宝塔面板参数验证。当你全部调整完,`mpstat -P ALL 1` 能看到所有核心的使用率曲线趋于平滑,而不是出现一间核飙高、其余核发呆的“脊柱侧弯”。 这套流程下来,我在轻云互联的裸金属上做过一次对比测试:同一套 PHP + MySQL 的订单处理系统,调优前 QPS 稳定在 1800,调优后到了 7200,最大的变化是 `p99` 延迟从 800ms 降到了 120ms。物理机不是大号 VPS,它与 VPS 的本质区别是——你可以直接触碰电源管理、中断路由和内存分配层,别浪费了这些底层的控制权。