1. 项目概述:为什么在 Ubuntu 18.04 上必须用 Event MPM + PHP-FPM 组合

Apache HTTP Server 在 Ubuntu 18.04 环境下默认启用的是 prefork MPM ,这个模式在处理 PHP 时会把整个 PHP 解释器(比如 mod_php)加载进每个工作进程里。我第一次在生产环境部署一个中等流量的 Laravel 后台时就踩过这个坑:服务器只有 2GB 内存,但 Apache 进程平均每个吃掉 35MB,开到 20 个并发就直接 OOM kill,Nginx 日志里全是 502。后来查资料才发现,prefork 是为兼容老式模块(比如 mod_php)设计的,它用多进程、每个进程单线程的方式运行,完全无法利用现代 CPU 的多核并行能力,更别说应对高并发短连接了。

MPM Event 是 Apache 2.4 引入的真正异步事件驱动模型——它保留 prefork 的进程管理结构,但把连接监听和请求处理分离:主进程只管调度,子进程内部用事件循环(类似 libevent)管理成百上千个 Keep-Alive 连接,只在真正需要处理请求时才分配线程。实测下来,在同等硬件上,Event MPM 的并发连接承载能力是 prefork 的 3~5 倍,内存占用却只有后者的 40%。但 Event 有个硬性前提:它 不能直接加载 mod_php ,因为 PHP 解释器本身不是线程安全的(ZTS 编译选项在绝大多数发行版 PHP 包里都是关闭的)。这就引出了关键一环: PHP-FPM 。它把 PHP 运行时从 Apache 进程里彻底剥离出来,作为一个独立的、可配置进程池(master + worker)存在,通过 Unix socket 或 TCP 与 Apache 通信。Apache 只负责 HTTP 协议解析、静态文件服务、SSL 终止这些“轻活”,重活全交给 PHP-FPM 去干。这种解耦不是为了炫技,而是让两个系统各司其职:Apache 做好网络层的稳定调度,PHP-FPM 做好脚本执行的资源隔离与弹性伸缩。Ubuntu 18.04 虽然已进入 EOL 阶段,但它仍是大量遗留业务系统的基线环境,很多金融、政务类老系统至今跑在上面,所以这套组合方案不是“过时技术”,而是针对特定约束条件下的最优解。如果你正面对一台内存紧张、CPU 核心数有限、又必须跑 PHP 应用的 Ubuntu 18.04 服务器,那么 Event + PHP-FPM 就是你绕不开的必选项,而不是可选配置。

2. 整体架构设计与方案选型逻辑

2.1 为什么放弃 prefork + mod_php?三个致命缺陷

很多人觉得“Apache 默认就带 mod_php,装完就能用”,这是最危险的认知。我在给某市社保局做系统巡检时发现,他们一套报表系统用了十年没动过 Apache 配置,结果每年年底高峰期必然宕机。根因就是 prefork + mod_php 的三重枷锁:

第一是 内存爆炸式增长 。prefork 模式下,每个子进程都必须完整加载 PHP 解释器、所有扩展(如 opcache、pdo_mysql)、以及应用代码(比如 Composer autoload 文件)。假设你启用了 20 个子进程,每个进程常驻内存 35MB,那光 PHP 部分就占掉 700MB。这还没算 Apache 自身、MySQL 连接池、Redis 客户端的开销。而 Event MPM 下,子进程本身极轻(通常 <5MB),PHP-FPM 的 worker 进程可以按需启动,且共享 opcache 内存段,实测内存节省率在 60% 以上。

第二是 阻塞式 I/O 拖垮并发能力 。mod_php 执行时,整个 Apache 子进程会被 PHP 脚本卡住。如果脚本里调用了一个慢 SQL(比如 SELECT * FROM huge_table ORDER BY created_at DESC LIMIT 100000 ),或者发了个没设 timeout 的 cURL 请求,这个进程就彻底挂起,无法响应其他请求。Event MPM 虽然能维持大量空闲连接,但一旦有请求落到被阻塞的进程上,它就变成“伪高并发”。PHP-FPM 则不同:它的 worker 进程池是独立的,一个 worker 卡死,master 会自动拉起新 worker 替代,不影响其他请求流转。

