跳至內容

Laravel Octane

簡介

Laravel Octane 透過使用高效能應用程式伺服器(包括 FrankenPHPOpen SwooleSwooleRoadRunner)來提供應用程式,從而大幅提升應用程式效能。Octane 只會啟動一次應用程式,將其保存在記憶體中,然後以超音速速度將請求饋送給它。

安裝

可以使用 Composer 套件管理器安裝 Octane

composer require laravel/octane

安裝 Octane 後,您可以執行 octane:install Artisan 命令,這會將 Octane 的設定檔安裝到您的應用程式中

php artisan octane:install

伺服器先決條件

exclamation

Laravel Octane 需要 PHP 8.1+

FrankenPHP

FrankenPHP 是一個用 Go 語言撰寫的 PHP 應用程式伺服器,它支援早期提示、Brotli 和 Zstandard 壓縮等現代 Web 功能。當您安裝 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

最後,將 SUPERVISOR_PHP_COMMAND 環境變數新增至應用程式 docker-compose.yml 檔案中 laravel.test 服務定義。此環境變數將包含 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}'"
XDG_CONFIG_HOME: /var/www/html/config
XDG_DATA_HOME: /var/www/html/data

若要啟用 HTTPS、HTTP/2 和 HTTP/3,請改為套用這些修改

services:
laravel.test:
ports:
- '${APP_PORT:-80}:80'
- '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
- '443:443'
- '443:443/udp'
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"
XDG_CONFIG_HOME: /var/www/html/config
XDG_DATA_HOME: /var/www/html/data

一般來說,您應該透過 https://127.0.0.1 存取您的 FrankenPHP Sail 應用程式,因為使用 https://127.0.0.1 需要額外的設定,且不建議這樣做,詳情請參閱 已知問題

透過 Docker 的 FrankenPHP

使用 FrankenPHP 的官方 Docker 映像可以提供更高的效能,並使用 FrankenPHP 靜態安裝中未包含的其他擴充功能。此外,官方 Docker 映像支援在它不原生支援的平台上執行 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 記錄。

如需在 Docker 上執行 FrankenPHP 的詳細資訊,您可以參考 官方 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

然後,將 SUPERVISOR_PHP_COMMAND 環境變數新增至應用程式 docker-compose.yml 檔案中 laravel.test 服務定義。此環境變數將包含 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}'"

最後,請確保 rr 二進位檔案是可執行的,並建置您的 Sail 映像

chmod +x ./rr
 
./vendor/bin/sail build --no-cache

Swoole

如果您計畫使用 Swoole 應用程式伺服器來提供 Laravel Octane 應用程式,您必須安裝 Swoole PHP 擴充功能。一般來說,這可以透過 PECL 完成

pecl install swoole

Open Swoole

如果您想使用 Open Swoole 應用程式伺服器來提供 Laravel Octane 應用程式,您必須安裝 Open Swoole PHP 擴充功能。一般來說,這可以透過 PECL 完成

pecl install openswoole

使用 Laravel Octane 與 Open Swoole 會提供與 Swoole 相同的功能,例如並行任務、Ticks 和間隔。

透過 Laravel Sail 的 Swoole

exclamation

在透過 Sail 提供 Octane 應用程式之前,請確保您擁有最新版本的 Laravel Sail,並在應用程式的根目錄中執行 ./vendor/bin/sail build --no-cache

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

首先,將 SUPERVISOR_PHP_COMMAND 環境變數新增至應用程式 docker-compose.yml 檔案中 laravel.test 服務定義。此環境變數將包含 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}'"

最後,建置您的 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:start Artisan 命令啟動 Octane 伺服器。預設情況下,此命令會使用應用程式 octane 組態檔案的 server 組態選項所指定的伺服器

php artisan octane:start

預設情況下,Octane 會在 8000 連接埠上啟動伺服器,因此您可以在 Web 瀏覽器中透過 https://127.0.0.1:8000 存取您的應用程式。

透過 HTTPS 提供您的應用程式

預設情況下,透過 Octane 執行的應用程式會產生以 http:// 為首碼的連結。當透過 HTTPS 提供應用程式時,您可以使用應用程式 config/octane.php 設定檔案中的 OCTANE_HTTPS 環境變數,並將其設定為 true。當此組態值設定為 true 時,Octane 會指示 Laravel 將所有產生的連結加上 https:// 的首碼

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

透過 Nginx 提供您的應用程式

lightbulb

如果您還沒有準備好管理自己的伺服器設定,或者不熟悉設定執行健全的 Laravel Octane 應用程式所需的所有各種服務,請查看 Laravel Forge

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

在下面的 Nginx 組態範例中,Nginx 將提供網站的靜態資源,並將請求 Proxy 給在 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 設定選項,來設定應該監看的目錄和檔案。

指定 Worker 數量

預設情況下,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

重新載入 Worker

您可以使用 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 輔助函式將始終傳回應用程式目前正在處理的請求,因此在您的應用程式中使用是安全的。

exclamation

在您的控制器方法和路由閉包中類型提示 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 [
// ...
];
}

在建置應用程式時,您應該特別注意避免建立這些類型的記憶體洩漏。建議您在本地開發期間監視應用程式的記憶體使用情況,以確保您沒有將新的記憶體洩漏引入到應用程式中。

並行任務

exclamation

此功能需要 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 和間隔

exclamation

此功能需要 Swoole

使用 Swoole 時,您可以註冊「刻度」操作,這些操作將在每個指定的秒數執行。您可以透過 tick 方法註冊「刻度」回呼。提供給 tick 方法的第一個引數應該是一個字串,表示刻度器的名稱。第二個引數應該是一個可呼叫的物件,將在指定的間隔中被呼叫。

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

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

使用 immediate 方法,您可以指示 Octane 在 Octane 伺服器首次啟動時立即呼叫刻度回呼,然後每 N 秒呼叫一次。

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

Octane 快取

exclamation

此功能需要 Swoole

使用 Swoole 時,您可以利用 Octane 快取驅動程式,該驅動程式提供高達每秒 200 萬次操作的讀取和寫入速度。因此,此快取驅動程式非常適合需要從其快取層獲得極高讀取/寫入速度的應用程式。

此快取驅動程式由 Swoole 表格提供支援。儲存在快取中的所有資料都可供伺服器上的所有工作程序使用。但是,快取的資料將在伺服器重新啟動時清除。

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

Octane 快取中允許的最大條目數可以在您的應用程式的 octane 設定檔中定義。

快取間隔

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

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

表格

exclamation

此功能需要 Swoole

使用 Swoole 時,您可以定義和與您自己的任意 Swoole 表格互動。Swoole 表格提供極高的效能吞吐量,並且伺服器上的所有工作程序都可以存取這些表格中的資料。但是,當伺服器重新啟動時,表格中的資料將會遺失。

表格應在您應用程式的 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');
exclamation

Swoole 表格支援的欄類型為:stringintfloat