簡介 (Introduction)
Laravel Octane 透過使用高效能的應用程式伺服器來提供您的應用程式,從而大幅提升應用程式的效能,這些伺服器包括 FrankenPHP、Open Swoole、Swoole 和 RoadRunner。Octane 會啟動您的應用程式一次,將其保留在記憶體中,然後以超音速的速度處理請求。
安裝 (Installation)
Octane 可以透過 Composer 套件管理器安裝:
composer require laravel/octane
安裝 Octane 後,您可以執行 octane:install Artisan 指令,這將會把 Octane 的設定檔安裝到您的應用程式中:
php artisan octane:install
伺服器先決條件 (Server Prerequisites)
FrankenPHP
FrankenPHP 是一個使用 Go 編寫的 PHP 應用程式伺服器,支援現代網頁功能,如 early hints、Brotli 和 Zstandard 壓縮。當您安裝 Octane 並選擇 FrankenPHP 作為您的伺服器時,Octane 會自動為您下載並安裝 FrankenPHP 二進位檔。
透過 Laravel Sail 使用 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 檔案中,將 SUPERVISOR_PHP_COMMAND 環境變數新增到 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}'" # [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 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 的更多資訊。
自訂 Caddyfile 設定 (Frankenphp Caddyfile)
使用 FrankenPHP 時,您可以在啟動 Octane 時使用 --caddyfile 選項指定自訂的 Caddyfile:
php artisan octane:start --server=frankenphp --caddyfile=/path/to/your/Caddyfile
這允許您自訂 FrankenPHP 的設定,超越預設設定,例如新增自訂中介軟體、設定進階路由或設定自訂指令。您可以參考 Caddy 官方文件 以獲取有關 Caddyfile 語法和設定選項的更多資訊。
RoadRunner
RoadRunner 由使用 Go 建構的 RoadRunner 二進位檔驅動。當您第一次啟動基於 RoadRunner 的 Octane 伺服器時,Octane 會提供下載並為您安裝 RoadRunner 二進位檔。
透過 Laravel Sail 使用 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 檔案中,將 SUPERVISOR_PHP_COMMAND 環境變數新增到 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}'" # [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 相同的功能,例如並行任務、ticks 和 intervals。
透過 Laravel Sail 使用 Swoole (Swoole Via Laravel Sail)
[!WARNING] 在透過 Sail 提供 Octane 應用程式服務之前,請確保您擁有最新版本的 Laravel Sail,並在應用程式的根目錄中執行
./vendor/bin/sail build --no-cache。
或者,您可以使用 Laravel Sail(Laravel 官方的基於 Docker 的開發環境)來開發基於 Swoole 的 Octane 應用程式。Laravel Sail 預設包含 Swoole 擴充功能。但是,您仍然需要調整 Sail 使用的 docker-compose.yml 檔案。
首先,在您應用程式的 docker-compose.yml 檔案中,將 SUPERVISOR_PHP_COMMAND 環境變數新增到 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}'" # [tl! add]
最後,建構您的 Sail 映像:
./vendor/bin/sail build --no-cache
Swoole 設定 (Swoole Configuration)
Swoole 支援一些額外的設定選項,如果需要,您可以將它們新增到您的 octane 設定檔中。因為它們很少需要修改,所以這些選項不包含在預設設定檔中:
'swoole' => [
'options' => [
'log_file' => storage_path('logs/swoole_http.log'),
'package_max_length' => 10 * 1024 * 1024,
],
],
提供應用程式服務 (Serving Your Application)
Octane 伺服器可以透過 octane:start Artisan 指令啟動。預設情況下,此指令將使用應用程式 octane 設定檔中 server 設定選項指定的伺服器:
php artisan octane:start
預設情況下,Octane 會在連接埠 8000 上啟動伺服器,因此您可以透過 http://localhost:8000 在網頁瀏覽器中存取您的應用程式。
在正式環境中保持 Octane 執行 (Keeping Octane Running In Production)
如果您要將 Octane 應用程式部署到正式環境,您應該使用程序監視器(如 Supervisor)來確保 Octane 伺服器保持執行。Octane 的範例 Supervisor 設定檔可能如下所示:
[program:octane]
process_name=%(program_name)s_%(process_num)02d
command=php /home/forge/example.com/artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8000
autostart=true
autorestart=true
user=forge
redirect_stderr=true
stdout_logfile=/home/forge/example.com/storage/logs/octane.log
stopwaitsecs=3600
透過 HTTPS 提供應用程式服務 (Serving Your Application via HTTPS)
預設情況下,透過 Octane 執行的應用程式會產生以 http:// 為前綴的連結。當透過 HTTPS 提供應用程式服務時,可以將應用程式 config/octane.php 設定檔中使用的 OCTANE_HTTPS 環境變數設定為 true。當此設定值設定為 true 時,Octane 會指示 Laravel 為所有產生的連結加上 https:// 前綴:
'https' => env('OCTANE_HTTPS', false),
透過 Nginx 提供應用程式服務 (Serving Your Application via Nginx)
[!NOTE] 如果您還沒有準備好管理自己的伺服器設定,或者不熟悉設定執行強大的 Laravel Octane 應用程式所需的各種服務,請查看 Laravel Cloud,它提供完全託管的 Laravel Octane 支援。
在正式環境中,您應該在傳統的網頁伺服器(如 Nginx 或 Apache)後面提供 Octane 應用程式服務。這樣做將允許網頁伺服器提供靜態資源(如圖片和樣式表),以及管理您的 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;
}
}
監視檔案變更 (Watching for File Changes)
由於您的應用程式在 Octane 伺服器啟動時會被載入到記憶體中一次,因此當您刷新瀏覽器時,對應用程式檔案的任何更改都不會被反映。例如,新增到 routes/web.php 檔案的路由定義在伺服器重新啟動之前不會被反映。為了方便起見,您可以使用 --watch 標誌來指示 Octane 在應用程式內的任何檔案更改時自動重新啟動伺服器:
php artisan octane:start --watch
在使用此功能之前,您應該確保在您的本地開發環境中安裝了 Node。此外,您應該在您的專案中安裝 Chokidar 檔案監視函式庫:
npm install --save-dev chokidar
您可以使用應用程式 config/octane.php 設定檔中的 watch 設定選項來設定應該監視的目錄和檔案。
指定 Worker 數量 (Specifying the Worker Count)
預設情況下,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
指定最大請求數 (Specifying the Max Request Count)
為了幫助防止離羣的記憶體洩漏,Octane 在 worker 處理了 500 個請求後會很好地重新啟動它。要調整此數字,您可以使用 --max-requests 選項:
php artisan octane:start --max-requests=250
指定最大執行時間 (Specifying the Max Execution Time)
預設情況下,Laravel Octane 透過應用程式 config/octane.php 設定檔中的 max_execution_time 選項為進入的請求設定最大執行時間為 30 秒:
'max_execution_time' => 30,
此設定定義了進入的請求在被終止之前允許執行的最大秒數。將此值設定為 0 將完全禁用執行時間限制。此設定選項對於處理長時間執行的請求的應用程式特別有用,例如檔案上傳、資料處理或對外部服務的 API 呼叫。
[!WARNING] 當您修改
max_execution_time設定時,您必須重新啟動 Octane 伺服器以使更改生效。
重新載入 Workers (Reloading the Workers)
您可以使用 octane:reload 指令很好地重新啟動 Octane 伺服器的應用程式 worker。通常,這應該在部署後執行,以便您新部署的程式碼被載入到記憶體中,並用於提供後續請求服務:
php artisan octane:reload
停止伺服器 (Stopping the Server)
您可以使用 octane:stop Artisan 指令停止 Octane 伺服器:
php artisan octane:stop
檢查伺服器狀態 (Checking The Server Status)
您可以使用 octane:status Artisan 指令檢查 Octane 伺服器的當前狀態:
php artisan octane:status
依賴注入與 Octane (Dependency Injection and Octane)
由於 Octane 會啟動您的應用程式一次,並在提供請求服務時將其保留在記憶體中,因此在建構應用程式時有一些需要考慮的注意事項。例如,應用程式服務提供者的 register 和 boot 方法只會在請求 worker 最初啟動時執行一次。在後續的請求中,將重複使用相同的應用程式實例。
有鑑於此,當將應用程式服務容器或請求注入到任何物件的建構函式中時,您應該特別小心。透過這樣做,該物件可能在後續的請求中擁有過時版本的容器或請求。
Octane 會自動處理在請求之間重設任何第一方框架狀態。然而,Octane 並不總是知道如何重設您的應用程式建立的全域狀態。因此,您應該知道如何以對 Octane 友好的方式建構應用程式。下面,我們將討論在使用 Octane 時可能導致問題的最常見情況。
容器注入 (Container Injection)
一般來說,您應該避免將應用程式服務容器或 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() 方法將始終返回最新版本的應用程式容器。
請求注入 (Request Injection)
一般來說,您應該避免將應用程式服務容器或 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實例是可以接受的。
設定 Repository 注入 (Configuration Repository Injection)
一般來說,您應該避免將設定儲存庫實例注入到其他物件的建構函式中。例如,以下繫定將設定儲存庫注入到作為單例繫定的物件中:
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 將始終返回最新版本的設定儲存庫,因此在您的應用程式中使用是安全的。
管理記憶體洩漏 (Managing Memory Leaks)
請記住,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 [
// ...
];
}
在建構您的應用程式時,您應該特別小心以避免建立這些類型的記憶體洩漏。建議您在本地開發期間監控應用程式的記憶體使用情況,以確保您不會將新的記憶體洩漏引入到您的應用程式中。
並行任務 (Concurrent Tasks)
[!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 的「任務 worker」,並在與進入請求完全不同的程序中執行。可用於處理並行任務的 worker 數量由 octane:start 指令上的 --task-workers 指令決定:
php artisan octane:start --workers=4 --task-workers=6
當呼叫 concurrently 方法時,由於 Swoole 任務系統的限制,您不應提供超過 1024 個任務。
Ticks 與 Intervals (Ticks And Intervals)
[!WARNING] 此功能需要 Swoole。
使用 Swoole 時,您可以註冊「tick」操作,該操作將每隔指定的秒數執行。您可以透過 tick 方法註冊「tick」回呼。提供給 tick 方法的第一個參數應該是一個字串,表示 ticker 的名稱。第二個參數應該是一個可呼叫的,它將在指定的間隔被呼叫。
在此範例中,我們將註冊一個閉包,該閉包將每 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 快取 (The Octane Cache)
[!WARNING] 此功能需要 Swoole。
使用 Swoole 時,您可以利用 Octane 快取驅動程式,它提供每秒高達 200 萬次操作的讀寫速度。因此,此快取驅動程式是需要從快取層獲得極高讀/寫速度的應用程式的絕佳選擇。
此快取驅動程式由 Swoole 表驅動。儲存在快取中的所有資料可供伺服器上的所有 worker 使用。然而,當伺服器重新啟動時,快取的資料將被清除:
Cache::store('octane')->put('framework', 'Laravel', 30);
[!NOTE] Octane 快取中允許的最大條目數可以在您的應用程式
octane設定檔中定義。
快取 Intervals (Cache Intervals)
除了 Laravel 快取系統提供的典型方法外,Octane 快取驅動程式還具有基於間隔的快取。這些快取會在指定的間隔自動刷新,並應該在應用程式其中一個服務提供者的 boot 方法中註冊。例如,以下快取將每五秒刷新一次:
use Illuminate\Support\Str;
Cache::store('octane')->interval('random', function () {
return Str::random(10);
}, seconds: 5);
資料表 (Tables)
[!WARNING] 此功能需要 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');
[!WARNING] Swoole 表支援的欄位類型為:
string、int和float。