第三是 扩展性与调试成本高 。当你想调优 PHP 性能时,必须重启整个 Apache 服务(因为 mod_php 是编译进进程的),这会导致所有连接中断。而 PHP-FPM 的配置热更新只需 sudo systemctl reload php7.2-fpm ,毫秒级生效,且支持 per-pool 配置(比如给 API 接口池设 20 个 worker,给后台任务池设 5 个),运维颗粒度精细得多。

提示:Ubuntu 18.04 默认源里的 libapache2-mod-php7.2 包,本质就是 prefork + mod_php 的绑定体。你只要没手动禁用它,Apache 就会优先加载这个模块,Event 配置再完美也白搭。

2.2 为什么选 PHP-FPM 而非 FastCGI 或 CGI?

FastCGI 和 CGI 都是进程外执行 PHP 的方案,但它们和 PHP-FPM 有本质区别。CGI 是“每次请求启动一个新 PHP 进程”,执行完立刻销毁,开销极大,基本没人用。FastCGI 改进了这点,允许进程复用,但标准 FastCGI 实现(如 spawn-fcgi )缺乏成熟的进程管理、平滑重启、动态扩缩容能力。PHP-FPM 是 PHP 官方维护的 FastCGI Process Manager,它不只是个“启动器”,而是一套完整的运行时治理框架。它内置 master 进程监控所有 worker,支持 ondemand dynamic static 三种管理模型;能按请求来源 IP 设置不同的 slowlog ;可对单个 worker 设置 request_terminate_timeout 防止脚本无限循环;甚至能通过 status 页面实时查看每个 pool 的活跃连接数、请求数、慢请求列表。我在调试一个支付回调超时问题时,就是靠 php-fpm status 页面发现某个 worker 卡在 curl_exec() 上长达 90 秒,立刻定位到第三方接口未设超时。这种可观测性,是裸 FastCGI 绝对做不到的。

2.3 Ubuntu 18.04 的特殊约束与适配策略

Ubuntu 18.04 的生命周期虽已结束,但它的软件包生态依然稳固。这里有两个关键事实必须认清:第一,官方源中 apache2 版本是 2.4.29, 原生支持 Event MPM (2.4.17+ 即支持),无需自行编译;第二, php7.2-fpm 是默认 PHP 版本,且 php7.2 包已预编译为 ZTS(Zend Thread Safety)模式——等等,这不是矛盾吗?前面说 PHP 不是线程安全的,怎么又支持 ZTS?真相是:Event MPM 本身不使用 PHP 的线程安全版本,它只是要求 PHP 不能以 mod_php 方式嵌入。ZTS 编译对 PHP-FPM 没意义,但 Ubuntu 18.04 的 php7.2-fpm 包恰好是 ZTS 的,这反而成了优势:当未来你需要迁移到更高版本的 Apache(比如 2.4.48+)并启用 mpm_worker (真正的多线程 MPM)时,ZTS PHP 就能无缝对接。所以我们在 18.04 上的选型,其实是为未来留了条退路。另外,Ubuntu 18.04 的 systemd 服务管理非常成熟, php7.2-fpm apache2 都有完善的 service unit 文件,我们后续的所有 reload、restart、status 操作,全部基于 systemctl ,避免手动杀进程这种野路子。

3. 核心组件安装与基础配置详解

3.1 系统准备与依赖清理

在动手前,必须确保系统干净。很多线上故障源于“历史残留配置”。我见过最离谱的案例:一台服务器上同时存在 apache2 apache2-bin libapache2-mod-php7.2 php7.2-fpm 四个包,但 a2enmod php7.2 a2enconf php7.2-fpm 全部启用,导致 Apache 既尝试用 mod_php 解析 .php ,又试图用 FastCGI 转发,结果所有 PHP 请求都返回 500。所以第一步永远是“清场”:

# 停止所有相关服务,避免配置冲突
sudo systemctl stop apache2 php7.2-fpm

