roadrunner 比 php-fpm 快因常驻内存复用 php 进程与 laravel 实例,避免重复加载框架和初始化,实测 qps 可达 1200+(fpm 仅 300+);但需注意状态残留、配置适配及生命周期陷阱。

为什么 RoadRunner 能比 PHP-FPM 快
RoadRunner 启动后常驻内存,复用 PHP 进程和 Laravel 应用实例,避免每次请求都重新加载框架、解析配置、重建容器。FPM 每次请求都要 fork 子进程 + 初始化 Laravel,尤其在高并发小请求场景下,开销明显。RoadRunner 的 php-worker 是长期运行的,配合 Swoole 或原生 pcntl 管理子进程,吞吐量通常能翻 2–4 倍(实测 Laravel 10 API 接口 QPS 从 300+ 提升到 1200+)。
但要注意:它不是“开箱即用”的 FPM 替代品——Laravel 的某些生命周期假设(比如每次请求都是干净的全局状态)会被打破,$app 实例、静态变量、单例对象会跨请求残留。
安装 RoadRunner 并对接 Laravel 的关键步骤
别直接用 rr 命令全局安装——Laravel 项目必须绑定特定版本的二进制与插件。推荐用 spiral/roadrunner-binary Composer 包管理:
composer require spiral/roadrunner-binary --dev
然后生成配置文件:
php artisan rr:install
这会在项目根目录生成 .rr.yaml。重点改两处:
-
http.address改成:8080(别用localhost:8080,RoadRunner 内部用 Unix socket 或 TCP 直连,填 host 会导致连接失败) -
services.http.pool.num_workers初始设为4(别盲目调高;超过 CPU 核数太多反而因上下文切换拖慢) - 确保
logs.level设为error或warn,否则日志刷屏影响性能
Laravel 代码里必须改掉的三个陷阱
RoadRunner 复用 Worker 进程,意味着 AppServiceProvider 的 boot()、全局中间件、甚至 __construct() 都不会每请求执行一次。常见崩点:
- 在
boot()里直接调用Cache::put()或Config::set()—— 这些值会污染后续所有请求。应改到中间件或控制器内按需设置 - 使用
static $counter = 0; $counter++类型的静态计数器 —— 它会在 Worker 生命周期内持续累加,变成“全局共享变量” - 依赖
$_SERVER['REQUEST_TIME_FLOAT']或microtime(true)做超时判断?没问题;但若用time()+ 缓存时间戳做“每分钟只执行一次”,就会失效——Worker 不重启,time()值永远在变,但你的逻辑没重置
简单验证方式:在中间件里打日志写 getmypid(),发起连续 5 次请求,看 PID 是否相同 —— 相同就说明复用生效了,也意味着你得检查状态残留问题。
启动、调试和上线前必查项
启动命令是:
./rr serve
别用 php ./rr serve,rr 是 Go 二进制,不是 PHP 脚本。启动后默认监听 :8080,前端 Nginx 需反向代理过去:
location / { proxy_pass http://127.0.0.1:8080; }
容易被忽略的坑:
-
.rr.yaml里rpc.listen默认关闭,但如果你用spiral/jobs或需要热重载,得手动打开并配好地址(如tcp://127.0.0.1:6001) - 本地开发时,
php artisan tinker无法直接连 RoadRunner,它只处理 HTTP 请求;调试逻辑仍要用dd()或日志 - 部署到生产环境前,务必关掉
debug: true,且确认env是production——RoadRunner 会缓存配置,.env 变更不重启服务不会生效
最麻烦的其实是“你以为改完了,其实某个包的 ServiceProvider 在 boot 里偷偷注册了全局钩子”,这种问题只能靠逐个禁用第三方包来排查。上线前至少压测 10 分钟,观察内存是否缓慢上涨——那是典型的资源未释放信号。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











