Roadrunner介绍
文档:roadrunner官方文档
roadrunner 是 go 语言开发的http服务器,主要实现两个功能。
1、监听端口,接收外部客户端的 http 请求。
2、管理 php 常驻进程,使用 psr7 规范封装请求报文、头部等信息,通过 sock/pipe 方式通信,并将 php 的执行结果回传给请求客户端。
php-fpm 与 roadrunner 区别简单对比
生命周期
PHP执行说明
例:执行 HomeController@index
php-fpm的work进程未销毁前,每触发一次web请求,会读取一次 HomeController.php 文件内容,所以修改业务代码即时生效。类似work进程执行了一个 white(true) 循环,内部执行完 php 代码后,会初始化到未执行前状态等待下次请求。
roadrunner 执行创建的 psr-worker 进程未销毁前,仅第一次触发 web 请求时读取HomeController.php 文件内容,同时保存到内存中,后续触发将不再读取文件,而是从内存中取出,所以修改代码不会即时生效。类似 php 内部实现了 white(true) 循环,在循环内接收每一次 web 请求携带的参数,执行不同业务逻辑,返回响应。这是真正意义上的常驻进程。
本地初次安装使用
- composer 依赖安装
composer require spiral/roadrunner
composer require symfony/psr-http-message-bridge
composer require nyholm/psr7
- 业务项目根目录创建文件
.rr.yaml: roadrunner配置文件
psr-worker.php: php入口文件
psr-worker.php.laravel
<?php
use Spiral\Goridge;
use Spiral\RoadRunner;
use \Illuminate\Http\Request;
use Nyholm\Psr7\Factory\Psr17Factory;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
ini_set('display_errors', 'stderr');
require 'vendor/autoload.php';
$app = require_once __DIR__.'/bootstrap/app.php';
if (!$app) {
throw new \RuntimeException('Not found lumen bootstrap file.');
}
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$relay = new Goridge\SocketRelay("rr.sock", null, Goridge\SocketRelay::SOCK_UNIX);
// $relay = new Spiral\Goridge\StreamRelay(STDIN, STDOUT);
$worker = new RoadRunner\Worker($relay);
$psr7 = new RoadRunner\PSR7Client($worker);
$httpFoundationFactory = new HttpFoundationFactory();
while ($req = $psr7->acceptRequest()) {
try {
$symfonyRequest = $httpFoundationFactory->createRequest($req);
$request = Request::createFromBase($symfonyRequest);
$response = $kernel->handle($request);
$psr17Factory = new Psr17Factory();
$psr7factory = new PsrHttpFactory(
$psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory
);
$psr7response = $psr7factory->createResponse($response);
$psr7->respond($psr7response);
$kernel->terminate($request, $psr7response);
} catch (\Throwable $e) {
$psr7->getWorker()->error((string)$e);
}
}
psr-worker.php.lumen
<?php
use Spiral\Goridge;
use Spiral\RoadRunner;
use \Illuminate\Http\Request;
use Nyholm\Psr7\Factory\Psr17Factory;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
ini_set('display_errors', 'stderr');
require 'vendor/autoload.php';
$app = require_once __DIR__.'/bootstrap/app.php';
if (!$app) {
throw new \RuntimeException('Not found lumen bootstrap file.');
}
$kernel = $app->make('Laravel\Lumen\Application');
$relay = new Goridge\SocketRelay("rr.sock", null, Goridge\SocketRelay::SOCK_UNIX);
// $relay = new Spiral\Goridge\StreamRelay(STDIN, STDOUT);
$worker = new RoadRunner\Worker($relay);
$psr7 = new RoadRunner\PSR7Client($worker);
$httpFoundationFactory = new HttpFoundationFactory();
while ($req = $psr7->acceptRequest()) {
try {
$symfonyRequest = $httpFoundationFactory->createRequest($req);
$request = Request::createFromBase($symfonyRequest);
$response = $kernel->handle($request);
$psr17Factory = new Psr17Factory();
$psr7factory = new PsrHttpFactory(
$psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory
);
$psr7response = $psr7factory->createResponse($response);
$psr7->respond($psr7response);
} catch (\Throwable $e) {
$psr7->getWorker()->error((string)$e);
}
}
.rr.yaml.laravel
http:
address: 0.0.0.0:80
workers:
command: "php psr-worker.php"
relay: "unix://rr.sock"
pool:
numWorkers: 1
maxJobs: 1
http2.h2c: true
# static file serving. remove this section to disable static file serving.
static:
# root directory for static file (HTTP would not serve .php and .htaccess files).
dir: "public"
# list of extensions for forbid for serving.
forbid: [".php", ".htaccess"]
# Always specifies list of extensions which must always be served by static
# service, even if file not found.
Always: [".css", ".js", ".ico", ".txt", ".svg", ".woff", ".ttf", ".gif", ".eot", ".swf"]
.rr.yaml.lumen*
http:
address: 0.0.0.0:80
workers:
command: "php psr-worker.php"
relay: "unix://rr.sock"
pool:
numWorkers: 1
maxJobs: 1
http2.h2c: true
psr-worker.php 区别:
Laravel 需要调用terminate
方法,执行一些类似 session 写入的后置逻辑。
.rr.yaml 区别:
Laravel 需要增加static
配置,来指定静态文件访问。
Windows 环境请注意:
由于默认设置的监听Unix Sock
,在 Windows 环境下会无法运行,需要做两处改动。
.rr.yaml
relay: "tcp://:7000"
psr-worker.php
$relay = new Goridge\SocketRelay("localhost", 7000);
- docker-compose.yml 配置
user:
image: registry.cn-hangzhou.aliyuncs.com/duojii/roadrunner-base:dev
volumes:
- /home/www/user:/var/www # 宿主机代码目录映射到容器中
command: rr serve -v -d -c .rr.yaml
environment:
XDEBUG_PORT: 9001
XDEBUG_HOST: 192.168.0.119
ports:
- "8000:80" # 端口映射
注意事项
- 修改代码后如果不重启镜像,新代码如何生效?
a) 执行一次 http 请求,worker 进程自动重启
.rr.yaml
指定 worker 进程数量 和 执行任务次数
numWorkers:1
maxJobs:1
numWorkers:#number of workers to be serving.
maxJobs:#maximum jobs per worker, 0 – unlimited.
设置后只存在一个 psr-worker 进程,每执行一次请求,psr-worker 进程会销毁重建
注意:如果修改预先加载处的代码,比如 Provider、Middleware,第一次触发仍会执行内存中的旧代码,第二次新代码才会生效。
b) 进入容器,执行命令:rr http:reset
,将重启所有的 php 进程。
c) Reload Configuration 自动监控文件改动
.rr.yaml
增加 reload 监控配置项
# reload can reset rr servers when files change
reload:
# refresh interval (default 1s)
interval: 1s
# file extensions to watch, defaults to [.php]
patterns: [".php"]
# list of services to watch
services:
http:
# list of dirs, "" root
dirs: [""]
# ignored dirs
ignore: [""]
# include sub directories
recursive: true
注意: dirs 配置空字符串,会默认检测根目录内所有文件,非常消耗 CPU 资源同时等待时间会很久,而不是设置的 1s。所以尽量手动设置检测目录,比如监控 app 和 routes 内 php 文件,则配置 dirs: ["app", "routes"]。也可以增加配置 ignore,指定目录内的文件修改会被忽略。
两种推荐配置
1、设置监听目录
dirs: ["app", "config", "routes", "storage"]
ignore: [""]
2、设置监听忽略目录
dirs: [""]
ignore: ["bootstrap", "database", "public", "resources", "tests", "vendor"]
上述配置测试 Laravel5.6 项目,第二种 CPU 消耗略高
d) maxJobs
必须不设置或设置成 0,否则此种方案是没有意义的。快键键映射触发执行 sh 脚本,对指定项目执行 rr http:reset
命令。以 terminal-vim 环境为例,IDE 理论上也存在映射功能,此处不研究。(Windows 用户可尝试快键键是否可以映射 docker 容器命令)
1、docker-compose.yml
同目录下创建 reset-rr-worker.sh
脚本文件
#!/bin/bash
# 已启用 rr 镜像的项目目录列表
itemArray=(
'management-frontend-lanqb'
'sales-backend-lanqb'
'sales-backend-duoji'
'user-management-backend-lanqb'
'school-backend-lanqb'
'copartner-backend-lanqb'
)
# 当前执行脚本的项目目录,用于拆分项目名和跳转回去
path=`PWD`
echo "execute path:${path}"
# 截取传入的项目名
newPath=${path##*/}
# 切换本地docker-compose目录
cd ~/www/local-dev-env
for item in ${itemArray[@]};
do
if [ "$newPath" == "$item" ]
then
docker-compose exec -T $item /usr/local/bin/rr http:reset
if [ $? -ne 0 ]; then
echo "rr-worker reset failed"
else
echo "rr-worker reset succeed"
fi
exit 0
fi
done
echo "invalid path"
2、init.vim 文件增加快键键映射,要对应本地的脚本文件目录
noremap <leader>R :! ~/www/local-dev-env/reset-rr-worker.sh<CR>
开发过程中,可以两键重启进程,刷新代码。
四种方案各有优劣,应根据实际情况自行选择,部分缺点如下:
1、每次请求都将销毁重启 worker 进程,即使代码没有改动。部分位置代码第二次请求才会改动。
2、需要进入容器内部,手动执行命令。
3、CPU 有额外消耗。
4、非 terminal-vim 模式,配置可能比较麻烦,另外也需要本地单独配置目录。强烈推荐 mac、windows (如果 IDE 可以映射)用户使用,两种环境 docker 读取主机文件较慢,减少不必要的进程重启过程。
网友评论