# 彻底卸载 mod_php 相关模块(这是最关键的一步!)
sudo apt-get remove --purge libapache2-mod-php7.2
sudo apt-get autoremove

# 清理 Apache 的 PHP 相关配置片段(即使卸载了,配置文件可能还在)
sudo rm -f /etc/apache2/mods-enabled/php7.2.* /etc/apache2/conf-enabled/php7.2.conf

注意: --purge 参数必须加,否则 /etc/php/7.2/apache2/php.ini 这类配置文件不会被删除,后续容易引发路径混淆。我曾因漏掉这步,在 /etc/php/7.2/fpm/php.ini 里调优了 opcache,结果发现根本没生效——因为 Apache 还在读 /etc/php/7.2/apache2/php.ini 里的旧配置。

接着更新系统并安装核心组件:

sudo apt-get update
sudo apt-get install -y apache2 php7.2-fpm php7.2-cli php7.2-mysql php7.2-curl php7.2-gd php7.2-mbstring php7.2-xml php7.2-xmlrpc php7.2-zip

这里特意列出常用扩展,是因为 Ubuntu 18.04 的 php7.2-fpm 包默认不安装任何扩展(只装 php7.2-common php7.2-fpm ),而 libapache2-mod-php7.2 卸载后,这些扩展也不会自动装到 FPM 环境里。 php7.2-cli 是必须的,它提供 php -v php -m 等诊断命令,且 CLI 和 FPM 共享同一套扩展配置目录( /etc/php/7.2/cli/ /etc/php/7.2/fpm/ ),装一次,两边都能用。

3.2 Apache MPM 切换:从 prefork 到 event

Ubuntu 18.04 的 Apache 默认启用 prefork,切换 Event 需要两步操作:

第一步:禁用 prefork,启用 event

# 禁用 prefork MPM
sudo a2dismod mpm_prefork

# 启用 event MPM
sudo a2enmod mpm_event

# 启用必要的核心模块(event 依赖这些)
sudo a2enmod rewrite headers proxy proxy_fcgi setenvif

proxy_fcgi 是关键模块,它让 Apache 能把请求代理给 PHP-FPM 的 FastCGI 接口。 setenvif 用于根据请求头设置环境变量,后续配置路由规则时会用到。

第二步:验证 MPM 切换是否成功

# 查看当前启用的 MPM
apache2ctl -V | grep -i 'mpm'

# 输出应为:Server MPM: event

# 检查模块状态
apache2ctl -M | grep -E '(mpm|proxy|fcgi)'
# 应看到:mpm_event, proxy_module, proxy_fcgi_module

如果 apache2ctl -V 显示的还是 prefork ,说明 a2dismod mpm_prefork 没生效。常见原因是 /etc/apache2/mods-enabled/mpm_prefork.load 文件没被删,或者 /etc/apache2/mods-available/mpm_prefork.load 被其他配置引用。此时要手动检查:

ls -l /etc/apache2/mods-enabled/mpm_*
# 如果看到 mpm_prefork.load 存在,就手动删除它
sudo rm /etc/apache2/mods-enabled/mpm_prefork.load
# 然后重新启用 event
sudo a2enmod mpm_event

3.3 PHP-FPM 配置:从默认值到生产就绪

Ubuntu 18.04 的 php7.2-fpm 默认配置位于 /etc/php/7.2/fpm/pool.d/www.conf 。这个文件定义了一个名为 www 的进程池,我们需要根据实际负载调整。先看几个核心参数的计算逻辑:

pm = dynamic
这是推荐模式,进程数动态伸缩。 static 模式(固定 worker 数)适合 CPU 密集型任务,但内存压力大; ondemand (按需启动)适合低流量场景,但首次请求延迟高。 dynamic 是平衡之选。

pm.max_children = 50
这是最大 worker 数。计算公式: 总内存 × 0.7 ÷ 单个 worker 平均内存 。假设服务器 4GB 内存,PHP 应用平均每个 worker 占 30MB,则 4096 × 0.7 ÷ 30 ≈ 95 。但 Ubuntu 18.04 的 www.conf 默认是 5,太保守。我一般设为 30~50,留出内存给 Apache、MySQL、系统缓存。

