展開文件目錄

Laravel Octane

簡介

Laravel Octane 透過使用高效能的應用程式伺服器,包括 FrankenPHPOpen SwooleSwooleRoadRunner,大幅提升您的應用程式效能。Octane 只需啟動您的應用程式一次,將其保留在記憶體中,然後以超音速速度處理請求。

安裝

Octane 可透過 Composer 套件管理員安裝:

composer require laravel/octane

安裝 Octane 後,您可以執行 octane:install Artisan 指令,該指令將安裝 Octane 的組態檔案到您的應用程式中:

php artisan octane:install

伺服器先決條件

[!警告]
Laravel Octane 需要 PHP 8.1+

FrankenPHP

FrankenPHP 是一個使用 Go 語言編寫的 PHP 應用伺服器,支援現代 Web 功能,如提前提示、Brotli 和 Zstandard 壓縮。當您安裝 Octane 並選擇 FrankenPHP 作為您的伺服器時,Octane 將自動為您下載並安裝 FrankenPHP 二進制檔。

透過 Laravel Sail 使用 FrankenPHP

如果您計劃使用 Laravel Sail 開發應用程式,您應運行以下命令來安裝 Octane 和 FrankenPHP:

./vendor/bin/sail up

./vendor/bin/sail composer require laravel/octane

接下來,您應該使用 octane:install Artisan 命令來安裝 FrankenPHP 二進制檔:

./vendor/bin/sail artisan octane:install --server=frankenphp

最後,在您應用程式的 docker-compose.yml 檔案中的 laravel.test 服務定義中添加一個 SUPERVISOR_PHP_COMMAND 環境變數。這個環境變數將包含 Sail 將用來使用 Octane 來提供應用程式的命令,而不是使用 PHP 開發伺服器:

services:
  laravel.test:
    environment:
      SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=frankenphp --host=0.0.0.0 --admin-port=2019 --port='${APP_PORT:-80}'" # [tl! add]
      XDG_CONFIG_HOME:  /var/www/html/config # [tl! add]
      XDG_DATA_HOME:  /var/www/html/data # [tl! add]

要啟用 HTTPS、HTTP/2 和 HTTP/3,請應用這些修改:

services:
  laravel.test:
    ports:
        - '${APP_PORT:-80}:80'
        - '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
        - '443:443' # [tl! add]
        - '443:443/udp' # [tl! add]
    environment:
      SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --host=localhost --port=443 --admin-port=2019 --https" # [tl! add]
      XDG_CONFIG_HOME:  /var/www/html/config # [tl! add]
      XDG_DATA_HOME:  /var/www/html/data # [tl! add]

通常,您應該透過 https://localhost 訪問您的 FrankenPHP Sail 應用程式,因為使用 https://127.0.0.1 需要額外的配置,並且是不建議的

透過 Docker 使用 FrankenPHP

使用 FrankenPHP 的官方 Docker 映像可以提供更好的性能,並使用附帶靜態安裝的 FrankenPHP 不包含的其他擴充功能。此外,官方 Docker 映像支持在 FrankenPHP 原生不支持的平台上運行 FrankenPHP,例如 Windows。FrankenPHP 的官方 Docker 映像適用於本地開發和生產用途。

您可以使用以下 Dockerfile 作為將您的 FrankenPHP 強化 Laravel 應用程式容器化的起點:

FROM dunglas/frankenphp

RUN install-php-extensions \
    pcntl
    # Add other PHP extensions here...

COPY . /app

ENTRYPOINT ["php", "artisan", "octane:frankenphp"]

然後,在開發期間,您可以使用以下 Docker Compose 檔案來運行您的應用程式:

# compose.yaml
services:
  frankenphp:
    build:
      context: .
    entrypoint: php artisan octane:frankenphp --workers=1 --max-requests=1
    ports:
      - "8000:8000"
    volumes:
      - .:/app

如果 --log-level 選項被明確傳遞給 php artisan octane:start 命令,Octane 將使用 FrankenPHP 的原生記錄器,並且除非另有配置,將生成結構化 JSON 日誌。

您可以參考官方 FrankenPHP 文件以獲取有關在 Docker 中運行 FrankenPHP 的更多信息。

RoadRunner

