美文网首页
Laravel之Roadrunner镜像

Laravel之Roadrunner镜像

作者: 上善丨若水 | 来源:发表于2020-12-31 11:59 被阅读0次

    Roadrunner介绍

    文档:roadrunner官方文档
    roadrunner 是 go 语言开发的http服务器,主要实现两个功能。
    1、监听端口,接收外部客户端的 http 请求。
    2、管理 php 常驻进程,使用 psr7 规范封装请求报文、头部等信息,通过 sock/pipe 方式通信,并将 php 的执行结果回传给请求客户端。

    php-fpm 与 roadrunner 区别简单对比
    生命周期

    image.png

    PHP执行说明
    例:执行 HomeController@index
    php-fpm的work进程未销毁前,每触发一次web请求,会读取一次 HomeController.php 文件内容,所以修改业务代码即时生效。类似work进程执行了一个 white(true) 循环,内部执行完 php 代码后,会初始化到未执行前状态等待下次请求。

    roadrunner 执行创建的 psr-worker 进程未销毁前,仅第一次触发 web 请求时读取HomeController.php 文件内容,同时保存到内存中,后续触发将不再读取文件,而是从内存中取出,所以修改代码不会即时生效。类似 php 内部实现了 white(true) 循环,在循环内接收每一次 web 请求携带的参数,执行不同业务逻辑,返回响应。这是真正意义上的常驻进程。

    本地初次安装使用

    1. composer 依赖安装

    composer require spiral/roadrunner
    composer require symfony/psr-http-message-bridge
    composer require nyholm/psr7

    1. 业务项目根目录创建文件

    .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);

    1. 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"           # 端口映射

    注意事项

    1. 修改代码后如果不重启镜像,新代码如何生效?
      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 读取主机文件较慢,减少不必要的进程重启过程。

    相关文章

      网友评论

          本文标题:Laravel之Roadrunner镜像

          本文链接:https://www.haomeiwen.com/subject/eauloktx.html