wordpress

PVE 环境下 1500+ WordPress 站点的性能救赎之路

—— 从 SAS RAID5 到 NVMe 直通与内网万兆互联架构

以下内容根据实际生产环境进行编写,部分内容参考了互联网上的内容,以及经过了AI的润色,请结合自己实际情况进行调整。

1. 现状与瓶颈诊断

环境背景:

  • 物理机:DELL EMC 96C / 256G / 10TB SAS / RAID 5。
  • 业务:单台 Debian 虚拟机(88C/200G)承载 1500 个 WordPress 站点。
  • 现象:尽管接入 Cloudflare 清洗流量,CPU 长期处于 I/O Wait,网站响应缓慢,经常出现 502/504。

根本原因分析:

  1. 存储介质瓶颈:机械硬盘 RAID 5 存在严重的写惩罚 (Write Penalty)。SAS 盘单盘 IOPS 约 150-200,面对 1500 个站点的并发 MySQL 写入(Binlog, Redolog, Undo Log)及 PHP Session/日志写入,磁头寻道时间已耗尽。
  2. 架构缺陷:Web 服务与数据库服务混跑在同一虚拟机,且共享同一组物理磁盘,造成严重的 I/O 争抢。
  3. 云数据库不可行:本地 Ping 云服务器延迟 5.8ms。WordPress 单页面渲染涉及 20-100 次串行 SQL 查询,5.8ms 延迟会被放大至 300ms+,导致 PHP-FPM 进程阻塞积压,瞬间打满内存。

最终决策:放弃云数据库,采用 本地 NVMe 直通 + PVE 内网互联 方案。


2. 硬件改造:引入企业级 NVMe

选型原则

  • 严禁使用消费级 SSD(如 Samsung 980/990 Pro)。无掉电保护 (PLP),数据库断电必丢数据;且缓存写满后性能暴跌。
  • 必须组 RAID 1。单盘无冗余是生产环境大忌。

推荐清单

  • 型号:Intel D7-P5520 / Samsung PM9A3 (U.2 接口)。
  • 容量:3.84TB x 2 (组 RAID 1)。
  • 连接:通过 PCIe x4 转 U.2 转接卡接入主板 PCIe 插槽。

3. 实施步骤 A:存储层 (PCIe Passthrough)

为了榨干 NVMe 的性能,不使用 PVE 的虚拟磁盘(VirtIO Block),直接将 PCIe 控制器直通给虚拟机。

3.1 宿主机开启 IOMMU

在 PVE Shell 中操作:

Bash

nano /etc/default/grub
# 修改如下 (Intel CPU):
GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt"
update-grub
reboot

验证dmesg | grep -e DMAR -e IOMMU 有输出即成功。

3.2 虚拟机直通配置

  1. 确认 NVMe ID:lspci -nn | grep Non-Volatile (记下 ID 如 04:00.0)。
  2. PVE 界面 -> DB 虚拟机 -> 硬件 -> 添加 PCI 设备 -> 选择对应 ID -> 勾选 “All Functions” 和 “PCI-Express”
  3. 警告:直通后该虚拟机无法使用 PVE 快照功能,备份需改用文件级或数据库级备份。

3.3 虚拟机内部组建 RAID 1

在 DB 虚拟机(Debian)内部操作:

Bash

# 1. 安装工具
apt install mdadm

# 2. 创建软 RAID 1 (假设盘符为 nvme0n1 和 nvme1n1)
mdadm --create /dev/md0 --level=1 --raid-devices=2 /dev/nvme0n1 /dev/nvme1n1

# 3. 格式化并挂载
mkfs.ext4 /dev/md0
mkdir -p /var/lib/mysql_data
mount /dev/md0 /var/lib/mysql_data

# 4. 写入 fstab (防止重启丢失)
echo "UUID=$(blkid -s UUID -o value /dev/md0) /var/lib/mysql_data ext4 defaults 0 0" >> /etc/fstab

# 5. 保存 RAID 配置
mdadm --detail --scan >> /etc/mdadm/mdadm.conf
update-initramfs -u

4. 实施步骤 B:网络层 (VirtIO Internal Fabric)

利用内存拷贝实现虚拟机间通信,带宽可达 20Gbps+,延迟 < 0.1ms。

4.1 PVE 宿主机网络架构

创建独立网桥,不绑定任何物理网口,实现物理隔离。

  • 设备vmbr1
  • IP/网关:全部留空
  • Bridge Ports:留空
  • MTU:9000 (关键:开启巨型帧以降低 CPU 中断消耗)

