Laravel之Roadrunner鏡像

Roadrunner介紹

文檔:roadrunner官方文檔
roadrunner 是 go 語言開發(fā)的http服務(wù)器,主要實(shí)現(xiàn)兩個(gè)功能。
1、監(jiān)聽端口,接收外部客戶端的 http 請求。
2、管理 php 常駐進(jìn)程,使用 psr7 規(guī)范封裝請求報(bào)文、頭部等信息,通過 sock/pipe 方式通信,并將 php 的執(zhí)行結(jié)果回傳給請求客戶端。

php-fpm 與 roadrunner 區(qū)別簡單對比
生命周期

image.png

PHP執(zhí)行說明
例:執(zhí)行 HomeController@index
php-fpm的work進(jìn)程未銷毀前,每觸發(fā)一次web請求,會(huì)讀取一次 HomeController.php 文件內(nèi)容,所以修改業(yè)務(wù)代碼即時(shí)生效。類似work進(jìn)程執(zhí)行了一個(gè) white(true) 循環(huán),內(nèi)部執(zhí)行完 php 代碼后,會(huì)初始化到未執(zhí)行前狀態(tài)等待下次請求。

roadrunner 執(zhí)行創(chuàng)建的 psr-worker 進(jìn)程未銷毀前,僅第一次觸發(fā) web 請求時(shí)讀取HomeController.php 文件內(nèi)容,同時(shí)保存到內(nèi)存中,后續(xù)觸發(fā)將不再讀取文件,而是從內(nèi)存中取出,所以修改代碼不會(huì)即時(shí)生效。類似 php 內(nèi)部實(shí)現(xiàn)了 white(true) 循環(huán),在循環(huán)內(nèi)接收每一次 web 請求攜帶的參數(shù),執(zhí)行不同業(yè)務(wù)邏輯,返回響應(yīng)。這是真正意義上的常駐進(jìn)程。

本地初次安裝使用

  1. composer 依賴安裝

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

  1. 業(yè)務(wù)項(xiàng)目根目錄創(chuàng)建文件

.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 區(qū)別:
Laravel 需要調(diào)用 terminate 方法,執(zhí)行一些類似 session 寫入的后置邏輯。
.rr.yaml 區(qū)別:
Laravel 需要增加 static 配置,來指定靜態(tài)文件訪問。
Windows 環(huán)境請注意:
由于默認(rèn)設(shè)置的監(jiān)聽 Unix Sock,在 Windows 環(huán)境下會(huì)無法運(yùn)行,需要做兩處改動(dòng)。
.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 # 宿主機(jī)代碼目錄映射到容器中
command: rr serve -v -d -c .rr.yaml
environment:
XDEBUG_PORT: 9001
XDEBUG_HOST: 192.168.0.119
ports:
- "8000:80"           # 端口映射

注意事項(xiàng)

  1. 修改代碼后如果不重啟鏡像,新代碼如何生效?
    a) 執(zhí)行一次 http 請求,worker 進(jìn)程自動(dòng)重啟
    .rr.yaml 指定 worker 進(jìn)程數(shù)量 和 執(zhí)行任務(wù)次數(shù)

numWorkers:1
maxJobs:1
numWorkers:#number of workers to be serving.
maxJobs:#maximum jobs per worker, 0 – unlimited.

設(shè)置后只存在一個(gè) psr-worker 進(jìn)程,每執(zhí)行一次請求,psr-worker 進(jìn)程會(huì)銷毀重建

注意:如果修改預(yù)先加載處的代碼,比如 Provider、Middleware,第一次觸發(fā)仍會(huì)執(zhí)行內(nèi)存中的舊代碼,第二次新代碼才會(huì)生效。

b) 進(jìn)入容器,執(zhí)行命令:rr http:reset,將重啟所有的 php 進(jìn)程。

c) Reload Configuration 自動(dòng)監(jiān)控文件改動(dòng)
.rr.yaml 增加 reload 監(jiān)控配置項(xiàng)