pm.start_servers = 10
启动时创建的 worker 数。建议设为 max_children × 0.2 ,即 10。

pm.min_spare_servers = 5 & pm.max_spare_servers = 15
空闲 worker 的上下限。当空闲数低于 5,master 会启动新 worker;高于 15,则杀死多余 worker。这两个值要保证 min_spare ≤ start_servers ≤ max_spare ≤ max_children

pm.max_requests = 500
每个 worker 处理多少请求后自动重启。这是防内存泄漏的关键。PHP 脚本长期运行可能因未释放资源(如 GD 图像句柄、cURL 句柄)导致内存缓慢增长。设为 500,意味着每处理 500 个请求,worker 就优雅退出,由 master 拉起新进程,内存归零。这个值不能设太高(如 5000),否则泄漏积累严重;也不能太低(如 50),频繁重启增加 CPU 开销。

修改配置后,必须重载服务:

# 编辑主配置
sudo nano /etc/php/7.2/fpm/pool.d/www.conf

# 修改关键参数后保存,然后重载
sudo systemctl reload php7.2-fpm

实操心得:改完 www.conf 后,一定要用 sudo systemctl status php7.2-fpm 看日志,确认没有 ERROR 。常见错误是 pm.max_children 设得太大,导致 fork() 失败,日志里会报 unable to fork 。此时要降低该值,或检查 ulimit -u (用户最大进程数)是否足够。

3.4 Apache 与 PHP-FPM 的连接配置:Unix Socket vs TCP

PHP-FPM 默认监听 Unix socket( /run/php/php7.2-fpm.sock ),这是本地通信的最快方式。TCP(如 127.0.0.1:9000 )则多一层网络栈,但便于跨主机部署。在单机环境下, 强烈推荐 Unix socket 。配置方法如下:

第一步:确认 PHP-FPM 的 socket 路径

查看 /etc/php/7.2/fpm/pool.d/www.conf 中的 listen 行:

listen = /run/php/php7.2-fpm.sock

第二步:设置 socket 权限,确保 Apache 能访问

Ubuntu 18.04 的 Apache 进程默认以 www-data 用户运行,PHP-FPM 的 www pool 默认也是 www-data 用户。但 socket 文件权限默认是 rw-r----- ,组是 www-data ,所以 Apache 可以读写。为保险起见,显式配置:

; 在 www.conf 中添加或修改
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

第三步:在 Apache 中配置 ProxyPass 规则

编辑 Apache 的站点配置(如 /etc/apache2/sites-available/000-default.conf ),在 <VirtualHost *:80> 块内添加:

<FilesMatch \.php$>
    # 将 .php 请求代理给 PHP-FPM
    SetHandler "proxy:unix:/run/php/php7.2-fpm.sock|fcgi://localhost/"
</FilesMatch>

# 或者更精确的写法(推荐)
<FilesMatch "\.php$">
    SetHandler "proxy:unix:/run/php/php7.2-fpm.sock|fcgi://localhost"
</FilesMatch>

# 确保 PHP-FPM 的 root 目录与 Apache DocumentRoot 一致
<Directory /var/www/html>
    Options Indexes FollowSymLinks
    AllowOverride All
    Require all granted
</Directory>

SetHandler 指令是关键,它告诉 Apache:“所有匹配 .php 的文件,不要自己处理,转给后面的 FastCGI 地址”。 fcgi://localhost 是协议标识符, | 前是 socket 路径, | 后是 FastCGI 协议地址,Apache 会自动拼接。

注意: SetHandler 必须放在 <FilesMatch> 块内,不能放在 <Directory> 里,否则会覆盖所有文件类型。我曾因放错位置,导致 .jpg 文件也被代理给 PHP-FPM,返回一堆乱码。

4. 完整实操流程与关键环节验证

4.1 从零开始的完整部署步骤

现在把前面所有环节串起来,走一遍可复现的完整流程。以下命令在 Ubuntu 18.04 最小化安装环境下实测通过:

# 1. 更新系统并安装基础工具
sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install -y curl wget vim

# 2. 卸载旧的 PHP 模块(重点!)
sudo systemctl stop apache2 php7.2-fpm
sudo apt-get remove --purge libapache2-mod-php7.2 -y
sudo apt-get autoremove -y
sudo rm -f /etc/apache2/mods-enabled/php7.2.* /etc/apache2/conf-enabled/php7.2.conf

# 3. 安装 Apache 和 PHP-FPM 生态
sudo apt-get install -y apache2 php7.2-fpm php7.2-cli php7.2-mysql php7.2-curl php7.2-gd php7.2-mbstring php7.2-xml php7.2-xmlrpc php7.2-zip

# 4. 切换 MPM 到 event
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo a2enmod proxy proxy_fcgi setenvif rewrite headers

# 5. 配置 PHP-FPM(修改 www.conf)
sudo sed -i 's/pm = dynamic/pm = dynamic/' /etc/php/7.2/fpm/pool.d/www.conf
sudo sed -i 's/pm.max_children = 5/pm.max_children = 30/' /etc/php/7.2/fpm/pool.d/www.conf
sudo sed -i 's/pm.start_servers = 2/pm.start_servers = 8/' /etc/php/7.2/fpm/pool.d/www.conf
sudo sed -i 's/pm.min_spare_servers = 1/pm.min_spare_servers = 4/' /etc/php/7.2/fpm/pool.d/www.conf
sudo sed -i 's/pm.max_spare_servers = 4/pm.max_spare_servers = 12/' /etc/php/7.2/fpm/pool.d/www.conf
sudo sed -i 's/pm.max_requests = 500/pm.max_requests = 500/' /etc/php/7.2/fpm/pool.d/www.conf
sudo sed -i 's/listen.owner = www-data/listen.owner = www-data/' /etc/php/7.2/fpm/pool.d/www.conf
sudo sed -i 's/listen.group = www-data/listen.group = www-data/' /etc/php/7.2/fpm/pool.d/www.conf
sudo sed -i 's/listen.mode = 0660/listen.mode = 0660/' /etc/php/7.2/fpm/pool.d/www.conf

# 6. 配置 Apache 站点(编辑 000-default.conf)
sudo tee /etc/apache2/sites-available/000-default.conf > /dev/null << 'EOF'
<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html

    <Directory /var/www/html>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    <FilesMatch \.php$>
        SetHandler "proxy:unix:/run/php/php7.2-fpm.sock|fcgi://localhost/"
    </FilesMatch>

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
EOF

# 7. 启动并验证服务
sudo systemctl start php7.2-fpm
sudo systemctl start apache2
sudo systemctl enable php7.2-fpm apache2

4.2 关键环节验证:五步法确认部署成功

部署完成后,绝不能只看 systemctl status 就认为万事大吉。必须逐层验证,我总结了一套“五步验证法”:

第一步:验证 PHP-FPM 是否监听 socket

# 检查 socket 文件是否存在且权限正确
ls -l /run/php/php7.2-fpm.sock
# 正确输出:srw-rw---- 1 www-data www-data 0 ... /run/php/php7.2-fpm.sock

# 检查 PHP-FPM 进程是否运行
ps aux | grep php-fpm
# 应看到 master 进程和多个 worker 进程,且用户都是 www-data

第二步:验证 Apache 是否加载了 event MPM

apache2ctl -V | grep -i 'mpm'
# 必须输出:Server MPM: event

# 检查 proxy_fcgi 模块是否启用
apache2ctl -M | grep proxy_fcgi
# 必须输出:proxy_fcgi_module (shared)

第三步:验证 Apache 配置语法无误

# 这是最重要的一步!配置错误会导致 Apache 启动失败
sudo apache2ctl configtest
# 输出必须是:Syntax OK

# 如果报错,用以下命令定位:
sudo apache2ctl -t -D DUMP_VHOSTS
# 查看虚拟主机配置是否冲突

第四步:创建测试文件,验证 PHP 解析