4.2 虚拟机网卡配置

为 Web VM 和 DB VM 分别添加第二张网卡:

  • 桥接vmbr1
  • 模型:VirtIO
  • 防火墙关闭 (Firewall=No,减少内核包过滤开销)

4.3 系统内网配置 (Debian /etc/network/interfaces)

DB Server (192.168.10.20):

Plaintext

auto ens19
iface ens19 inet static
    address 192.168.10.20
    netmask 255.255.255.0
    mtu 9000

Web Server (192.168.10.10):

Plaintext

auto ens19
iface ens19 inet static
    address 192.168.10.10
    netmask 255.255.255.0
    mtu 9000

注:配置完成后需 systemctl restart networking 并互 ping 测试。


5. 实施步骤 C:应用层迁移与调优

5.1 MySQL 针对 NVMe 调优 (my.cnf)

默认配置无法发挥 NVMe 性能,必须修改 IO 相关参数。

Ini, TOML

[mysqld]
datadir = /var/lib/mysql_data

# 内存分配 (假设 VM 64G 内存)
innodb_buffer_pool_size = 48G
innodb_log_file_size = 4G

# NVMe 核心优化
innodb_io_capacity = 20000        # 释放 SSD 性能
innodb_io_capacity_max = 40000
innodb_flush_neighbors = 0        # SSD 不需要邻近刷新
innodb_flush_method = O_DIRECT    # 绕过系统缓存
skip-name-resolve                 # 禁用 DNS 反查
bind-address = 0.0.0.0            # 允许内网连接

5.2 权限迁移

在 DB Server 执行:

SQL

-- 创建允许内网段访问的用户
GRANT ALL PRIVILEGES ON *.* TO 'root'@'192.168.10.%' IDENTIFIED BY 'PASSWORD' WITH GRANT OPTION;
FLUSH PRIVILEGES;

注意:如果每个 WP 站点使用独立用户,需批量修改 mysql.user 表中的 Host 字段为 192.168.10.10

5.3 批量修改 wp-config.php

在 Web Server 站点根目录执行,将 localhost 批量替换为内网 DB IP。

Bash

# 备份是个好习惯
# cp -r /var/www/html /var/www/html_backup 

# 批量替换 DB_HOST
find /var/www/html -name "wp-config.php" -exec sed -i "s/define( *'DB_HOST', *'localhost' *);/define( 'DB_HOST', '192.168.10.20' );/g" {} +

6. 最终检查清单

  1. I/O 验证:在 DB VM 执行 iostat -mx 1,确认 svctmawait 处于微秒级。
  2. 网络验证:使用 iperf3 压测 Web 与 DB 间带宽,预期应 > 15Gbps。
  3. WP 验证:随机抽取站点,检查页面加载速度及 PHP 错误日志。
  4. 安全验证:在 DB VM 上使用 iptables 封锁 3306 端口,仅允许 192.168.10.10 访问。

架构收益总结:

此方案通过物理隔离 I/O(NVMe 直通)和逻辑隔离网络(VirtIO 网桥),彻底消除了 SAS 机械硬盘的随机读写瓶颈,是目前单机 PVE 环境下最高性能的 WordPress 托管架构。

阅读更多PVE 环境下 1500+ WordPress 站点的性能救赎之路

Woocommer如何批量删除指定分类下的产品?

woocommerce错误了发布/导入了大量产品,如何删除清理指定分类的所有产品?

首先,查询对应的产品分类ID,这个不多说,自行查询

然后使用wp cli进行如下操作

第一种方法:适合少量产品删除

#需指定wordprdss账号,--force=true为强制删除不进垃圾箱
wp wc product list --category=分类ID --field=id --user=指定一个wordprss账号 | xargs -n 1 wp wc product delete --force=true --user=指定一个wordprss账号

第二种方法:适合大量产品的删除

#删除指定分类ID下的产品
wp db query "DELETE FROM wp_posts WHERE ID IN (SELECT object_id FROM wp_term_relationships WHERE term_taxonomy_id = (SELECT term_taxonomy_id FROM wp_term_taxonomy WHERE term_id = 分类ID AND taxonomy = 'product_cat'));"

#删除关联的元数据
wp db query "DELETE FROM wp_postmeta WHERE post_id NOT IN (SELECT ID FROM wp_posts);"
wp db query "DELETE FROM wp_term_relationships WHERE object_id NOT IN (SELECT ID FROM wp_posts);"

#重新统计分类和TAG的数量
wp term recount product_cat product_tag

