ARTICLE DETAIL

资讯详情

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

PHP并行与并发的本质区别:高并发场景下如何选型与调优

PHP并行与并发的本质区别:高并发场景下如何选型与调优 先给结论PHP 并行 ≠ 并发。这话听着像在抠字眼但我在不少团队里见过因为混用这两个词导致架构选型争论几个月没结果的情况。讨论“PHP 到底能不能扛高并发”表面上是技术之争实际上很多人根本没统一自己在讨论的是“并行”还是“并发”。这两个概念一旦混了性能瓶颈分析第一步就歪了后面再折腾 Swoole、多进程、异步都是拿着锤子到处找钉子。这篇文章主要写给正在写 PHP 业务代码、经常被“并发压垮”困扰的同学也适合做技术选型时在 PHP 和其他语言之间纠结的朋友。先说清楚并行与并发的本质区别再拆开 PHP 默认运行模型里的“能”与“不能”然后给出现实中最容易踩的四个瓶颈现场和对应的处理方式最后聊聊 PHP 生态里真正能提供并行/并发能力的方案以及我压测和调优过程中看到的真实数据。读完后你会明白PHP 不是不能做高并发而是它默认的运行模型决定了它的并发方式和 Java、Go 不一样。搞清楚这个“不一样”很多代码问题根本不需要换语言就能解决。1. 并行与并发被误用最多的一对概念1.1 从一次“点餐”开始理解两件事我去楼下小饭馆吃饭厨师只有一口锅。我点了番茄炒蛋隔壁桌点了青椒肉丝。厨师的做法是先把番茄炒蛋下锅翻炒两分钟盛出来放在一边再炒青椒肉丝。两个菜最终都上了桌但同一时刻锅里只有一个菜。这就是并发。如果饭馆老板有钱多加了一口锅、雇了第二个厨师那番茄炒蛋和青椒肉丝就能真正同时下锅。这才是并行。把“锅”换成 CPU 核心把“菜”换成任务事情一下就清楚了并发Concurrency处理多个任务的能力任务可以交替执行同一时刻不一定真的同时在跑。并行Parallelism多个任务在同一时刻真正同时执行前提是有多个执行单元多核 CPU、多台机器。单核 CPU 环境下操作系统通过时间片轮转让多个进程“看起来”同时运行这是并发不是并行。双核 CPU 上两个进程分别跑在各自的核心上才是并行。1.2 并发是架构能力并行是物理现实很多人问“PHP 能支持多少并发”其实真正关心的是同时来 1000 个请求我的服务会不会崩。这个问题落在一个维度上服务能不能同时接待很多请求并且每个请求都能在可接受的时间内返回。这属于并发能力的范畴。而“并行”在服务端场景里通常指向另一个问题一个任务能不能拆成多个子任务同时开跑最后合并结果。比如你要处理 100 万行日志4 核机器能不能分成 4 份同时算最后汇总。这两件事是不同层面的并发关注的是“系统同时面对大量请求时的调度能力”。并行关注的是“计算密集型任务如何真正利用多核”。互联网业务里绝大多数场景压垮系统的不是“并行不够”而是“并发来了之后资源被耗死”。这也是为什么 PHP 这种看起来“不够并行”的语言依然能支撑大量线上业务。1.3 并行、并发、异步、分布式一张表理清关系这几个词经常一起出现但代表完全不同层级的关注点。我整理了一张表概念核心问题典型场景关键前提并发多任务如何交错/调度1000 个请求同时打到服务器调度器、资源隔离并行多任务是否真同时执行多核并行处理日志/图像多核 CPU、可拆分任务异步发起操作后是否需要阻塞等待调用外部 API、读取文件事件循环、回调/协程分布式多台机器如何分工协作微服务、负载均衡、消息队列网络、一致性协议想说明一个问题PHP 默认缺的不是“并行”而是“在等待 I/O 时还能腾出手处理其他请求”的异步并发能力。这一点后面展开讲。2. PHP 的并发真相一请求一进程的利与弊2.1 PHP-FPM到底是怎么“接客”的绝大多数 PHP Web 程序跑在 PHP-FPM 里。它的工作模式可以用一句话概括一个 worker 进程同时只能处理一个请求。master 进程负责管理启动时 fork 出若干个 worker。每个 worker 空闲时就等在队列边上Nginx 转发过来一个 fastcgi 请求它接住执行完整个 PHP 脚本返回结果然后继续接下一个。这个模式的优点是进程隔离。一个请求把内存写爆了、把进程搞崩了最多就是那一次请求失败其他 worker 不受影响。相比共享内存的多线程模型PHP 这种“share nothing”架构天然不容易出现内存竞争、死锁这类问题。缺点是资源开销大。一个 PHP-FPM worker 常驻内存通常几十到上百 MB机器内存有限能同时开的 worker 数量就有限。同时来了 2000 个请求而 worker 只有 50 个那 1950 个请求只能在队列里干等。从 Web 场景看这个模型的横向扩展能力其实非常优秀——PHP 应用基本无状态丢到哪台服务器都能跑加机器就能扛更多并发。真正的瓶颈往往出现在单机内部worker 被慢请求占满后续请求全部排队。2.2 “无共享架构”是 PHP 的护城河也是它的边界写 Java 的同学可能会有个疑问为什么不直接搞多线程一个进程里开 200 个线程共享一部分内存这样不是又省资源又能并发吗理由成立但代价是复杂度。共享内存意味着你要处理同步、锁、可见性、死锁、竞态条件。这些问题是并发编程里最难看、最难排查的一类Java 程序员天天要和它们斗争。PHP 选择了一条更“笨”但更稳的路进程之间完全隔离没有共享状态。数据要共享放在 Redis、数据库、文件里。这个取舍让 PHP 开发者在绝大多数业务场景里不需要考虑锁竞争代码写起来简单直接。但也是这个特性让 PHP 很难像 Java 那样在一个进程内优雅地承载大量连接和大量计算任务。它天生是为“请求来了处理一下就走”这种模式设计的不是为“一个进程长期驻留并维护大量连接”设计的。2.3 CPU密集 vs I/O密集PHP真正怕的是什么要判断 PHP 行不行先分清任务是 CPU 密集型还是 I/O 密集型。CPU 密集型大量计算、字符串处理、图像处理、加密解密。这类任务吃满 CPU耗时和计算量成正比。I/O 密集型等待数据库返回、调用第三方接口、读写文件、读写 Redis。这类任务里 PHP 引擎大部分时间在“等待”CPU 占用并不高。PHP Web 业务绝大多数是 I/O 密集。一个接口耗时 500ms可能只有 30ms 是 PHP 代码在计算剩下 470ms 都在等 MySQL 查询、等 Redis 响应、等外部 HTTP 接口。问题就出在等待上PHP-FPM 的 worker 是阻塞式的查询 MySQL 没返回之前这个 worker 就悬在那里什么也不能干。只要慢查询多或者外部接口响应慢很快所有 worker 都会被“挂在等待上”新请求全部排队。所以结论很明确PHP 真正弱的是“阻塞等待期间不能做别的事”而不是计算能力本身。想要改善要么减少等待要么让等待期间能处理其他请求——后者的本质就是引入异步。3. 一次 PHP 请求的前世今生从 Nginx 到响应3.1 完整链路拆解先走一遍一个 PHP 请求的完整链路后面分析瓶颈时更有体感。用户在浏览器输入 URLHTTP 请求到达 Nginx。Nginx 判断如果是静态文件图片、CSS、JS直接自己返回如果是动态请求通过 fastcgi 协议转发给 PHP-FPM。PHP-FPM master 从空闲 worker 队列里挑一个 worker把请求交给它。worker 开始工作加载 php.ini 和扩展、初始化请求上下文、执行入口文件如 index.php、加载框架、解析路由、执行控制器方法。控制器里通常要查数据库、读 Redis、调用第三方 API、渲染模板。结果返回给 NginxNginx 再返回给浏览器。worker 回收资源准备处理下一个请求。每一步都可能变成瓶颈。数据库查询慢整个请求就慢模板渲染里写了巨大的循环CPU 就飙高第三方 API 超时worker 就长时间挂着。3.2 阻塞式 I/O问题通常在等待 MySQL 而非 PHP 本身很多团队把接口慢归咎于“PHP 性能差”打开 Xdebug 一测才发现真正耗时的全是mysqli_query、file_get_contents、curl_exec这类阻塞调用。我见过一个真实接口功能是调用外部地图服务返回附近门店列表。业务方投诉接口太慢平均要 3 秒。后来加了日志发现PHP 脚本自身执行时间是 40ms剩下的 2960ms 全部花在等外部接口返回上。而且因为调外部服务时没设置超时对方服务抖动一次PHP worker 就集体“挂机”好几十秒。这个场景很典型等待不可怕可怕的是一个 worker 在等待期间不能服务其他人。默认模型下一个慢请求占住一个 worker性能瓶颈就被放大了几十倍。3.3 怎么用 status 接口和 slow log 一眼看出瓶颈排查这类问题不需要一上来就上 APM 工具PHP-FPM 自带的两个功能就够用。开启 status 接口在 php-fpm 配置里加pm.status_path /status然后在 Nginx 里放行这个路径浏览器访问http://127.0.0.1/status?full能看到当前所有 worker 的状态、请求耗时、队列长度。?full参数可以看到每个 worker 正在执行的脚本路径和开始时间。另外开启慢日志request_slowlog_timeout 2s slowlog /var/log/php-fpm/slow.log当某个请求执行超过 2 秒会把完整的调用栈写进 slow.log。通过查看调用栈能精准定位是哪个函数卡住——是数据库查询、Redis 连接还是外部接口调用。这两个工具结合使用通常能解决 80% 的 PHP 性能排查需求。4. 四个把 PHP 打趴下的高并发场景以及它们的解法4.1 进程数天花板max_children 到底设多少PHP-FPM 的高并发问题排第一的一定是 worker 进程数设置不合理。很多人直接把pm.max_children设成 512以为越大越好。结果高峰期内存暴涨系统 OOMworker 进程被内核杀掉页面直接 502、504。正确做法是先测出单个 worker 的平均内存再算总账ps -ylC php-fpm --sort:rss假设一台 8G 内存的服务器给 PHP 分配 6G查出来 worker 平均占用 120MB那合理配置大约是6 * 1024 / 120 ≈ 51max_children就设在 50 左右。同时建议设置pm.max_requests 1000让每个 worker 处理 1000 个请求后自动重启避免内存泄漏导致进程无限膨胀。这个数字不是越大越好而是要和请求平均耗时挂钩。举个例子接口平均耗时 100ms一个 worker 每秒能处理 10 个请求50 个 worker 理论上每秒最多处理 500 个请求。如果 QPS 长期超过这个数值调大进程数只是治标更该做的是减少每个请求的耗时。4.2 数据库并发锁ERP 库存扣减怎么避免超卖“高并发”落到具体业务上最常见的就是库存扣减、余额扣减、订单号生成这一类。这类问题的核心不是“PHP 能不能并发”而是并发场景下数据一致性怎么保证。一个经典错误写法$stock $db-query(SELECT stock FROM products WHERE id 1)-fetch(); if ($stock[stock] 0) { $db-exec(UPDATE products SET stock stock - 1 WHERE id 1); }在并发请求下两个请求同时读到 stock1都满足条件都执行了更新库存就变成负数了。这不是 PHP 的错是逻辑就不对。正确做法之一是使用 MySQL 事务加行锁$pdo-beginTransaction(); $stmt $pdo-query(SELECT stock FROM products WHERE id 1 FOR UPDATE); $stock $stmt-fetch(); if ($stock[stock] 0) { $pdo-exec(UPDATE products SET stock stock - 1 WHERE id 1); } $pdo-commit();FOR UPDATE会对这一行加锁在事务提交前其他事务必须等待。这样能保证不会超卖代价是并发扣减变成了串行但库存操作本身吞吐量要求没那么高完全够用。另一个常见做法是用 Redis 原子操作$num $redis-decr(product_stock_1); if ($num 0) { // 库存不足需要把数字加回去并返回错误 $redis-incr(product_stock_1); }decr是原子操作能保证减库存不会出现竞态。但要注意Redis 只是临时计数最终还是要和数据库对账落库。这个例子想说明的是高并发下的数据一致性是靠锁、事务、原子操作保证的不是靠“换一个更快更并行的语言”自动解决的。4.3 同步外部 API 调用流量一来直接把 FPM 拖死还有一类很隐蔽的坑业务代码里串行调用了多个第三方 API。假设下单接口要做三件事调用风控服务、调用短信服务、调用库存服务。三个串行调用每个平均 200ms接口整体耗时 600ms。如果 QPS 只有 2050 个 worker 够用。但如果某个外部服务突然变慢单个调用变成 2 秒那 20 个请求就把所有 worker 占满其他所有请求全部排队服务整体雪崩。解法有几层设置超时所有 cURL 调用必须设CURLOPT_TIMEOUT不给外部服务无限制拖垮你的机会。并行调用如果 PHP 跑在支持协程的 Swoole 环境里可以直接用协程并发发起多个 HTTP 请求。普通 FPM 环境可以用curl_multi_init把多个请求合并并行发出把三个 200ms 从串行 600ms 压缩到 200ms 左右。缓存结果风控、短信发送这类结果通常有重复加一层 Redis 缓存能极大减少外部调用量。我见过最惨的一次事故是接口里调了一个第三方 AI 审核服务对方服务出故障后每个请求要等 10 秒超时。200 QPS 的流量直接把所有 worker 占满整个服务死了 40 分钟。后来加了超时控制和降级开关这种问题才没有再发生。4.4 缓存与热点读放大Redis 在中间扮演的角色高并发场景里数据库永远是最脆弱的环节。一个 MySQL 实例通常能扛几千 QPS但一个简单查询只要慢一点比如关联了 5 张大表几百 QPS 就能把它打爆。PHP 项目里最容易做的优化就是给热点数据加 Redis 缓存。比如商品详情、配置信息、用户资料这些数据读多写少非常适合缓存。但缓存不是一加了事至少要防三个问题缓存穿透查一个不存在的数据每次都会打到数据库。解决方式缓存空值或者用布隆过滤器拦截。缓存击穿某个热点 key 过期瞬间大量请求同时打到数据库。解决方式加互斥锁只让一个请求去重建缓存。缓存雪崩大量 key 在同一时段过期数据库压力瞬间放大。解决方式过期时间加随机值打散过期窗口。在 PHP 里实现互斥锁很轻量用 Redis 的setnx就行$key product_detail_1; $data $redis-get($key); if (!$data) { $lockKey lock_ . $key; // 拿到锁的才去查数据库 if ($redis-setnx($lockKey, 1, [nx, ex 5])) { $data $db-query(SELECT * FROM products WHERE id 1)-fetch(); $redis-setex($key, 300, json_encode($data)); $redis-del($lockKey); } }高并发下 PHP 能不能扛得住很多时候不取决于 PHP 本身而是取决于 Redis、MySQL 这些下游服务是否能接住流量。5. PHP 生态里的并行牌Swoole、Workerman、pcntl 与 parallel5.1 Swoole让 PHP 从“一请求一进程”进入常驻内存事件循环如果业务确实到了 PHP-FPM 扛不住、需要异步并发能力的时候Swoole 是目前最成熟的方案。Swoole 的核心不是“多线程”而是常驻内存 事件循环 协程。Worker 进程启动后不销毁持续接收请求。在每个 worker 内部协程可以让一个进程同时处理大量请求遇到 I/O 操作时自动让出 CPU等 I/O 完成后再回来继续执行。举个协程并发请求的例子use Swoole\Coroutine; Coroutine\run(function () { $client new Swoole\Coroutine\Http\Client(api.example.com, 80); $client-get(/data); echo $client-body; });常规 FPM 下调用一次外部接口进程就要阻塞等待响应。Swoole 协程下这个等待变成了自动挂起和恢复进程可以去处理别的请求。这就把前面说的“worker 干等”问题解决了。实测中一台 8G 内存的机器跑 Swoole 服务维持几万个长连接是很轻松的事。而 PHP-FPM 模式下同样配置可能几百个请求就把 worker 打满。不过 Swoole 也有学习成本需要常驻内存思维、协程安全不能再像 FPM 那样随意写全局变量。5.2 Workerman 与 ReactPHP纯 PHP 的异步可行解并不是所有项目都能装 Swoole 扩展。有些环境受限于服务器权限或团队维护能力这时可以选纯 PHP 的异步方案。Workerman 是完全用 PHP 写的常驻内存网络框架内置事件循环支持 TCP/UDP/WebSocket。部署时不需要额外安装 PHP 扩展对现有项目兼容性好很多团队用它做消息推送、聊天、物联网网关。ReactPHP 更底层一些相当于 PHP 界的 Node.js 事件循环库可以自己拼出异步 HTTP 客户端、异步 Redis 客户端等。这两类方案在纯业务敏捷性上比 Swoole 弱一些但胜在简单、可控、纯 PHP 代码可读性高。如果你的场景主要是 WebSocket 推送和中低并发的长连接服务Workerman 完全够用。5.3 parallel 扩展与 pcntl_fork多线程和多进程的底牌如果需求是做“真正的并行”——一个任务拆成多个子任务同时跑——那要看是进程还是线程。CLI 环境下pcntl_fork()可以创建子进程把一个大任务分给多个进程并行处理。这种模式适合批量数据处理、多队列消费者。比如我要处理 100 万条消息机器有 4 核就 fork 4 个子进程每个处理 25 万条总耗时能压到接近四分之一。PHP 7.2 之后官方推出了 parallel 扩展提供真正的多线程。但使用门槛比较高需要满足zend.assertions 1 zend.exception_ignore_args 1 opcache.enable_cli 1而且 parallel 不适合 Web 环境它适合 CLI 下做计算密集型任务的分片比如大文件解析、敏感词扫描、数据转换。从实际使用上看能用 pcntl_fork 解决的批量并行场景可能比上 parallel 更稳。进程隔离的容错性更好一个子进程挂了不会影响其他进程。5.4 方案对比与选型建议方案运行模式是否常驻适合场景主要成本PHP-FPM多进程一请求一进程否常规 Web API、后台管理简单可控但并发上限较低Swoole事件循环协程是长连接、WebSocket、高 QPS API学习曲线、协程安全Workerman事件循环是纯 PHP 的 WebSocket/TCP 服务性能上限低于 Swoolepcntl_fork多进程并行否CLI 批量任务拆分进程间通信、结果汇总parallel多线程并行否CPU 密集计算分片配置苛刻、调试困难选型有一条铁律先看业务形态再选方案。常规 CRUD 系统、后台管理、中等流量 APIFPM 加缓存完全够用没必要上 Swoole。真到了需要维护几十万长连接或者单接口 P99 有硬性要求的阶段再上常驻内存方案也不迟。6. 一次真实的压测与调优记录数据比观点更有说服力6.1 压测准备ab 和 JMeter 的最小可用姿势纸上谈兵没有用建议自己动手压一次。最小可用方案用 Apache 自带的ab就够了ab -n 20000 -c 200 http://127.0.0.1/test.php-n 20000表示总共发 20000 个请求-c 200表示同时保持 200 个并发连接。如果想做更复杂的场景脚本登录、连续请求、带参数可以用 JMeter。线程组设 200 个线程循环次数设 100加一个聚合报告监听器压完直接看 QPS、平均响应时间、错误率。JMeter 的好处是结果可视化清晰适合做团队内的压测报告。压测要在独立环境做不能直接在线上服务器压否则干扰真实流量。压测机和目标服务器最好分两台避免压测本身抢占资源。6.2 压测数据200 并发下的崩溃现场拿一台 8核8G 的云服务器举例配置如下PHP-FPMpm.max_children 64没有开 OPcache接口逻辑查一次 MySQL 全表列表返回 JSON压测结果指标数据总请求数20000并发数200QPS约 120平均响应时间约 890msP95 响应时间约 1200ms错误率约 9.6%日志里能看到大量这样的警告WARNING: [pool www] server reached pm.max_children setting (64), consider raising it说明 64 个 worker 全被占满新的请求开始排队队列越长响应时间越慢最终部分请求直接超时失败。这个现场很多人遇到过配置看着不低但流量一大就 502、504。调试半天最后发现是 worker 满载导致。6.3 三步调优OPcache、DB 连接复用、Redis 缓存针对上面的问题不需要换语言先做三步基础调优。第一步开 OPcachePHP 每次请求都要把 PHP 文件从磁盘读出来、解析成字节码、再执行。OPcache 把解析后的字节码存在共享内存里省掉重复解析的时间。opcache.enable1 opcache.memory_consumption128 opcache.max_accelerated_files10000 opcache.validate_timestamps0实际效果纯 PHP 执行耗时通常能下降 30% 到 50%。第二步处理 MySQL 连接FPM 模式下每次请求都会建立一个新的 MySQL 连接高并发下连接数会非常高MySQL 本身也会成为瓶颈。排查数据库连接数如果发现有大量 TIME_WAIT说明连接没有被复用。可以用 PDO 长连接注意不是每个请求都 new PDO或者引入数据库中间层做连接池。不过 PHP-FPM 默认模型下长短连接各有坑需要结合实际部署选型。第三步接口走 Redis 缓存压测接口是个全表查询列表数据 30 分钟内基本不变。最直接的办法就是读 Redis。$key product_list_page_1; $data $redis-get($key); if (!$data) { $data $db-query(SELECT * FROM products LIMIT 10)-fetchAll(); $redis-setex($key, 1800, json_encode($data)); } echo $data;同时把max_children从 64 调回合理的 50加上max_requests 1000避免进程无限膨胀。调优后同一台机器再压一次指标调优前调优后QPS约 120约 320平均响应时间约 890ms约 180msP95 响应时间约 1200ms约 260ms错误率9.6%0.15%这不是 PHP“变强了”而是把资源用在刀刃上减少了重复解析、减少了对数据库的依赖、规避了 worker 占满后的排队。6.4 复盘PHP 真不行的场景和 PHP 背锅的场景压测之后你会发现很多“PHP 性能不行”的结论来源于配置没优化、代码绕了远路、数据库打满这些可解决的问题。PHP 真正不行的场景是什么我的看法是几十万级长连接并发的网关服务这不是 FPM 模型擅长的事。海量实时推送的推送服务需要事件驱动PHP 默认模型确实做不到。极致 CPU 并行计算比如训练模型、大规模数据管道这类场景请交给更合适的语言和框架。在这些场景里坚持“用 PHP 硬扛”没有意义换技术栈是理智的选择。但反过来说一个只有几百 QPS 的业务因为听说 PHP 高并发不行就全组切 Go这也是拿大炮打蚊子。我做过的实践是核心的长连接推送服务用 Swoole 单独起了一个常驻服务其余业务接口继续保留 PHP-FPM 跑在 Nginx 后面。这样的混合架构既解决了并发瓶颈又没有推翻原有业务代码上线过程非常平稳。7. 结合业务场景的选型复盘以及我踩过的坑7.1 三类典型业务场景到底选谁把前面讲的东西落到选型上我习惯把业务分成三类业务类型特征推荐方案常规 Web 后台登录、后台管理、简单 CRUDPHP-FPM OPcache Redis高 QPS API 服务日活百万、接口 P95 有要求Swoole/Workerman 常驻内存长连接/推送服务WebSocket、IM、实时推送Swoole 或 Workerman计算密集任务日志处理、数据清洗、批量脚本pcntl_fork/parallel或直接换语言如果业务既有 Web 后台又有实时推送建议拆成两个服务。Web 后台保持 FPM 模式推送服务单独用常驻内存方案。不要试图一个框架解决所有问题。7.2 一个被并发打挂的 PHP 服务的抢救过程去年我接手过一个内部统计报表服务每天凌晨批量任务跑完后要往报表库写几十万行数据然后业务方要立刻看到统计结果。最初实现是在 PHP-FPM 里写了一个同步接口内部循环插入十几万条数据。结果一到整点高峰多个业务方同时打开报表接口慢到超时Nginx 返回 504。更糟的是慢请求占满了 FPM worker其他部门调这个服务的基础接口也跟着全挂。抢救分了两步第一步把“批量写库”从同步接口改成异步任务接口只接收请求、返回“已受理”真正写入操作放进 Redis 队列由 CLI 消费进程慢慢处理。第二步消费进程用pcntl_fork分成了 4 个子进程并行消费写库能力提升了将近 3 倍。高峰期数据库压力依然存在但不再占 FPM worker核心接口不会因为慢任务被拖垮。改造之后服务没再出过大故障原来的接口响应从 30 秒压到 200ms 以内。这个案例让我格外注意一点高并发下慢任务和快任务必须隔离。让慢任务拖住快任务的资源是 FPM 架构下最惨烈的死法。7.3 别让“PHP 不能高并发”变成一种偷懒借口这几年听了很多关于 PHP 的讨论有一种论调很流行“PHP 高并发不行所以要用 Java/Go 重写。”重写没有错但重写前应该先问自己几个问题当前系统的瓶颈真的在 PHP 吗还是在下游的 MySQL、Redis 配置有没有先做过 OPcache、缓存、慢查询、worker 配置这类基础优化现有的并发量是否真的到了 FPM 的上限我在实际项目里踩过太多“并发问题”最后发现是缓存代码写错、没有设超时、数据库少建了一个索引、worker 数拍脑袋设了一个离谱值。这些问题换成任何语言都会出现。我个人的原则是先优化到当前模型的天花板再谈架构升级。如果基础优化做完了FPM 模型还是到极限那就大胆地换 Swoole 或引入 Go/Java 写专项服务。这时候换技术栈是有数据支撑的决策而不是拍脑袋跟风。PHP 并行不等于并发这是概念问题而用不用 Swoole、换不换语言是架构决策问题。把这两件事分开很多争论自然就停了。
返回列表