Laravel Octane
簡介
Laravel Octane 透過使用高效能應用程式伺服器(包括 FrankenPHP、Open Swoole、Swoole 和 RoadRunner)來提供應用程式,從而大幅提升應用程式效能。Octane 只會啟動一次應用程式,將其保存在記憶體中,然後以超音速速度將請求饋送給它。
安裝
可以使用 Composer 套件管理器安裝 Octane
composer require laravel/octane
安裝 Octane 後,您可以執行 octane:install
Artisan 命令,這會將 Octane 的設定檔安裝到您的應用程式中
php artisan octane:install
伺服器先決條件
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.yamlservices: 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
在透過 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 提供您的應用程式
如果您還沒有準備好管理自己的伺服器設定,或者不熟悉設定執行健全的 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 會啟動您的應用程式一次,並在處理請求時將其保留在記憶體中,因此在建置應用程式時,您應該考慮一些注意事項。例如,您的應用程式服務提供者的 register
和 boot
方法只會在請求工作程序首次啟動時執行一次。在後續的請求中,將會重複使用相同的應用程式實例。
有鑑於此,您應該特別注意將應用程式服務容器或請求注入到任何物件的建構子中。這樣做的話,該物件在後續的請求中可能會擁有過時的容器或請求版本。
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 的「任務工作程序」,並在與傳入請求完全不同的程序中執行。處理並行任務的工作程序數量由 octane:start
命令上的 --task-workers
指令決定。
php artisan octane:start --workers=4 --task-workers=6
在呼叫 concurrently
方法時,由於 Swoole 任務系統施加的限制,您不應提供超過 1024 個任務。
Ticks 和間隔
此功能需要 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 快取
此功能需要 Swoole。
使用 Swoole 時,您可以利用 Octane 快取驅動程式,該驅動程式提供高達每秒 200 萬次操作的讀取和寫入速度。因此,此快取驅動程式非常適合需要從其快取層獲得極高讀取/寫入速度的應用程式。
此快取驅動程式由 Swoole 表格提供支援。儲存在快取中的所有資料都可供伺服器上的所有工作程序使用。但是,快取的資料將在伺服器重新啟動時清除。
Cache::store('octane')->put('framework', 'Laravel', 30);
Octane 快取中允許的最大條目數可以在您的應用程式的 octane
設定檔中定義。
快取間隔
除了 Laravel 快取系統提供的典型方法外,Octane 快取驅動程式還具有基於間隔的快取。這些快取會在指定的間隔自動重新整理,並且應該在您應用程式的其中一個服務提供者的 boot
方法中註冊。例如,以下快取將每五秒重新整理一次。
use Illuminate\Support\Str; Cache::store('octane')->interval('random', function () { return Str::random(10);}, seconds: 5);
表格
此功能需要 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');
Swoole 表格支援的欄類型為:string
、int
和 float
。