RoadRunner 是由使用 Go 構建的 RoadRunner 二進制文件提供支持。第一次啟動基於 RoadRunner 的 Octane 伺服器時,Octane 將提供下載並安裝 RoadRunner 二進制文件的選項。

透過 Laravel Sail 使用 RoadRunner

如果您計劃使用 Laravel Sail 開發應用程式,您應運行以下命令來安裝 Octane 和 RoadRunner:

./vendor/bin/sail up

./vendor/bin/sail composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http

接下來,您應啟動一個 Sail shell 並使用 rr 執行檔檢索 RoadRunner 二進制文件的最新 Linux 版本:

./vendor/bin/sail shell

# Within the Sail shell...
./vendor/bin/rr get-binary

然後,在您應用程式的 docker-compose.yml 檔案中的 laravel.test 服務定義中添加一個 SUPERVISOR_PHP_COMMAND 環境變數。此環境變數將包含 Sail 將使用的命令,以使用 Octane 代替 PHP 開發伺服器來提供您的應用程式:

services:
  laravel.test:
    environment:
      SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=roadrunner --host=0.0.0.0 --rpc-port=6001 --port='${APP_PORT:-80}'" # [tl! add]

最後,確保 rr 執行檔具有執行權限並構建您的 Sail 映像:

chmod +x ./rr

./vendor/bin/sail build --no-cache

Swoole

如果您計劃使用 Swoole 應用程式伺服器來提供您的 Laravel Octane 應用程式,您必須安裝 Swoole PHP 擴充功能。通常,這可以通過 PECL 完成:

pecl install swoole

開啟 Swoole

如果您想要使用開源 Swoole 應用伺服器來提供 Laravel Octane 應用程式,您必須安裝開源 Swoole PHP 擴展。通常,這可以通過 PECL 完成:

pecl install openswoole

使用 Laravel Octane 與開源 Swoole 提供了與 Swoole 相同的功能,例如並行任務、時脈和間隔。

透過 Laravel Sail 使用 Swoole

[!WARNING]
在透過 Sail 提供 Octane 應用程式之前,請確保您已經安裝了最新版本的 Laravel Sail,並在應用程式的根目錄中執行 ./vendor/bin/sail build --no-cache

或者,您可以使用 Laravel Sail 開發基於 Swoole 的 Octane 應用程式,這是 Laravel 的官方基於 Docker 的開發環境。 Laravel Sail 預設包含 Swoole 擴展。但是,您仍需要調整 Sail 使用的 docker-compose.yml 檔案。

要開始,請在應用程式的 docker-compose.yml 檔案中的 laravel.test 服務定義中添加一個 SUPERVISOR_PHP_COMMAND 環境變數。這個環境變數將包含 Sail 用來使用 Octane 來提供您的應用程式的命令,而不是使用 PHP 開發伺服器:

services:
  laravel.test:
    environment:
      SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=swoole --host=0.0.0.0 --port='${APP_PORT:-80}'" # [tl! add]

最後,建立您的 Sail 映像:

./vendor/bin/sail build --no-cache

Swoole 配置

如果需要,Swoole 支援一些額外的配置選項,您可以將其添加到您的 octane 配置檔案中。由於這些選項很少需要修改,因此它們不包含在默認配置檔案中:

'swoole' => [
    'options' => [
        'log_file' => storage_path('logs/swoole_http.log'),
        'package_max_length' => 10 * 1024 * 1024,
    ],
],

提供您的應用程式

Octane 伺服器可以通過 octane:start Artisan 命令啟動。默認情況下,此命令將使用您的應用程式 octane 配置檔案中的 server 選項指定的伺服器:

php artisan octane:start

預設情況下,Octane 會在 8000 端口啟動伺服器,因此您可以通過 http://localhost:8000 在網頁瀏覽器中訪問應用程式。

透過 HTTPS 提供您的應用程式

預設情況下,透過 Octane 運行的應用程式會生成以 http:// 為前綴的連結。OCTANE_HTTPS 環境變數可在您的應用程式的 config/octane.php 配置檔案中使用,當透過 HTTPS 提供您的應用程式時,可以將此配置值設置為 true。當此配置值設置為 true 時,Octane 將指示 Laravel 將所有生成的連結以 https:// 為前綴:

'https' => env('OCTANE_HTTPS', false),

