静默降频的裸金属是头蛮牛:宝塔面板 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 的本质区别是——你可以直接触碰电源管理、中断路由和内存分配层,别浪费了这些底层的控制权。