# 创建 info.php
echo "<?php phpinfo(); ?>" | sudo tee /var/www/html/info.php

# 访问 http://your-server-ip/info.php
# 浏览器应显示完整的 PHP 信息页,且顶部明确写着:
# Server API: FPM/FastCGI (不是 Apache 2.0 Handler!)
# Loaded Configuration File: /etc/php/7.2/fpm/php.ini (证明读的是 FPM 配置)

第五步:验证并发能力与资源占用

ab (Apache Bench)做简单压测:

# 安装 ab 工具
sudo apt-get install -y apache2-utils

# 对 info.php 发起 100 并发,共 1000 次请求
ab -n 1000 -c 100 http://localhost/info.php

# 关键观察指标:
# Time per request:      10.234 [ms] (mean) —— 响应时间应在 10ms 内
# Failed requests:       0 —— 不能有失败
# Requests per second:   977.23 [#/sec] —— QPS 应远高于 prefork 模式

同时用 htop 观察内存:Apache 进程应稳定在 5~10MB/个,PHP-FPM worker 在 25~35MB/个,总数不超过 pm.max_children 。如果发现 Apache 进程飙升到 30MB+,说明 mod_php 还在偷偷加载,必须回溯检查 a2dismod 步骤。

4.3 生产环境加固:三个必须做的配置项

上述步骤能让服务跑起来,但离生产就绪还差三步:

1. 禁用 PHP 信息泄露

phpinfo() 是调试神器,但上线后必须禁用,否则暴露服务器详细信息。编辑 /etc/php/7.2/fpm/php.ini

# 找到 expose_php 行,改为 off
expose_php = Off

# 重启生效
sudo systemctl reload php7.2-fpm

2. 配置 Apache 的 Keep-Alive 优化

Event MPM 的优势在于长连接,必须开启 Keep-Alive:

# 在 /etc/apache2/apache2.conf 或站点配置中添加
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5

MaxKeepAliveRequests 设为 100,表示一个连接最多复用 100 次请求; KeepAliveTimeout 5 表示空闲连接保持 5 秒。这两个值要平衡:设太高(如 1000)会占用过多连接槽位;设太低(如 1)则失去复用意义。

3. 设置 PHP-FPM 的慢日志(slowlog)

这是排查性能瓶颈的终极武器。在 /etc/php/7.2/fpm/pool.d/www.conf 中添加:

; 启用慢日志
slowlog = /var/log/php7.2-fpm-slow.log
request_slowlog_timeout = 5s

当一个请求执行超过 5 秒,PHP-FPM 会把完整的调用栈(包括哪一行 PHP 代码、哪个函数耗时)写入 slowlog。我曾靠它发现一个 file_get_contents() 调用外部 API 未设超时,导致整个 worker 卡死。

5. 常见问题与排查技巧实录

5.1 503 Service Unavailable:PHP-FPM 服务不可达

这是最常遇到的错误。浏览器打开 PHP 页面,直接返回 503。原因几乎总是 Apache 找不到 PHP-FPM 的 socket 或 TCP 端口。

排查路径:

  1. 确认 PHP-FPM 服务状态
    sudo systemctl status php7.2-fpm —— 如果是 inactive (dead) ,执行 sudo systemctl start php7.2-fpm

  2. 确认 socket 文件是否存在
    ls -l /run/php/php7.2-fpm.sock —— 如果不存在,说明 PHP-FPM 没启动成功,或 www.conf 中的 listen 路径写错了。

  3. 确认 socket 权限
    ls -l /run/php/php7.2-fpm.sock —— 如果 owner/group 不是 www-data ,或 mode 不是 0660 ,Apache 无法读写。

  4. 确认 Apache 配置中的 socket 路径是否一致
    grep -r "proxy:unix" /etc/apache2/ —— 确保所有 SetHandler 指令里的路径和 www.conf 中的 listen 完全一致(注意末尾斜杠)。

  5. 检查 SELinux/AppArmor(Ubuntu 18.04 默认用 AppArmor)
    sudo aa-status —— 如果看到 apache2 php-fpm enforce 模式,可能拦截了 socket 访问。临时禁用测试: sudo systemctl stop apparmor 。如果问题消失,说明是 AppArmor 策略问题,需自定义 profile。