透過 Nginx 提供您的應用程式

[!NOTE]
如果您尚未準備好管理自己的伺服器配置或不熟悉配置運行強大的 Laravel Octane 應用程式所需的各種服務,請查看 Laravel Cloud,該服務提供完全管理的 Laravel Octane 支援。

在正式環境中,您應該將 Octane 應用程式放在傳統的網頁伺服器後面,例如 Nginx 或 Apache。這樣做將允許網頁伺服器提供您的靜態資源,如圖像和樣式表,並管理您的 SSL 憑證終止。

在下面的 Nginx 配置範例中,Nginx 將提供站點的靜態資源並將請求代理到運行在 8000 端口上的 Octane 伺服器:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    listen [::]:80;
    server_name domain.com;
    server_tokens off;
    root /home/forge/domain.com/public;

    index index.php;

    charset utf-8;

    location /index.php {
        try_files /not_exists @octane;
    }

    location / {
        try_files $uri $uri/ @octane;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/domain.com-error.log error;

    error_page 404 /index.php;

    location @octane {
        set $suffix "";

        if ($uri = /index.php) {
            set $suffix ?$query_string;
        }

        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header Scheme $scheme;
        proxy_set_header SERVER_PORT $server_port;
        proxy_set_header REMOTE_ADDR $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_pass http://127.0.0.1:8000$suffix;
    }
}

監視檔案變更

由於您的應用程式在 Octane 伺服器啟動時只加載一次到記憶體中,因此對應用程式檔案的任何更改在您重新整理瀏覽器時不會反映出來。例如,添加到您的 routes/web.php 檔案的路由定義在重新啟動伺服器之前不會反映出來。為了方便起見,您可以使用 --watch 標誌指示 Octane 在應用程式中的任何檔案更改時自動重新啟動伺服器:

php artisan octane:start --watch

在使用此功能之前,您應確保 Node 已安裝在您的本地開發環境中。此外,您應在您的專案中安裝 Chokidar 檔案監視程式庫:

npm install --save-dev chokidar

您可以使用應用程式的 config/octane.php 組態檔案中的 watch 配置選項來配置應該被監視的目錄和檔案。

指定工作人員數量

預設情況下,Octane 將為您的機器提供的每個 CPU 核心啟動一個應用程式請求工作人員。這些工作人員將用於處理進入應用程式的傳入 HTTP 請求。您可以在調用 octane:start 命令時使用 --workers 選項手動指定要啟動多少工作人員:

php artisan octane:start --workers=4

如果您正在使用 Swoole 應用程式伺服器,您還可以指定您希望啟動多少 "任務工作人員"

php artisan octane:start --workers=4 --task-workers=6

指定最大請求次數

為了幫助防止零散的記憶體洩漏,Octane 在處理 500 個請求後優雅地重新啟動任何工作人員。要調整此數字,您可以使用 --max-requests 選項:

php artisan octane:start --max-requests=250

重新載入工作人員

您可以使用 octane:reload 命令優雅地重新啟動 Octane 伺服器的應用程式工作人員。通常應在部署後執行此操作,以便您的新部署的程式碼被載入記憶體並用於處理後續請求:

php artisan octane:reload

停止伺服器

您可以使用 octane:stop Artisan 命令停止 Octane 伺服器:

php artisan octane:stop

檢查伺服器狀態

您可以使用 octane:status Artisan 命令來檢查 Octane 伺服器的當前狀態:

php artisan octane:status

依賴注入與 Octane

由於 Octane 在啟動應用程式後將其保留在記憶體中,並在處理請求時保持運行,因此在構建應用程式時應考慮一些注意事項。例如,當請求工作程序初始啟動時,將僅執行應用程式服務提供者的 registerboot 方法一次。在後續請求中,將重複使用相同的應用程式實例。

基於此,當將應用程式服務容器或請求注入到任何物件的建構子中時,您應特別小心。這樣一來,在後續請求中,該物件可能會使用容器或請求的舊版本。

Octane 將自動處理在請求之間重置任何第一方框架狀態。但是,Octane 不總是知道如何重置您的應用程式創建的全域狀態。因此,您應該知道如何以符合 Octane 的方式構建應用程式。下面,我們將討論在使用 Octane 時可能引起問題的最常見情況。

容器注入

