场景与目标
- 机器:2C4G VPS(Debian)
- 面板:1Panel
- Web:OpenResty
- PHP:PHP 8.4.x(容器)
- DB:MariaDB(容器)
- 缓存:Redis(容器)
- 站点:3 个在跑 + 准备上第 4 个
- 特点:无前台会员/购物车;动态主要是搜索、评论;只有站长自己登录后台
- 目标:不升级 VPS,在现有资源内提升稳定性、降低峰值风险(尤其是 502 / OOM)
先定原则(少折腾也能稳)
- 别为了“先进”上 K8s:现状是 LEMP/LEMP+容器混合,先把并发与缓存做对。
- 不要为了省资源去降 PHP 版本:省不出多少,风险更大;真正省资源靠缓存与并发控制。
- 避免面板不可见的“野改”:能在面板里做的优先在面板做,减少升级覆盖风险。
- 先保命再加速:没有 swap 的 4G 机器,突发内存峰值容易直接 OOM。
一、网络暴露与容器通信(避免“看着安全、实际有坑”)
1) 用 docker ps 判断端口是否暴露到宿主机/公网
docker ps --format 'table {{.Names}}\t{{.Ports}}'当时关键结果(示例):
- MariaDB:
127.0.0.1:3306->3306/tcp - PHP:
127.0.0.1:9000->9000/tcp - Redis:
127.0.0.1:6379->6379/tcp - OpenResty:Ports 为空(由面板/宿主机层管理 80/443)
结论:
- DB/PHP/Redis 仅绑定宿主机 127.0.0.1,不对公网开放 ✅
- 因此即使 PHP-FPM 容器内
listen = 0.0.0.0:9000,也不会被公网直接访问(关键在“宿主机端口映射”)。
2) 为什么把 PHP-FPM 改 listen=127.0.0.1 会 502?
因为容器内的 127.0.0.1 只代表容器自身,而 OpenResty 与 PHP 通过宿主机端口映射/容器网络通信;当 PHP 只监听容器内回环地址时,外部自然连不上 → 502。
正确思路:保持容器内 listen=0.0.0.0:9000,并确保宿主机侧只对本地开放(127.0.0.1 映射)即可。
二、PHP-FPM:用“限并发 + 防卡死 + 防膨胀”换稳定
目标
- 多站共用 PHP 时,最怕某个站/爬虫/慢请求把 worker 占满 → 其他站一起 502。
- 2 核机器不适合把并发开得很大:宁可排队,也不要 CPU+内存雪崩。
1) 采用 pm = ondemand
适合低中流量、多站点、希望省内存的场景:没有请求就不常驻 worker。
2) 关键参数(最终采用)
pm = ondemandpm.max_children = 6(总并发上限)pm.process_idle_timeout = 10s(空闲回收,省内存)pm.max_requests = 500(防进程越跑越胖)request_terminate_timeout = 120s(防卡死拖全站)request_slowlog_timeout = 3s(慢日志阈值,用于抓插件/慢请求)
注意:
ondemand下pm.start_servers / min_spare / max_spare不再有意义,建议移除避免误判。
3) 用 FPM status 验收是否需要继续调参
看这些就够:
listen queue:长期为 0 = 没排队max children reached:长期为 0 = 并发没撞墙slow requests:接近 0 = 没明显慢请求堆积
三、缓存体系:把 PHP 从请求链路里“尽量拿掉”才省资源
1) 页面缓存(Page Cache):WP Super Cache ✅
所有站点统一使用 WP Super Cache,并采用“简单模式(推荐)”。
建议设置要点:
- ✅ 启用缓存
- ✅ 登录访客不缓存
- ✅ 评论后刷新对应文章缓存
- ✅ 发布/更新内容清理相关缓存
- ✅ 不缓存带 querystring 的页面(例如
?x=y) - ✅ 搜索页不缓存(
?s=...关键词无穷多,缓存碎片大且收益小) - 垃圾回收/过期清理频率:按站点更新频率设置,不必太激进(避免无意义 IO)
纯内容站 + 匿名访问为主:Page Cache 的收益最大(直接减少 PHP 调用次数)。
2) 对象缓存(Object Cache):Redis ✅
已确认所有站点 Redis 插件“绿灯”并且各站点 key 前缀不冲突(多站最常见坑)。
对象缓存的价值:
- 减少重复的 DB 查询与 options 读取
- 后台编辑、插件较多时更明显
3) OPcache:确认已开启 ✅
在 PHP 容器内确认:
docker exec -it PHP8 sh -lc 'php -i | egrep -i "Zend OPcache =>|opcache.enable =>|opcache.memory_consumption =>|opcache.jit" | head -n 30'关键输出(示例):
opcache.enable => Onopcache.memory_consumption => 128opcache.jit => disable(WordPress 场景关闭也很正常)
结论:OPcache 已开,128MB 对 4 个小站一般够用;若后续插件/主题变多再考虑增到 192/256。
四、MariaDB:小库别“浪费内存”,把风险上限收紧
背景
- 数据库体量:几十 MB
- 站点有 Page Cache + Redis 后,DB 压力本就不大
- 优化目标不是“跑分”,而是:减少无效占用、降低峰值风险、减少临时表落盘
采取的调整(面板内完成)
- 降低
key_buffer_size
- 原因:MyISAM 用得少,128MB 往往是白占(WordPress 主要 InnoDB)。
- 提高
innodb_buffer_pool_size
- 从过小值提升到更合理区间(例如 256–512MB),避免 InnoDB 频繁折腾磁盘。
- 提高
tmp_table_size(并确保max_heap_table_size不成为短板)
- 目的:降低“临时表落盘比例”,减少 IO 抖动。
- 降低
max_connections
- 目的:降低最坏情况下的内存上限,防止异常连接/扫描导致资源被拉爆。
- 对小站而言 80–100 通常足够。
经验判断
- 小站优化 DB 的关键不是“越大越好”,而是“够用且上限可控”。
- 真遇到慢,先看慢查询日志/插件行为,而不是盲目扩大各种 buffer。
五、系统层兜底:开启 swap 防止突发 OOM(强烈推荐)
为什么必须要 swap?
4G 机器一旦出现:
- 突发爬虫、插件内存膨胀
- 容器瞬间涨内存
在无 swap 情况下容易直接 OOM → 容器被杀 → 站点短暂挂。
最终实施:2GB swap(/swapfile)
关键步骤(已验证可用):
sudo fallocate -l 2G /swapfile || sudo dd if=/dev/zero of=/swapfile bs=1M count=2048
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
swapon --show
free -h
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab并设置:
printf '\nvm.swappiness=10\nvm.vfs_cache_pressure=50\n' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p目标:
- swap 用于“兜底”,不要长期依赖;
swappiness=10让系统尽量不碰 swap,仅在内存压力大时使用。
六、上线第 4 个站的“最小风险”步骤
- 复制现有栈(Redis + WP Super Cache;OPcache 默认已开)
- 评论/搜索做回归:
- 发表评论后文章页能看到新评论(缓存清理正确)
- 搜索正常且不出现异常缓存结果(搜索页排除正确)
- 观察 1–2 天:
- PHP-FPM status:queue=0、max_children_reached 不增长
docker stats:PHP/MariaDB 内存不持续爬升- swap 使用量长期接近 0
七、快速巡检命令(只读、随时可跑)
1) 容器资源占用
docker stats --no-stream2) 系统内存与 swap
free -h
swapon --show3) OPcache 状态
docker exec -it PHP8 sh -lc 'php -i | egrep -i "Zend OPcache =>|opcache.enable =>|opcache.memory_consumption =>|opcache.jit" | head -n 30'4) 端口映射(确认不对公网暴露)
docker ps --format 'table {{.Names}}\t{{.Ports}}'结论(这套就够用)
在“不升级 VPS、不搞分站 PHP 多容器”的约束下:
- Page Cache + Redis Object Cache + OPcache 是省资源核心;
- PHP-FPM ondemand + 限并发 + 超时/回收 是稳定核心;
- MariaDB 收紧上限 + 合理 buffer + 减少临时表落盘 是稳态核心;
- 2GB swap + swappiness 调低 是兜底核心。
后续若真的出现 502/CPU 飙升,优先排查:
- 缓存是否失效(Page Cache 未命中)
- 爬虫/扫描是否激增(wp-login、xmlrpc)
- 某插件是否产生大量慢请求/写入
- PHP-FPM queue / max_children reached 是否开始增长
