PHP-FPM 调用大模型接口为什么会拖垮全站?Webman/Swoole 协程方案对比

17
发布时间:2026-11-10 11:17:05

一个 30 秒的大模型摘要请求,足以让一台配置正常的 PHP 服务器在 CPU 利用率不到 15% 的情况下陷入全站不可用。这不是危言耸听,而是 PHP-FPM 的并发模型与大模型接口的高延迟特性正面碰撞时,一个几乎必然发生的结果。

一个 30 秒的请求,如何锁死一个 Worker

PHP-FPM 的并发公式极其简单:并发上限 = worker 数。每个 worker 进程在任意时刻只处理一个请求,从头跑到尾,中间不切走。当一个请求通过同步客户端调用大模型接口,curl_exec 或 Guzzle 会阻塞当前 worker,直到模型返回第一个 token,甚至直到全部内容生成完毕。

假设 pm.max_children 设为 50。如果有 50 个用户同时触发一个需要 15 秒的大模型功能,整个 worker 池会被瞬间占满。第 51 个请求进入 listen.backlog 队列排队,队列再满,Nginx 直接返回 502/504。此时登录页、首页、静态资源请求全部因为拿不到 worker 而超时,故障从一个大模型功能蔓延为整站雪崩。

问题的实质不在于“进程数不够多”,而在于这些进程的绝大部分时间都在等待——等待 curl 握手、等待大模型吐出第一个 token。等待期间 CPU 空闲,但进程被占着,无法做任何其他事情。

为什么 PHP-FPM “装不下”协程

有人会问:给 PHP-FPM 加一个事件循环和协程不就好了?答案是否定的。FPM 的 worker 主循环长这样:fcgi_accept_request → php_execute_script → php_request_shutdown。在 php_execute_script() 返回之前,进程的控制流完全属于当前请求。事件循环想运行,要么在请求开始前(等于没跑),要么在请求结束后(已经晚了)。要在请求执行到一半时切去处理另一个请求,唯一的办法就是把前一个请求挂起——而“把执行到一半的请求挂起”,定义上就是协程。FPM 的进程模型从设计第一天起就没有为这件事留出位置。

更深层的问题是阻塞 IO。file_get_contents、curl_exec 是 C 实现的内部函数,执行期间线程在系统调用里睡眠,PHP 的 Fiber 或协程没有能力挂起一个正在阻塞的 C 调用。要让异步真正发生,需要把整条 IO 链路(HTTP 客户端、MySQL 驱动、Redis 驱动)都替换为非阻塞的协程实现,这正是 Swoole 除了 hook 之外还要提供一整套协程客户端的原因。

此外,FPM 依赖“一个请求独占一个进程”的假设来维持全局状态的安全:$_SERVER、静态变量、单例对象在请求结束时全部归零。一旦两个请求在同一进程内并发执行,header()、函数静态变量、容器单例都会互相覆盖。协程化意味着应用代码的语义契约变了——静态变量跨请求存活,请求边界需要手工模拟。

同步阻塞 vs 协程异步 · 并发模型对比

Webman/Swoole 的协程方案,解决了什么

Webman 基于 Swoole/Workerman 的常驻内存架构,将“等待”从执行单元手中拿走,交还给事件循环。当一个协程在等待大模型响应时,当前协程被挂起,事件循环立即调度其他就绪协程运行,worker 进程不会被独占。

这意味着,同样是 50 个 worker,在协程模式下可以同时维持数千甚至上万个并发的大模型调用,而不会出现“worker 池耗尽”的连锁反应。实测数据也印证了这一差异:Webman 在相同请求下的 QPS 通常比 PHP-FPM 高出 3—5 倍,开启 Swoole 协程后,同一 API 接口的 QPS 可从 3.2 万跃升至 6.8 万。

但协程不是“装上扩展就自动生效”的。Webman 默认不启用协程,必须显式配置 'event_loop' => 'swoole'(或 swow)。更关键的是,同步客户端在协程上下文中仍然会阻塞整个 worker:curl_exec、file_get_contents、PDO 同步查询都会让协程机制失效。调用大模型必须改用 Swoole\Coroutine\Http\Client 或 webman-php/openai 异步客户端;数据库和 Redis 操作也需要切换到协程客户端,否则 HTTP 层的并发能力会被数据库连接排队抵消殆尽。

在流式场景(SSE)下,Webman 的优势更加明显。大模型 API 的 stream=true 响应是逐块到达的 SSE 格式。在 PHP-FPM 中,维持一个 SSE 连接意味着一个 worker 进程被持续占用直到流结束;而在 Webman 的协程模型中,等待下一个 chunk 时协程挂起,worker 继续服务其他请求。实现上需要使用 webman-php/openai 的 $stream 回调逐块处理,手动构造 text/event-stream 响应头,并关闭输出缓冲以确保 chunk 实时到达前端。

选型判断:什么时候该换,什么时候不该

维度 PHP-FPM Webman / Swoole(协程模式)
并发上限 硬性等于 worker 数 单进程可支撑数千至上万协程
大模型调用等待 Worker 被独占,全站可被拖垮 协程挂起,等待不影响其他请求
SSE 流式长连接 一连接占用一进程,资源效率低 连接由事件循环管理,资源占用恒定
并发多模型调用 同步客户端导致串行等待 协程可实现真正并发
代价 无需改造,开发简单 需协程化 IO 客户端,注意全局状态与请求边界

如果大模型功能只是低频、非核心的辅助能力,且并发极低,PHP-FPM 并非不可用,只需为大模型请求设置严格的超时,并考虑将其放入独立 worker 池做故障隔离。但如果大模型调用是产品的主路径,或者需要支持流式输出、多模型并发比较、长连接会话,PHP-FPM 的进程模型会成为结构性的天花板,问题不是靠加 worker 或调超时能解决的。

宇扬科技工作室在 PHP 高性能服务架构与 AI 接口集成方面具备工程落地经验。如果你的项目正在经历大模型调用带来的并发瓶颈,或计划从 PHP-FPM 向 Webman/Swoole 协程架构迁移,可以从识别关键 IO 路径、替换同步客户端、建立协程连接池开始做渐进式改造,而不必一次性重写全部业务。

本文被 PHP编程 专题收录

有疑问?试试 AI 助手,答案来自站内真实资料