一般來說,您應該避免將應用程式服務容器或 HTTP 請求實例注入到其他物件的建構子中。例如,以下綁定將整個應用程式服務容器注入為單例綁定到物件中:

use App\Service;
use Illuminate\Contracts\Foundation\Application;

/**
 * Register any application services.
 */
public function register(): void
{
    $this->app->singleton(Service::class, function (Application $app) {
        return new Service($app);
    });
}

在此示例中,如果在應用程式啟動過程中解析 Service 實例,容器將被注入到服務中,並且在後續請求中,Service 實例將保留相同的容器。這可能對您的特定應用程式並非問題;但是,這可能導致容器意外地缺少後來在啟動週期中或後續請求中添加的綁定。

作為解決方法,您可以停止將綁定註冊為單例,或者您可以將一個容器解析器閉包注入到服務中,該閉包始終解析當前容器實例:

use App\Service;
use Illuminate\Container\Container;
use Illuminate\Contracts\Foundation\Application;

$this->app->bind(Service::class, function (Application $app) {
    return new Service($app);
});

$this->app->singleton(Service::class, function () {
    return new Service(fn () => Container::getInstance());
});

全域 app 輔助函式和 Container::getInstance() 方法將始終返回應用程式容器的最新版本。

請求注入

一般來說,您應該避免將應用程式服務容器或 HTTP 請求實例注入到其他物件的建構子中。例如,以下綁定將整個請求實例注入為單例綁定到物件中:

use App\Service;
use Illuminate\Contracts\Foundation\Application;

/**
 * Register any application services.
 */
public function register(): void
{
    $this->app->singleton(Service::class, function (Application $app) {
        return new Service($app['request']);
    });
}

在這個例子中,如果在應用程式啟動過程中解析 Service 實例,HTTP 請求將被注入到服務中,並且相同的請求將由 Service 實例保留在後續請求中。因此,所有標頭、輸入和查詢字串數據將是不正確的,以及所有其他請求數據。

作為解決方法,您可以停止將綁定註冊為單例,或者您可以將一個請求解析器閉包注入到服務中,該閉包始終解析當前請求實例。或者,最推薦的方法是在運行時將物件需要的特定請求信息傳遞給物件的其中一個方法:

use App\Service;
use Illuminate\Contracts\Foundation\Application;

$this->app->bind(Service::class, function (Application $app) {
    return new Service($app['request']);
});

$this->app->singleton(Service::class, function (Application $app) {
    return new Service(fn () => $app['request']);
});

// Or...

$service->method($request->input('name'));

全域 request 輔助函式將始終返回應用程式當前處理的請求,因此在應用程式中使用它是安全的。

[!WARNING]
在控制器方法和路由閉包上對 Illuminate\Http\Request 實例進行型別提示是可以接受的。

組態存儲庫注入

一般來說,您應該避免將組態存儲庫實例注入到其他物件的建構子中。例如,以下綁定將組態存儲庫注入為單例綁定到物件中:

use App\Service;
use Illuminate\Contracts\Foundation\Application;

/**
 * Register any application services.
 */
public function register(): void
{
    $this->app->singleton(Service::class, function (Application $app) {
        return new Service($app->make('config'));
    });
}

在這個例子中,如果在請求之間組態值發生變化,該服務將無法訪問新值,因為它依賴於原始存儲庫實例。

作為一個解決方法,您可以停止將綁定註冊為單例,或者您可以將配置存儲庫解析器閉包注入到類中:

use App\Service;
use Illuminate\Container\Container;
use Illuminate\Contracts\Foundation\Application;

$this->app->bind(Service::class, function (Application $app) {
    return new Service($app->make('config'));
});

$this->app->singleton(Service::class, function () {
    return new Service(fn () => Container::getInstance()->make('config'));
});

全局的 config 將始終返回配置存儲庫的最新版本,因此在應用程序中使用它是安全的。

管理記憶體洩漏

請記住,Octane 在請求之間保留應用程序在記憶體中;因此,將數據添加到靜態維護的陣列中將導致記憶體洩漏。例如,以下控制器由於每次對應用程序的請求都會繼續向靜態 $data 陣列添加數據,因此存在記憶體洩漏:

use App\Service;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

/**
 * Handle an incoming request.
 */
