发布于2026-07-11 阅读(0)
扫一扫,手机访问
想要让PHP API接口在万级并发下依然保持毫秒级响应,传统PHP-FPM那种每次请求都创建进程、销毁进程、阻塞I/O的模式必须淘汰。正确的做法是直接用Swoole常驻内存+协程调度,接管整个请求生命周期——这才是当下的最优解。
说白了,PHP-FPM的瓶颈在于:每个请求都要重新加载框架、创建数据库连接、用完即毁,再加上阻塞式IO导致CPU大量空转。而Swoole把所有进程常驻在内存里,用协程替代线程,一个worker进程就能同时处理成千上万个请求——这才是实现高性能API的底层逻辑。
第一步,直接拿SwooleHttpServer替换掉Apache/Nginx+PHP-FPM那套组合,让它自己监听端口并开启协程。
$server = new SwooleHttpServer('0.0.0.0', 9501);
关键配置必须到位:enable_coroutine => true 开启自动协程包裹,worker_num => swoole_cpu_num() * 2 填满物理核的并发能力,max_coroutine => 100000 确保高并发时协程不被挤爆。
剩下的就简单了——在onRequest回调里处理所有请求。注意,此时每个请求已经自动运行在独立协程中,完全不需要手动go()。底层会自动调度。
这里有两种成熟的做法。
方法一:手动解析路径与动词
提取$request->server['path_info']和$request->server['request_method'],然后用switch按路径段数和动词组合做分发。比如/users/123 + PUT → 更新用户;/posts + GET → 列表分页。轻量、可控,适合小项目。
方法二:引入无状态路由库
用nikic/fast-route这类预编译路由表的库,能避免每次请求都做正则匹配。实测表明,当路由规则超过50条时,这一步能省掉约12%的CPU时间——尤其在QPS高的时候,省下来的资源很可观。
【务必警惕:路由分发前绝不能做任何阻塞操作,比如file_get_contents或mysql_connect。一旦阻塞,协程无法让出,整个worker就会卡死,并发能力直接归零。】
数据层面的调用必须全面换血:用SwooleCoroutineMySQL替代mysqli,用SwooleCoroutineRedis替代phpredis,用SwooleCoroutineHttpClient替代curl。
举个例子,查询用户时直接写$db->query("SELECT * FROM users WHERE id = ?", [$uid]),底层会自动挂起当前协程等待网络回包。挂起的同时,CPU会立刻切换到其他请求去处理——这才是协程真正的威力。
但这里有个大坑:如果你的项目已经用了PDO或Eloquent,必须替换成协程适配版本。哪怕你开了enable_coroutine,PDO底层依然是阻塞调用,协程根本不会让出,等于白搞。
连接池是协程高性能的基石,少了它,哪怕代码写得再优雅,并发一上来数据库就扛不住。
第一步:定义MySQL连接池工厂,限制最大连接数(比如20个)。这能避免瞬时并发把所有数据库连接打爆。
第二步:每次请求从池子里get()一个连接,用完立即put()归还,千万不能close()——连接复用才是性能的关键。
第三步:Redis同理,用SwooleCoroutinePool包装CoRedis实例,池大小建议设为worker_num × 4,防止多个协程争抢。
不做这一步的后果很直接:1000并发时可能瞬间建立3000个Redis连接,数据库直接拒绝新连接,服务雪崩。
对于那些重复查询、数据不常变动的接口(比如商品列表、配置中心),直接上静态缓存,彻底绕开数据库。
方法1:SwooleTable缓存
用SwooleTable在内存中缓存分页列表,结构设计成page_no → {data, expire_at},TTL设60秒。更新数据时广播信号清空相关页,简单高效。
方法2:静态JSON文件
生成JSON文件到/runtime/api_cache/目录,用file_get_contents读取。协程下该函数已被hook为非阻塞,比Table省内存,但多一次磁盘IO——适合对内存敏感的场景。
方法3:定时预热
配合crontab每5分钟预热热门接口,直接写入Table或文件,请求过来时直接命中缓存,零计算开销。
实践证明,高频商品列表这类接口走静态化后,QPS能从800跃升至12000+,平均延迟压到0.8ms以内——这才是万级并发下毫秒响应该有的样子。