# 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 配置空字符串,會(huì)默認(rèn)檢測根目錄內(nèi)所有文件,非常消耗 CPU 資源同時(shí)等待時(shí)間會(huì)很久,而不是設(shè)置的 1s。所以盡量手動(dòng)設(shè)置檢測目錄,比如監(jiān)控 app 和 routes 內(nèi) php 文件,則配置 dirs: ["app", "routes"]。也可以增加配置 ignore,指定目錄內(nèi)的文件修改會(huì)被忽略。

兩種推薦配置
1、設(shè)置監(jiān)聽目錄
dirs: ["app", "config", "routes", "storage"]
ignore: [""]
2、設(shè)置監(jiān)聽忽略目錄
dirs: [""]
ignore: ["bootstrap", "database", "public", "resources", "tests", "vendor"]
上述配置測試 Laravel5.6 項(xiàng)目,第二種 CPU 消耗略高

d) maxJobs 必須不設(shè)置或設(shè)置成 0,否則此種方案是沒有意義的??戽I鍵映射觸發(fā)執(zhí)行 sh 腳本,對指定項(xiàng)目執(zhí)行 rr http:reset 命令。以 terminal-vim 環(huán)境為例,IDE 理論上也存在映射功能,此處不研究。(Windows 用戶可嘗試快鍵鍵是否可以映射 docker 容器命令)
1、docker-compose.yml 同目錄下創(chuàng)建 reset-rr-worker.sh 腳本文件

#!/bin/bash

# 已啟用 rr 鏡像的項(xiàng)目目錄列表
itemArray=(
'management-frontend-lanqb'
'sales-backend-lanqb'
'sales-backend-duoji'
'user-management-backend-lanqb'
'school-backend-lanqb'
'copartner-backend-lanqb'
)

# 當(dāng)前執(zhí)行腳本的項(xiàng)目目錄,用于拆分項(xiàng)目名和跳轉(zhuǎn)回去
path=`PWD`
echo "execute path:${path}"
# 截取傳入的項(xiàng)目名
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 文件增加快鍵鍵映射,要對應(yīng)本地的腳本文件目錄

noremap <leader>R :! ~/www/local-dev-env/reset-rr-worker.sh<CR>

開發(fā)過程中,可以兩鍵重啟進(jìn)程,刷新代碼。

四種方案各有優(yōu)劣,應(yīng)根據(jù)實(shí)際情況自行選擇,部分缺點(diǎn)如下:

1、每次請求都將銷毀重啟 worker 進(jìn)程,即使代碼沒有改動(dòng)。部分位置代碼第二次請求才會(huì)改動(dòng)。
2、需要進(jìn)入容器內(nèi)部,手動(dòng)執(zhí)行命令。
3、CPU 有額外消耗。
4、非 terminal-vim 模式,配置可能比較麻煩,另外也需要本地單獨(dú)配置目錄。強(qiáng)烈推薦 mac、windows (如果 IDE 可以映射)用戶使用,兩種環(huán)境 docker 讀取主機(jī)文件較慢,減少不必要的進(jìn)程重啟過程。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Welcome 目前網(wǎng)絡(luò)上充斥著大量的陳舊信息,讓PHP新手誤入歧途,傳播著錯(cuò)誤的實(shí)踐和糟糕的代碼,這必須得到糾正...
    layjoy閱讀 21,833評論 7 118
  • 傳統(tǒng)基于LNMP的Web架構(gòu)中,Nginx作為Web服務(wù)器,PHP-FPM維護(hù)一個(gè)進(jìn)程池去運(yùn)行Web項(xiàng)目。簡單、成...
    JunChow520閱讀 3,729評論 0 7
  • 前言 這里筑夢師,是一名正在努力學(xué)習(xí)的iOS開發(fā)工程師,目前致力于全棧方向的學(xué)習(xí),希望可以和大家一起交流技術(shù),共同...
    筑夢師Winston閱讀 26,434評論 80 514
  • Composer Repositories Composer源 Firegento - Magento模塊Comp...
    零一間閱讀 4,017評論 1 66
  • PHP優(yōu)化 默認(rèn)安裝的 PHP 就像是在百貨商店里購買的普通套裝,雖然合身,卻不完美。調(diào)優(yōu)的 PHP 就像是定做的...
    師娘哪里去了閱讀 538評論 0 0

友情鏈接更多精彩內(nèi)容