ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PHP开发者需要协程吗?从执行模型到Swoole/Fiber选型全解析

PHP开发者需要协程吗?从执行模型到Swoole/Fiber选型全解析 这几天在社区里又被同一个问题顶上来了「PHP 开发者需要协程吗」。说实话这个问题我前几年也纠结过那时候 Python 的 asyncio 和 Go 的 goroutine 把「高并发」这个词炒得火热我一度怀疑自己写 PHP 是不是落后了。后来在真实项目里把队列消费、批量抓取、长连接推送都折腾了一遍才慢慢摸清一件事协程不是银弹它只解决特定层面的问题而 PHP 的执行模型决定了它大部分时候用不上但在某些场景下又确实绕不开。这篇文章我想把这个话题彻底掰开揉碎先讲 PHP 的执行模型天然缺少什么再讲哪些信号说明你该考虑协程然后逐个拆解 Swoole、Workerman、Amp、PHP 8.1 Fiber 这些方案的真实面貌最后给出我自己的实测数据和落地建议。无论你是刚入行的 PHP 新人还是想给现有项目引入协程的老手应该都能从这里找到判断依据。1. 先从 PHP 的执行模型说起协程到底在解决什么1.1 每个 PHP 请求都是一条「直线」先看看传统 PHP-FPM 跑一个请求的完整流程Web 服务器Nginx 或 Apache把请求转交给一个空闲的 FPM 进程FPM 进程从入口文件开始从上往下执行遇到数据库查询就等数据库遇到 curl 调用就等外部接口最后输出响应体进程重置等待下一个请求。整个过程是严格同步的、一条直线走到底。每个 PHP-FPM 子进程在同一时刻只能处理一个请求它的内存和上下文也都是为这一个请求服务的。请求结束了进程变量清空、内存释放所有资源回归初始状态。这种模型有个被很多人忽视的优点进程短命且无状态。即使某个请求把内存干爆了挂掉的也只是一个 worker其他请求毫发无损即使代码里泄漏了连接资源请求结束也会被统一回收。你不需要像写 Java 服务那样考虑线程池的泄漏、对象的全局状态等一堆问题。缺点是显而易见的这个 worker 在等数据库、等外部接口、等磁盘的时候CPU 完全闲置。传统 Web 请求通常只有一两个 IO 等待点用户本来就要等结果多等几毫秒没什么感觉所以这个缺点被掩盖了。1.2 协程不是一个线程更不是并行先给协程下一个准确的定义协程是一个可以被暂停、稍后再恢复的函数执行流。它不像线程那样由操作系统抢占式调度而是由程序员在代码里主动声明挂起点让出控制权。协程的好处是切换成本极低几乎就是在用户态保存和恢复几个寄存器、跳转一下栈指针开销通常只有微秒级别。关键在于协程不是并行而是并发。一个 CPU 核心同一时刻依然只能在跑一段代码只是当某个任务在等待 IO 时CPU 立刻切去执行另一段代码不会干等着。我用一个生活化的类比来解释一个厨师要做红烧肉、清蒸鱼、炒青菜三道菜。传统同步做法是先把红烧肉切好下锅炖然后傻站在灶台边等它熟熟完了再做鱼这样一顿饭要一小时。会做菜的师傅不会这么等他在红烧肉上汽的时候去切鱼鱼上锅之后去洗菜炒菜的空隙回来给肉翻个面。桌子上的菜同时都在推进但厨师只有一个。协程就是让这一个厨师学会同时开三个灶。所以结论先摆在这里协程解决的是「单个进程/线程在处理多个 IO 等待时把等待时间利用起来」的问题。它是给 IO 密集型任务用的不是给 CPU 密集型任务用的。如果你的任务是纯粹的数学计算或者写一万行文件那协程帮不了你。1.3 为什么 FPM 时代一直没觉得缺协程大多数经典 PHP 项目长这样用户访问一个页面或接口PHP 代码从 Redis 读个缓存或查一次数据库然后渲染模板或输出 JSON。整条执行链路上只有一个主要的阻塞点而这个阻塞时间本身就是用户的等待时间。你不可能用一个协程把一个数据库查询给「并发掉」——查询本来就只有一次。就算遇到少量的多接口聚合场景比如一个页面要同时调订单接口、用户接口、商品接口串行耗时是三者之和。在传统的 FPM 模型下你确实可以在 Swoole 里把这三个请求并发出去把 900ms 变成 300ms。但对很多业务来说这个优化没有那么迫在眉睫尤其是页面本身还有缓存可以兜底的时候。更现实的情况是传统 PHP 项目遇到性能瓶颈第一反应通常是加 FPM worker、加缓存、加服务器。一台机器跑 50 个 FPM 进程每个进程同时就是一个并发单位Nginx 往外再横向扩展几台架构简单得不需要聪明人也能维护。这种「堆机器、堆进程」的模型很笨但它极其稳定、极好排查问题。所以说在传统 Web 场景下协程是「锦上添花」不是「雪中送炭」。你感觉不到缺它是因为你的瓶颈根本不在进程内部的 IO 等待。2. 哪些场景真的会让 PHP 开发者「卡脖子」2.1 队列消费者一个进程被阻塞 IO 拖到怀疑人生我见过最典型的协程刚需场景是 CLI 队列消费。假设你用 RabbitMQ 或 Redis 做消息队列消费者进程的逻辑是从队列里取一条消息 → 调用支付网关查单约 2 秒→ 发一条通知短信约 1 秒→ 更新订单状态写几张表约 0.5 秒→ 确认消费。这个消费者进程是典型的「串行循环」每条消息从取出到确认大概耗时 3.5 秒其中 3 秒是在等外部接口和网络返回。单进程一小时只能处理约 1000 条消息。要提升吞吐最容易想到的是多开几个 worker 进程比如开 8 个进程一小时 8000 条。这是最简单的方案很多人也是这么干的。但如果你把业务代码改造为协程并发让同一个进程里同时跑 10 个消息处理协程一个协程等支付网关的时候CPU 立刻切到另一个协程去发短信去写表。单进程吞吐直接就能到 9000 条/小时左右一台 4 核机器不用开太多进程IO 等待时间就被榨干了。当然代价是你得处理并发带来的副作用数据库连接不能共用、写入同一行数据要防冲突、调用外部接口要注意并发限流。这些在串行模型里不存在的问题引入协程后会冒出来。2.2 批量抓取和接口聚合串行等待的天花板另一个直接就让你感觉到「疼」的场景是批量抓取。比如要抓取某个平台 50 个商品详情页每个页面平均响应 300ms。串行循环的时间就是 50 × 300ms 15 秒这是物理上限改代码优化不动。用协程把并发度控制在 2050 个请求以 20 个为一批并发发出总耗时只由最慢的那一批决定大概 3 倍延迟也就是 900ms 左右。同样是 50 个请求时间从 15 秒降到 1 秒以内。接口聚合也是同理。一个管理后台页面要同时加载 5 个第三方平台的库存数据来对比串行要累加延迟并发的话总耗时等于最慢的那个接口。负责这类后台功能的朋友会非常理解这种感觉。我在这里强调一下这类场景里协程提升的是单进程的并发能力前提是上游服务允许你并发打过去。如果对方接口限流比如每秒钟最多 5 个请求那你并发度再高也会被限流挡住需要配合信号量控制并发数。2.3 长连接和推送服务FPM 模型的结构性短板WebSocket 聊天、站内推送、游戏服务端这类长连接服务FPM 模型基本做不了。原因很简单FPM 的一个 worker 在处理一个请求期间不能腾出手去处理别的请求。如果有 1000 个客户端保持 WebSocket 连接你需要 1000 个常驻的 FPM worker这在现实里根本不可行。这类服务必须用事件驱动的常驻进程模型一个进程维护成千上万个连接某个连接没有消息到达时进程去做别的事情等消息来了再回来处理。这个模型天然就和协程是绝配每个连接对应一个协程连接没数据时协程挂起事件循环检测到数据后恢复对应的协程。Swoole 和 Workerman 能在这个领域站稳脚跟核心原因就是这个。需要注意的是长连接场景的难点不只是协程还包括进程间通信、连接心跳、集群横向扩展、消息广播等协程只是其中一块拼图。2.4 用这张表快速判断你到底需要不需要我把上面说的内容合并成一张自查表你可以对照自己手头的项目信号说明建议CLI 批量任务多且大量时间花在等待外部接口/网络队列消费、数据同步、抓取脚本认真考虑协程单进程处理大量长连接WebSocket、TCP 推送、IM必须上事件驱动 协程一个请求要并行访问多个上游服务聚合接口、竞品价格对比可以针对性用协程瓶颈集中在数据库慢 SQL 或自身业务逻辑查询本身慢等协程没意义先优化 SQL、加索引项目跑在共享主机装不了扩展全是 FPM部署受限暂时别碰团队没人熟悉事件循环项目排期又紧交付压力大先堆进程稳定第一一句话总结协程解决的是「一个进程在 IO 等待时白白浪费 CPU」的问题你的项目要是没有这个痛点就不需要协程。3. PHP 生态里的协程方案Swoole、Workerman、Amp 和 Fiber 该怎么看3.1 Swoole激进但成熟C 扩展直接把协程引擎搬进来Swoole 是 PHP 协程生态里最重型的方案。它是一个 C 扩展内置了事件循环、协程调度器还维护了一套协程版本的网络客户端比如协程 MySQL 客户端、协程 Redis 客户端、协程 HTTP 客户端。最让人惊艳的是它的 Hook 机制开启SWOOLE_HOOK_ALL之后很多原生 PHP 函数在协程上下文里会自动变成非阻塞版本像file_get_contents、sleep、PDO 查询这些你在协程里照常写同步代码底层却是异步执行。这一点极大地降低了使用门槛写起来跟普通 PHP 没区别这是 Swoole 很难被替代的核心优势。基于 Swoole 的框架有 Hyperf、Swoft 等它们把常驻进程、依赖注入、AOP、连接池都做了封装适合用来构建微服务。Hyperf 的文档和社区都比较活跃如果你决定走这条路我建议直接从 Hyperf 上手而不是裸写 Swoole。代价也有Swoole 要求常驻进程代码里一旦出现全局变量残留就会跨请求污染数据内存泄漏在短命进程模型里无所谓在常驻进程里就是灾难调试方式也从「一次请求一次日志」变成「进程日志 性能分析」。你可以在传统 PHP 和 Swoole 之间无缝切换语言但思维方式需要调整。3.2 Workerman纯 PHP 的 event-loop 老牌方案5.0 拥抱 FiberWorkerman 是另一个常驻进程网络服务框架纯 PHP 实现不需要编译扩展历史比 Swoole 更早。它最早是事件循环加多进程模型写网络服务时会用到回调。Workerman 5.0 之后引入了基于 Fiber 的协程支持用起来比之前舒服不少。它的优点是部署简单PHP 版本够新就行没有扩展编译的问题框架本身也比较克制源码好读。基于 Workerman 的 webman 框架在性能和易用性之间做到了不错的平衡很多项目用它替代 FPM 跑普通 HTTP 服务。缺点是纯 PHP 实现的网络层在极高并发下性能不如 C 扩展但对大多数中小型业务来说差距感受不明显。我个人的经验是如果你只是需要开一个 TCP 服务或者轻量 HTTP 服务Workerman 足够而且踩坑比 Swoole 少。3.3 Amp / ReactPHP纯 PHP 异步生态适合学原理Amp 和 ReactPHP 是纯 PHP 的异步编程库没有扩展要求。ReactPHP 很早就提出了基于事件循环和 Promise 的异步模型Amp 在 3.x 之后把底部换成了 Revolt 事件循环也支持 Fiber。这两个库的问题在于生态比较小成熟的开源组件不如 Swoole 和 Workerman 丰富。而且它们的使用方式更接近 Node.js 的 Promise 风格跟 PHP 开发者习惯的同步顺序编程差异较大容易写出嵌套地狱。我自己用过一段 ReactPHP 写简单的聚合接口能跑但心智负担不小。纯 PHP 异步库最大的价值是学习。通过读它们的源码你能彻底搞懂事件循环、Promise、协程调度是怎么回事这些知识在理解 Swoole 或 Workerman 内部机制时非常有用。如果目的不是生产而是学习我很推荐拿 Amp 练手。3.4 PHP 8.1 自带 Fiber一个基础原语不是完整答案PHP 8.1 发布了 Fiber 类这是 PHP 官方首次在语言层面提供协程原语。很多人在朋友圈兴奋地转发「PHP 终于有协程了」但这里必须泼一盆冷水Fiber 只是提供了一个「挂起-恢复」的执行流原语它没有事件循环也不接管任何 IO。用 Fiber 可以做到让两个任务交错执行任务 A 跑到一半挂起任务 B 开始跑再切换回来。但如果你用file_get_contents去请求一个外部接口这个函数本身是阻塞的Fiber 挂不挂起都改变不了它阻塞的事实。真正让网络请求非阻塞的是底层的非阻塞 socket 和stream_select/epoll事件循环Fiber 只是让「每个 socket 的处理逻辑」可以写成同步顺序的样子挂起时把控制权交回事件循环。换句话说裸用 Fiber 写一个并发 HTTP 抓取程序你需要自己维护非阻塞连接、事件循环、超时处理工作量相当于重新造一个 ReactPHP。所以官方的 Fiber 是框架的基石而不是开发者的直接工具。3.5 方案对比表与选型建议方案协程实现部署要求框架生态适合场景学习曲线SwooleC 扩展 Hook 原生函数需要编译扩展常驻进程Hyperf、Swoft高并发服务、长连接、重 IO 消费陡Workerman纯 PHP 事件循环 Fiber5.0无特殊扩展常驻进程webmanSocket 服务、中小型长连接、队列消费中Amp / ReactPHP纯 PHP 事件循环 Promises / Fiber无特殊扩展组件较少异步编程学习、小型服务中裸 FiberPHP 8.1 原语仅挂起/恢复无需自建事件循环学习原理、极小规模实验难不引入协程FPM 多进程 队列无无传统 Web、水平扩展瓶颈不大无给一个直接的选型建议如果你是为了性能优化且能接受编译扩展和常驻进程选 Swoole Hyperf如果你只想做一个网络服务、不想引入 C 扩展选 Workerman 5如果你是想搞懂原理用 Amp 或者直接拿 Fiber 写个小实验。不要裸用 Fiber 上生产这是我希望你能记住的一句话。4. 上手实测串行 vs 协程的差距以及 Fiber 的真实用法4.1 先跑一个串行基线20 个请求耗时多少判断协程值不值得上第一步一定是先量出当前串行版本的耗时。比如这样一个简单脚本顺序抓取 20 个接口?php $urls []; for ($i 1; $i 20; $i) { $urls[] https://api.example.com/items/{$i}; } $start microtime(true); foreach ($urls as $url) { $body file_get_contents($url); echo $url . : . strlen($body) . bytes\n; } echo 串行耗时: . round(microtime(true) - $start, 3) . s\n;假设每个接口平均响应 250ms串行 20 个就是 5 秒左右。这个数字就是你的基线。所有并发改造后的收益都要拿它来对比。4.2 Fiber 的最小示例挂起、恢复、交错执行在进入复杂的 IO 并发之前先用 PHP 8.1 的 Fiber 做一个最简单的实验把「挂起/恢复」的机制跑明白。下面这段代码创建两个任务让它们轮流执行?php function makeTask(string $name, int $steps): Fiber { return new Fiber(function () use ($name, $steps) { for ($i 1; $i $steps; $i) { echo {$name}: 执行到第 {$i} 步\n; Fiber::suspend(); // 主动挂起把控制权交回外层 } return {$name} 完成; }); } $taskA makeTask(任务A, 3); $taskB makeTask(任务B, 3); while (!$taskA-isTerminated() || !$taskB-isTerminated()) { if (!$taskA-isTerminated()) { $taskA-resume(); } if (!$taskB-isTerminated()) { $taskB-resume(); } } var_dump($taskA-getReturn(), $taskB-getReturn()); echo 主程序结束\n;运行输出任务A: 执行到第 1 步 任务B: 执行到第 1 步 任务A: 执行到第 2 步 任务B: 执行到第 2 步 任务A: 执行到第 3 步 任务B: 执行到第 3 步 string(9) 任务A 完成 string(9) 任务B 完成 主程序结束两个任务在单线程内交错执行每一步之间的切换都是我们主动调用resume()的结果。这就是协程最底层的机制你帮我挂起我恢复你咱们轮流用这一个 CPU。注意这里没有真正的收益因为任务之间没有任何阻塞等待切换本身还带一点点开销。协程的价值从来都在「等待」上这点必须想清楚。4.3 关键点Fiber 不管 IO事件循环才管 IO好现在我们把问题升级既然 Fiber 能挂起恢复那能不能用它来实现 20 个并发 HTTP 请求答案是可以但你需要自己搭一个事件循环。原理是这样的把每个 socket 设置为非阻塞模式发起请求后不等待数据返回而是先挂起对应协程然后主循环用stream_select同时监听所有 socket哪个 socket 可读了就去恢复等待它的那个协程。核心调度代码长这样示意非完整实现public function waitRead($sock, Fiber $fiber): void { $this-readMap[get_resource_id($sock)] [$sock, $fiber]; } public function tick(): void { if (!$this-readMap !$this-writeMap) { return; } $read array_column($this-readMap, 0); $write array_column($this-writeMap, 0); $except null; stream_select($read, $write, $except, null); // 阻塞等待事件 foreach ($read as $sock) { $id get_resource_id($sock); [$_, $fiber] $this-readMap[$id]; unset($this-readMap[$id]); $fiber-resume(); // 此刻读取这个 socket 必定有数据不会阻塞 } }对应的协程任务这样写$fiber new Fiber(function () use ($loop, $url) { $fp stream_socket_client(tcp://api.example.com:80, $errno, $errstr, 30); stream_set_blocking($fp, false); fwrite($fp, GET /items/1 HTTP/1.1\r\nHost: api.example.com\r\nConnection: close\r\n\r\n); $body ; while (!feof($fp)) { $loop-waitRead($fp, Fiber::this()); // 登记等待 Fiber::suspend(); // 挂起等可读再恢复 $body . fread($fp, 8192); } fclose($fp); return $body; });我特意把这两段代码写得极简目的是让你看清协程和事件循环的分工事件循环负责告诉你「哪个 socket 能动了」Fiber 负责把「等这个 socket」的逻辑挂起来、不占 CPU。真正在生产里实现还得处理连接握手、半包、响应头解析、超时这些细节所以没人会手写直接上框架。4.4 生产环境写法Swoole 的 Co\run Channel 实现并发抓取如果你装了 Swoole 扩展生产环境写同样的事情只需要这么简单。先保证扩展就绪pecl install swoole php --ri swoole然后写一个并发抓取脚本?php Co::set([hook_flags SWOOLE_HOOK_ALL]); $urls []; for ($i 1; $i 20; $i) { $urls[] https://api.example.com/items/{$i}; } $start microtime(true); $stats []; Co\run(function () use ($urls, $stats) { $chan new Swoole\Coroutine\Channel(count($urls)); foreach ($urls as $url) { go(function () use ($url, $chan) { $body file_get_contents($url); // 被 Swoole Hook 接管后是非阻塞的 $chan-push([$url, strlen($body)]); }); } for ($i 0; $i count($urls); $i) { [$url, $len] $chan-pop(); $stats[$url] $len; } }); echo 协程并发耗时: . round(microtime(true) - $start, 3) . s\n;这里有一个大多数人会忽略的知识点file_get_contents在普通 PHP CLI 里是阻塞的为什么在 Swoole 下就变成协程版了因为SWOOLE_HOOK_ALL让 Swoole 在协程环境里用自己实现的事件驱动版本替换了底层的流函数。没有 Hook这个写法会阻塞整个进程这就是为什么我要在前面花那么大篇幅讲 Fiber 本身不管 IO。假设网络延迟同样是 250ms20 个串行请求耗时约 5 秒Swoole 并发版本大约只需要 1 秒左右差距就是协程在 IO 等待上榨出来的时间。具体数字当然要看上游实际响应但这已经足够说明问题了。5. 三个常见错觉以及我不建议上协程的情况5.1 错觉一用了协程网站整体变快这个错觉的迷惑性最强。很多人看到 Swoole 的并发能力就幻想「我把 FPM 换成 Swoole网站不就高并发了吗」如果你只是把单个 FPM 请求里的业务代码包进协程它的速度不会有任何提升因为一个请求本来就是一条直线没有多余等待可以优化。要让网站因为协程变快前提是你把整个 Web 服务改成 Swoole/Workerman 常驻进程模式让一个 worker 进程同时处理多个请求。这时候模型变了、部署方式变了、框架也要换。这是「换运行环境」级别的改造不是「加一个库」级别的操作。如果你只是想优化现有 FPM 项目协程帮不上忙老老实实做缓存、加机器更划算。5.2 错觉二协程让代码可以随便写阻塞调用在 Swoole 里sleep()会被 Hook 成非阻塞版本file_get_contents()会被 HookPDO 查询也能在连接池配合下协程化。但这些 Hook 不是万能的。第三方 C 扩展的阻塞调用、某些没被 Hook 覆盖的函数、一个复杂的原生 socket 库都有可能在某一个协程里把整个进程卡死。后果是严重的一个协程阻塞住了事件循环所有协程全部停摆对外表现为「整个服务卡住」的雪崩式故障。所以在引入协程时必须清楚地知道哪些函数在协程环境里是安全的、哪些不是数据库要用连接池而不是共用一个 PDORedis 客户端要用协程版本或走 Swoole 的协程支持。5.3 错觉三协程能替代数据库优化和队列扩容协程提高的是吞吐上限不是单次任务的延迟。一个查询本身要 1.5 秒加协程它还是 1.5 秒只是同一进程可以同时跑更多这样的查询而已。如果数据库连接池只有 10 条连接你把并发开成 50多出来的 40 个协程都在排队等连接性能不会有提升。同理如果你的队列消费瓶颈在业务逻辑里的一段 CPU 密集计算协程并发也帮不了你反而可能因为多协程共享 CPU 导致互相拖慢。这种情况下合理多开进程、或者优化算法比上协程靠谱得多。5.4 什么情况下你应该坚定地说「不需要」把这条单独列出来是因为太多人学协程纯粹是因为「技术焦虑」。我建议遇到以下情况直接放弃项目跑在共享主机上装不了扩展现有系统瓶颈明确是慢 SQL 且还没做索引优化团队没有一个人理解事件循环而项目下个月要上线现有 FPM Redis 队列 多进程 worker 的扩展方式运行良好加机器就够用。要记住协程引入的是「常驻进程 事件驱动」的一套新思维它在提高单进程吞吐的同时也把短命进程模型里那些被自动兜底的问题全部暴露出来内存泄漏要自己扛、全局变量要小心、连接要手动管理。这些成本在小型团队里很容易吞噬掉性能收益。6. 决定要学/要上协程之后我的建议路线6.1 先补事件循环和非阻塞 IO 的心智模型如果你已经决定要学协程我建议不要一上来就装 Swoole 写 Demo。先把底层的两块知识补齐事件循环是什么、非阻塞 IO 和阻塞 IO 的区别是什么。推荐路径是读 PHP 官方文档里stream_select和stream_set_blocking的用法写一个「同时监听两个 socket、谁有数据读谁」的小程序然后读一篇讲 Reactor 模式的文章理解 Nginx、Redis 单线程为什么能支撑那么高的并发原理完全相通。这些基础打牢之后再去碰 Fiber你会立刻明白它到底是干嘛的也知道为什么单纯的 Fiber 不够用。我见过太多人跳过了这步结果被 Swoole 的各种调度怪问题折磨。6.2 从小项目切入聚合接口、抓取任务、队列消费学协程最好的方式是找一个痛感明确的小项目练手别拿核心业务祭刀。我个人推荐三个练手方向按难度排序一是写一个多接口聚合脚本。比如输入一批 URL并发抓取并聚合结果返回对比串行和并发的耗时差异。这个项目能让你理解并发度控制、超时处理和结果收集几天就能搞定。二是把某个定时同步脚本改成协程。脚本要根据 ID 列表拉取远程数据回写本地表串行要跑半小时协程后五分钟跑完。项目里需要有数据库连接池的处理正好逼你去解决真正的生产问题。三是做一个简单的 WebSocket 服务端用 Workerman 或 Swoole 实现一个聊天室。这个项目能让你体会长连接 多个客户端并发连接的处理模式跟 HTTP 请求模型完全不同。每一步都必须做前后对比记录串行耗时、并发耗时、内存峰值、失败率。没有数据支撑任何改造都是耍流氓。6.3 生产化前必须解决的四件事进程管理、超时、连接池、监控从练手项目走向生产有四件事是必须提前规划好的。第一是进程管理常驻进程必须交给 Supervisor 或 systemd 管理进程挂了能自动拉起手动 kill 后能优雅退出。下面是一个 Supervisor 配置示例[program:queue_consumer] commandphp /data/app/bin/consumer.php numprocs4 autostarttrue autorestarttrue stopasgrouptrue killasgrouptrue第二是超时控制。每个协程都必须有超时兜底比如等待数据库连接、等待上游响应、等待 Channel 出队都要设置最大等待时间。一个协程无限期卡住实际上占用的不只是一个协程还有底层的连接资源。第三是连接池。数据库连接、Redis 连接都不能无节制创建用统一连接池管理池大小就是你的并发上限比直接把协程数开到几千更可控。Swoole 提供了Swoole\Database\PDOPool之类的组件别绕过它。第四是监控与日志。常驻进程不像 FPM 那样每个请求都有独立日志你需要自己记录进程级指标当前活跃协程数、协程调度延迟、平均处理时长、错误计数。日志里最好带上协程 ID否则并发时的错误日志根本对不上号。6.4 最后一点个人体会我自己在项目里做过一次类似的决定。当时我们维护了二十多个 PHP 服务大部分是典型 CRUD 接口跑在 FPM 上一直稳如老狗。真正让我决定引入协程的是一个数据抓取调度服务和一个推送网关。前者一个进程每小时只能跑几百个任务大量时间花在等第三方接口后者需要维持上万个长连接。我把这两个服务分别迁到了 Swoole 和 Workerman 上抓取吞吐提升了十几倍推送网关也稳定跑了很久。但剩下的二十个服务我一个都没动。我的结论是协程对 PHP 开发者来说不是必需的技能但它是一味对症的药。判断要不要吃你先看自己有没有「进程在 IO 等待上大量浪费」的病。如果你写的代码里有大量 curl、file_get_contents、外部 API 调用并且这些调用集中在 CLI 的循环任务里那协程几乎肯定能带来质变如果你的日常就是写 FPM 接口、查数据库、渲染模板那优雅地保持现状也是一种专业。如果你真的决定尝试建议从我最开始讲的心智模型补起用一个小抓取项目跨出第一步改完之后拿串行数据做一次诚实的对比。到那时候你自己心里就有答案了。
返回列表