public function index(Request $request): array
{
    Service::$data[] = Str::random(10);

    return [
        // ...
    ];
}

在構建應用程序時,您應特別注意避免創建這些類型的記憶體洩漏。建議您在本地開發期間監控應用程序的記憶體使用情況,以確保您不會將新的記憶體洩漏引入應用程序中。

並行任務

[!WARNING]
此功能需要 Swoole

在使用 Swoole 時,您可以通過輕量級後台任務並行執行操作。您可以使用 Octane 的 concurrently 方法來實現這一點。您可以將此方法與 PHP 陣列解構結合使用,以檢索每個操作的結果:

use App\Models\User;
use App\Models\Server;
use Laravel\Octane\Facades\Octane;

[$users, $servers] = Octane::concurrently([
    fn () => User::all(),
    fn () => Server::all(),
]);

Octane 處理的並行任務利用 Swoole 的 "任務工作者",並在與傳入請求完全不同的進程中執行。處理並行任務的工作者數量由 octane:start 命令上的 --task-workers 指令確定:

php artisan octane:start --workers=4 --task-workers=6

在調用 concurrently 方法時,由於 Swoole 任務系統施加的限制,您不應提供超過 1024 個任務。

Ticks 和 Intervals

[!WARNING]
此功能需要 Swoole

在使用 Swoole 時,您可以註冊每隔指定秒數執行的 "tick" 操作。您可以通過 tick 方法註冊 "tick" 回調函式。提供給 tick 方法的第一個引數應該是表示計時器名稱的字符串。第二個引數應該是在指定間隔時調用的可調用對象。

在這個範例中,我們將註冊一個閉包,每隔 10 秒調用一次。通常,tick 方法應該在應用程式的其中一個服務提供者的 boot 方法中調用:

Octane::tick('simple-ticker', fn () => ray('Ticking...'))
    ->seconds(10);

使用 immediate 方法,您可以指示 Octane 在 Octane 伺服器初始啟動時立即調用 tick 回調,並在之後每 N 秒調用一次:

Octane::tick('simple-ticker', fn () => ray('Ticking...'))
    ->seconds(10)
    ->immediate();

Octane 快取

[!WARNING]
此功能需要 Swoole

在使用 Swoole 時,您可以利用 Octane 快取驅動程式,提供每秒高達 200 萬次的讀寫速度。因此,對於需要從其快取層獲取極端讀取/寫入速度的應用程式來說,這個快取驅動程式是一個優秀的選擇。

此快取驅動程式由 Swoole tables 提供支援。快取中存儲的所有資料對伺服器上的所有工作進程都是可用的。但是,當伺服器重新啟動時,快取的資料將被清除:

Cache::store('octane')->put('framework', 'Laravel', 30);

[!NOTE]
Octane 快取中允許的最大條目數量可以在您應用程式的 octane 組態檔中定義。

快取間隔

除了 Laravel 快取系統提供的典型方法外,Octane 快取驅動程式還提供基於間隔的快取。這些快取會在指定的間隔自動刷新,應該在應用程式的其中一個服務提供者的 boot 方法中註冊。例如,以下快取將每五秒刷新一次:

use Illuminate\Support\Str;

Cache::store('octane')->interval('random', function () {
    return Str::random(10);
}, seconds: 5);

表格

[!WARNING]
此功能需要 Swoole

在使用 Swoole 時,您可以定義並與自己的任意 Swoole tables 進行交互。Swoole tables 提供極高的性能吞吐量,這些表中的資料可以被伺服器上的所有工作進程訪問。但是,當伺服器重新啟動時,其中的資料將會丟失。

表格應該在應用程式的 octane 組態檔案的 tables 組態陣列中定義。已為您配置了一個允許最多 1000 列的範例表格。可以通過在列類型後指定列大小來配置字串列的最大大小,如下所示:

'tables' => [
    'example:1000' => [
        'name' => 'string:1000',
        'votes' => 'int',
    ],
],

要訪問表格,您可以使用 Octane::table 方法:

use Laravel\Octane\Facades\Octane;

Octane::table('example')->set('uuid', [
    'name' => 'Nuno Maduro',
    'votes' => 1000,
]);

return Octane::table('example')->get('uuid');

[!WARNING]
Swoole 表格支援的列類型有:stringintfloat