Laravel Octane
簡介
Laravel Octane 透過使用高效能應用程式伺服器來執行您的應用程式,大幅提升您應用程式的效能,這些伺服器包含 FrankenPHP、Open Swoole、Swoole 與 RoadRunner。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 應用程式伺服器,支援 early hints、Brotli 與 Zstandard 壓縮等現代網頁功能。當您安裝 Octane 並選擇 FrankenPHP 作為伺服器時,Octane 將會自動為您下載並安裝 FrankenPHP 二進位檔。
FrankenPHP via Laravel Sail
如果您打算使用 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
需要額外的設定,並且 不建議。
FrankenPHP via Docker
使用 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 日誌。
您可以參考 官方 FrankenPHP 文件 以取得更多關於使用 Docker 執行 FrankenPHP 的資訊。
RoadRunner
RoadRunner 由以 Go 語言建置的 RoadRunner 二進位檔驅動。當您第一次啟動基於 RoadRunner 的 Octane 伺服器時,Octane 將會詢問您是否要下載並安裝 RoadRunner 二進位檔。
RoadRunner via Laravel Sail
如果您打算使用 Laravel Sail 開發您的應用程式,您應該執行以下指令來安裝 Octane 和 RoadRunner:
./vendor/bin/sail up
./vendor/bin/sail composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http
接著,您應該啟動一個 Sail shell,並使用 rr
可執行檔來取得最新基於 Linux 的 RoadRunner 二進位檔:
./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
Open Swoole
如果您想使用 Open Swoole 應用程式伺服器來執行您的 Laravel Octane 應用程式,您必須安裝 Open Swoole PHP 擴充功能。通常,這可以透過 PECL 完成:
pecl install openswoole
將 Laravel Octane 與 Open Swoole 搭配使用,可提供與 Swoole 相同的功能,例如並行任務、定時器和間隔。
Swoole via Laravel Sail
⚠️ 警告
在透過 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 Configuration
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://
為前綴的連結。當透過 HTTPS 執行您的應用程式時,可以將應用程式 config/octane.php
設定檔中使用的 OCTANE_HTTPS
環境變數設定為 true
。當此設定值設為 true
時,Octane 將會指示 Laravel 為所有產生的連結加上 https://
的前綴:
'https' => env('OCTANE_HTTPS', false),
透過 Nginx 執行您的應用程式
📌 備註
如果您還沒有準備好管理自己的伺服器設定,或者不熟悉設定運行一個強健 (robust) 的 Laravel Octane 應用程式所需的所有服務,請查看 Laravel Forge。
在正式環境中,您應該將您的 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
設定選項來配置應該被監控的目錄和檔案。
指定 Worker 數量
預設情況下,Octane 將為您的機器每個 CPU 核心啟動一個應用程式請求 Worker。這些 Worker 將用於處理傳入的 HTTP 請求。您可以在調用 octane:start
指令時使用 --workers
選項來手動指定您希望啟動多少個 Worker:
php artisan octane:start --workers=4
如果您正在使用 Swoole 應用程式伺服器,您也可以指定您希望啟動多少個「任務 Worker」:
php artisan octane:start --workers=4 --task-workers=6
指定最大請求數量
為了幫助防止零星的記憶體洩漏,Octane 會在任何 Worker 處理 500 個請求後優雅地重新啟動。要調整此數量,您可以使用 --max-requests
選項:
php artisan octane:start --max-requests=250
重新載入 Worker
您可以使用 octane:reload
指令優雅地重新啟動 Octane 伺服器的應用程式 Worker。通常,這應該在部署後進行,以便您新部署的程式碼會載入到記憶體中,並用於處理後續的請求:
php artisan octane:reload
停止伺服器
您可以使用 octane:stop
Artisan 指令停止 Octane 伺服器:
php artisan octane:stop
檢查伺服器狀態
您可以使用 octane:status
Artisan 指令檢查 Octane 伺服器的當前狀態:
php artisan octane:status
依賴注入與 Octane
由於 Octane 會在啟動應用程式後將其保留在記憶體中以處理請求,因此在建構應用程式時,有一些注意事項需要考慮。例如,您的應用程式服務提供者的 register
和 boot
方法只會在請求 worker 初次啟動時執行一次。在後續請求中,會重複使用相同的應用程式實例。
有鑑於此,當將應用程式服務容器或請求注入到任何物件的建構子時,您應特別小心。這樣做的話,該物件在後續請求中可能會持有容器或請求的過時版本。
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
輔助函式將始終返回應用程式當前處理的請求,因此在您的應用程式中可以安全使用。
⚠️ 警告
在控制器方法和路由閉包中對 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 [
// ...
];
}
在建構應用程式時,您應特別注意避免造成這類記憶體洩漏。建議您在本地開發環境中監控應用程式的記憶體使用情況,以確保您沒有引入新的記憶體洩漏到您的應用程式中。
並行任務
⚠️ 警告
此功能需要 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 的「任務 worker」,並在與傳入請求完全不同的程序中執行。用於處理並行任務的可用 worker 數量由 octane:start
命令上的 --task-workers
指令決定:
php artisan octane:start --workers=4 --task-workers=6
呼叫 concurrently
方法時,由於 Swoole 任務系統所施加的限制,您提供的任務不應超過 1024 個。
定時器與間隔
⚠️ 警告
此功能需要 Swoole。
當使用 Swoole 時,您可以註冊「定時器 (tick)」操作,這些操作將每隔指定秒數執行一次。您可以使用 tick
方法來註冊「定時器 (tick)」回呼。提供給 tick
方法的第一個參數應該是一個字串,代表定時器的名稱。第二個參數應該是一個可呼叫 (callable) 物件,它將會以指定的間隔被呼叫。
在此範例中,我們將註冊一個閉包,每 10 秒被呼叫一次。通常,tick
方法應該在您應用程式的服務提供者 (service provider) 中的 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 快取
⚠️ 警告
此功能需要 Swoole。
當使用 Swoole 時,您可以利用 Octane 快取驅動程式,它提供高達每秒 2 百萬次操作的讀寫速度。因此,對於需要極致讀寫速度的快取層應用程式來說,此快取驅動程式是絕佳的選擇。
此快取驅動程式由 Swoole 資料表提供支援。所有儲存在快取中的資料都可供伺服器上的所有 worker 存取。然而,當伺服器重新啟動時,快取資料將會被清除:
Cache::store('octane')->put('framework', 'Laravel', 30);
📌 備註
Octane 快取允許的最大條目數可以在您應用程式的 octane
設定檔中定義。
快取間隔
除了 Laravel 快取系統提供的典型方法之外,Octane 快取驅動程式還具備基於間隔的快取。這些快取會以指定的間隔自動刷新,並且應該在您應用程式的服務提供者 (service provider) 中的 boot
方法內註冊。例如,以下快取將每五秒刷新一次:
use Illuminate\Support\Str;
Cache::store('octane')->interval('random', function () {
return Str::random(10);
}, seconds: 5);
資料表
⚠️ 警告
此功能需要 Swoole。
當使用 Swoole 時,您可以定義並與您自己的任意 Swoole 資料表互動。Swoole 資料表提供極致的效能吞吐量,並且這些資料表中的資料可供伺服器上的所有 worker 存取。然而,當伺服器重新啟動時,其中的資料將會遺失。
資料表應定義在您應用程式的 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');
⚠️ 警告
Swoole 資料表支援的欄位類型為:string
、int
和 float
。