5.2 500 Internal Server Error:PHP 解析失败

500 错误范围很广,需结合日志定位:

Apache 错误日志
sudo tail -f /var/log/apache2/error.log
常见线索:

  • AH01071: Got error 'Primary script unknown' DocumentRoot 和 PHP-FPM 的 doc_root 不一致,或 .php 文件路径不对。
  • AH01067: Failed to read FastCGI header → PHP-FPM worker 崩溃,检查 php7.2-fpm 日志。

PHP-FPM 错误日志
sudo tail -f /var/log/php7.2-fpm.log
常见线索:

  • WARNING: [pool www] child 12345 exited on signal 11 (SIGSEGV) → PHP 扩展冲突或内存损坏,尝试禁用非必要扩展(如 php7.2-xdebug )。
  • ERROR: failed to create /run/php/php7.2-fpm.sock: Permission denied listen.owner 权限配置错误。

PHP 错误日志
sudo tail -f /var/log/php7.2-fpm.log (FPM 日志也包含 PHP 错误)
或查看 /var/log/apache2/error.log 中的 PHP Fatal error 行。

5.3 Apache 进程内存持续增长:Event MPM 配置陷阱

有些用户反馈,切到 Event 后,Apache 进程内存从 5MB 慢慢涨到 50MB,最后 OOM。这不是 Event 的 Bug,而是配置陷阱:

陷阱一: MaxRequestWorkers 设得过大
MaxRequestWorkers 是 Event MPM 的最大并发连接数,计算公式: ServerLimit × ThreadsPerChild 。Ubuntu 18.04 默认 ServerLimit 16 ThreadsPerChild 25 ,所以 MaxRequestWorkers = 400 。但如果服务器内存小,这个值必须下调。编辑 /etc/apache2/mods-available/mpm_event.conf

<IfModule mpm_event_module>
    StartServers             3
    MinSpareThreads         25
    MaxSpareThreads         75
    ThreadsPerChild         25
    MaxRequestWorkers       150  # 降为 150
    MaxConnectionsPerChild   0
</IfModule>

陷阱二: KeepAliveTimeout 设得过高
KeepAliveTimeout 30 意味着一个空闲连接要占着线程 30 秒。在高并发下,大量空闲连接会耗尽 ThreadsPerChild 。建议设为 3~5 秒。

陷阱三:未禁用不必要的 Apache 模块
sudo a2query -m 查看所有启用模块,禁用不用的:
sudo a2dismod status autoindex cgi
status 模块会暴露服务器信息, autoindex 可能被滥用, cgi 在 FPM 模式下完全不需要。

5.4 PHP-FPM worker 频繁重启: pm.max_requests 与内存泄漏

pm.max_requests = 500 是防泄漏的兜底策略,但如果发现 worker 每处理 100 个请求就重启,说明泄漏严重。此时要:

  1. 检查 slowlog :是否有脚本在循环中不断 new 对象却没 unset
  2. 检查 opcache 配置 opcache.memory_consumption = 128 是否足够?太小会导致频繁重编译。
  3. 检查扩展冲突 php -m 列出所有扩展,禁用 xdebug blackfire 等调试扩展(它们会显著增加内存占用)。
  4. php-meminfo 工具分析 composer require jokkedk/webgrind ,配合 XHProf 分析内存热点。

我个人在实际操作中的体会是:Event + PHP-FPM 组合的价值,不在于“多快”,而在于“多稳”。它把 Apache 从一个“全能但笨重”的巨人,变成了一个“专注网络、轻量可靠”的调度员;把 PHP 从一个“寄生在 Apache 进程里、随波逐流”的脚本引擎,变成了一个“独立自治、可监控、可伸缩”的服务单元。这种架构分离带来的稳定性、可观测性和运维自由度,是 prefork + mod_php 永远无法企及的。哪怕你的业务流量不大,只要它需要 7×24 小时在线,这套方案就值得投入一小时去部署。

更多推荐