结束!

阅读更多Woocommer如何批量删除指定分类下的产品?

使用WP CLI批量更新WordPress站点

场景:一台Debian机器下有500+worpdrsss站点,需将所有站点的wordpress程序、主题、插件,还有Woocommerce商店都更新到最新版。

首先 sudo apt update && apt upgrade -y 更新一下。

安装 WP CLI

#安装 WP CLI
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar

#检测phar文件工作状态
php wp-cli.phar --info

#给wp-cli.phar文件增加可执行权限,并调整为可使用wp命令来执行
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp

#验证
wp --info

#输出如下
OS:     Linux 5.10.0-18-amd64 #1 SMP Debian 5.10.140-1 (2022-09-02) x86_64
Shell:  /bin/bash
PHP binary:     /www/server/php/80/bin/php
PHP version:    8.0.20
php.ini used:   /www/server/php/80/etc/php-cli.ini
MySQL binary:   /usr/bin/mysql
MySQL version:  mysql  Ver 14.14 Distrib 5.7.39, for Linux (x86_64) using  EditLine wrapper
SQL modes:
WP-CLI root dir:        phar://wp-cli.phar/vendor/wp-cli/wp-cli
WP-CLI vendor dir:      phar://wp-cli.phar/vendor
WP_CLI phar path:       /root
WP-CLI packages dir:
WP-CLI cache dir:       /root/.wp-cli/cache
WP-CLI global config:
WP-CLI project config:
WP-CLI version: 2.7.1

#更新 wp cli
wp cli update

执行批量更新

假设所有网站存放路径为 /www/web/ ,方法如下:

新建以下 update_wp.sh 文件

#!/bin/bash

for dir in /www/web/*; do
  if [[ -d "$dir" ]]; then
    echo "正在处理: $dir"
    cd "$dir" || { echo "错误: 无法切换到 $dir 目录"; continue; }
    wp core update || echo "注意: $dir wp内核升级失败"
    wp theme update --all || echo "注意: $dir 主题升级失败"
    wp plugin update --all || echo "注意: $dir 插件升级失败"
    wp rewrite flush || echo "注意: $dir 的rewrite刷新失败"
    echo "完成升级: $dir"
    echo "--------------------"
  fi
done
#给执行权限
chmod +x update_wp.sh

#执行脚本
./update_wp.sh

如果是用root用户执行的,会提示命令后面需要加上 --allow-root

#!/bin/bash

for dir in /www/web/*; do
  if [[ -d "$dir" ]]; then
    echo "正在处理: $dir"
    cd "$dir" || { echo "错误: 无法切换到 $dir 目录"; continue; }
    wp core update --allow-root || echo "注意: $dir wp内核升级失败"
    wp theme update --all --allow-root || echo "注意: $dir 主题升级失败"
    wp plugin update --all --allow-root || echo "注意: $dir 插件升级失败"
    wp rewrite flush --allow-root || echo "注意: $dir 的rewrite刷新失败"
    echo "完成升级: $dir"
    echo "--------------------"
  fi
done

有安装woocommer的需要再加一条商店数据库的更新

wp wc update || echo "注意: $dir woocommerce数据库更新失败"

以下为老方法,不建议使用

cd /www/web ,然后 vim wpexec.sh,内容如下,保存。

#!/bin/bash
DIR=`ls .`
for dir in ${DIR};do
	if [ -d ${dir} ];then
		echo $dir
		cp wpup.sh ${dir}
		cd ${dir}
		pwd
		./wpup.sh 
		cd ..
	fi
done

vim wpup.sh 内容如下,保存。这里要注意一下权限的问题,建议先单个执行wp core update看下。

#!/bin/bash
#更新wordrepss
wp core update --allow-root
#更新wordpress db
wp core update-db --allow-root
# 更新插件,排除 akismet和hello
wp plugin update --all --exclude=akismet,hello --allow-root
# 更新主题
wp theme update --all --allow-root
# 更新woocommerce db
wp wc update --allow-root

然后给文件增加可执行权限

chmod +x wpexec.sh
chmod +x wpup.sh

执行脚本

bash wpexec.sh

最后删除这2个文件

find . -maxdepth 2 -name 'wp*.sh' -delete

参考:

https://wp-cli.org/
https://developer.wordpress.org/cli/commands/
https://github.com/woocommerce/woocommerce/wiki/Upgrading-the-database-using-WP-CLI
https://blog.csdn.net/robinblog/article/details/8853628

阅读更多使用WP CLI批量更新WordPress站点