Laravel Octane
簡介
Laravel Octane 透過使用高效能應用程式伺服器(包含 FrankenPHP、Open Swoole、Swoole 和 RoadRunner)服務您的應用程式,從而大幅提升應用程式效能。Octane 只需啟動您的應用程式一次,並將其保存在記憶體中,然後以超音速的速度將請求饋送給它。
安裝
Octane 可以透過 Composer 套件管理器安裝
1composer require laravel/octane
安裝 Octane 後,您可以執行 octane:install
Artisan 命令,這會將 Octane 的設定檔安裝到您的應用程式中
1php artisan octane:install
伺服器先決條件
Laravel Octane 需要 PHP 8.1+。
FrankenPHP
FrankenPHP 是一個 PHP 應用程式伺服器,以 Go 語言編寫,支援現代網路功能,如早期提示、Brotli 和 Zstandard 壓縮。當您安裝 Octane 並選擇 FrankenPHP 作為您的伺服器時,Octane 將自動為您下載並安裝 FrankenPHP 二進制檔案。
透過 Laravel Sail 使用 FrankenPHP
如果您計劃使用 Laravel Sail 開發您的應用程式,您應該執行以下命令以安裝 Octane 和 FrankenPHP
1./vendor/bin/sail up2 3./vendor/bin/sail composer require laravel/octane
接下來,您應該使用 octane:install
Artisan 命令來安裝 FrankenPHP 二進制檔案
1./vendor/bin/sail artisan octane:install --server=frankenphp
最後,將 SUPERVISOR_PHP_COMMAND
環境變數新增到您的應用程式 docker-compose.yml
檔案中的 laravel.test
服務定義中。此環境變數將包含 Sail 將用來使用 Octane 而不是 PHP 開發伺服器來服務您的應用程式的命令
1services:2 laravel.test:3 environment:4 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}'" 5 XDG_CONFIG_HOME: /var/www/html/config 6 XDG_DATA_HOME: /var/www/html/data
若要啟用 HTTPS、HTTP/2 和 HTTP/3,請改為套用這些修改
1services: 2 laravel.test: 3 ports: 4 - '${APP_PORT:-80}:80' 5 - '${VITE_PORT:-5173}:${VITE_PORT:-5173}' 6 - '443:443' 7 - '443:443/udp' 8 environment: 9 SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --host=localhost --port=443 --admin-port=2019 --https" 10 XDG_CONFIG_HOME: /var/www/html/config 11 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 映像檔支援在原生不支援的平台上(例如 Windows)執行 FrankenPHP。FrankenPHP 的官方 Docker 映像檔適用於本機開發和生產環境使用。
您可以將以下 Dockerfile 作為容器化您的 FrankenPHP 驅動的 Laravel 應用程式的起點
1FROM dunglas/frankenphp2 3RUN install-php-extensions \4 pcntl5 # Add other PHP extensions here...6 7COPY . /app8 9ENTRYPOINT ["php", "artisan", "octane:frankenphp"]
然後,在開發期間,您可以使用以下 Docker Compose 檔案來執行您的應用程式
1# compose.yaml 2services: 3 frankenphp: 4 build: 5 context: . 6 entrypoint: php artisan octane:frankenphp --workers=1 --max-requests=1 7 ports: 8 - "8000:8000" 9 volumes:10 - .:/app
如果 --log-level
選項明確傳遞到 php artisan octane:start
命令,Octane 將使用 FrankenPHP 的原生記錄器,並且除非另行設定,否則將產生結構化的 JSON 日誌。
您可以查閱 官方 FrankenPHP 文件,以獲取有關使用 Docker 執行 FrankenPHP 的更多資訊。
RoadRunner
RoadRunner 由 RoadRunner 二進制檔案驅動,該二進制檔案使用 Go 語言構建。當您第一次啟動基於 RoadRunner 的 Octane 伺服器時,Octane 將提供為您下載並安裝 RoadRunner 二進制檔案。
透過 Laravel Sail 使用 RoadRunner
如果您計劃使用 Laravel Sail 開發您的應用程式,您應該執行以下命令以安裝 Octane 和 RoadRunner
1./vendor/bin/sail up2 3./vendor/bin/sail composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http
接下來,您應該啟動 Sail shell 並使用 rr
可執行檔來檢索 RoadRunner 二進制檔案的最新基於 Linux 的版本
1./vendor/bin/sail shell2 3# Within the Sail shell...4./vendor/bin/rr get-binary
然後,將 SUPERVISOR_PHP_COMMAND
環境變數新增到您的應用程式 docker-compose.yml
檔案中的 laravel.test
服務定義中。此環境變數將包含 Sail 將用來使用 Octane 而不是 PHP 開發伺服器來服務您的應用程式的命令
1services:2 laravel.test:3 environment:4 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 映像檔
1chmod +x ./rr2 3./vendor/bin/sail build --no-cache
Swoole
如果您計劃使用 Swoole 應用程式伺服器來服務您的 Laravel Octane 應用程式,您必須安裝 Swoole PHP 擴充功能。通常,這可以透過 PECL 完成
1pecl install swoole
Open Swoole
如果您想使用 Open Swoole 應用程式伺服器來服務您的 Laravel Octane 應用程式,您必須安裝 Open Swoole PHP 擴充功能。通常,這可以透過 PECL 完成
1pecl install openswoole
將 Laravel Octane 與 Open Swoole 結合使用,可提供與 Swoole 相同的功能,例如並行任務、ticks 和 intervals。
透過 Laravel Sail 使用 Swoole
在透過 Sail 服務 Octane 應用程式之前,請確保您擁有最新版本的 Laravel Sail,並在應用程式的根目錄中執行 ./vendor/bin/sail build --no-cache
。
或者,您可以使用 Laravel Sail(Laravel 的官方基於 Docker 的開發環境)來開發基於 Swoole 的 Octane 應用程式。Laravel Sail 預設包含 Swoole 擴充功能。但是,您仍然需要調整 Sail 使用的 docker-compose.yml
檔案。
首先,將 SUPERVISOR_PHP_COMMAND
環境變數新增到您的應用程式 docker-compose.yml
檔案中的 laravel.test
服務定義中。此環境變數將包含 Sail 將用來使用 Octane 而不是 PHP 開發伺服器來服務您的應用程式的命令
1services:2 laravel.test:3 environment:4 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 映像檔
1./vendor/bin/sail build --no-cache
Swoole 設定
Swoole 支援一些額外的設定選項,如果需要,您可以將其新增到您的 octane
設定檔中。由於它們很少需要修改,因此預設設定檔中未包含這些選項
1'swoole' => [2 'options' => [3 'log_file' => storage_path('logs/swoole_http.log'),4 'package_max_length' => 10 * 1024 * 1024,5 ],6],
服務您的應用程式
Octane 伺服器可以透過 octane:start
Artisan 命令啟動。預設情況下,此命令將使用應用程式 octane
設定檔的 server
設定選項指定的伺服器
1php artisan octane:start
預設情況下,Octane 將在端口 8000 上啟動伺服器,因此您可以透過 https://127.0.0.1:8000
在網路瀏覽器中存取您的應用程式。
透過 HTTPS 服務您的應用程式
預設情況下,透過 Octane 執行的應用程式會產生以 http://
為前綴的連結。當透過 HTTPS 服務您的應用程式時,可以在應用程式的 config/octane.php
設定檔中使用的 OCTANE_HTTPS
環境變數設定為 true
。當此設定值設定為 true
時,Octane 將指示 Laravel 為所有產生的連結加上 https://
前綴
1'https' => env('OCTANE_HTTPS', false),
透過 Nginx 服務您的應用程式
如果您還沒準備好管理自己的伺服器設定,或者不熟悉設定執行穩健的 Laravel Octane 應用程式所需的所有各種服務,請查看 Laravel Cloud,它提供完全託管的 Laravel Octane 支援。
在生產環境中,您應該在傳統網路伺服器(例如 Nginx 或 Apache)後面服務您的 Octane 應用程式。這樣做將允許網路伺服器服務您的靜態資源(例如圖片和樣式表),並管理您的 SSL 憑證終止。
在下面的 Nginx 設定範例中,Nginx 將服務網站的靜態資源,並將請求代理到在端口 8000 上執行的 Octane 伺服器
1map $http_upgrade $connection_upgrade { 2 default upgrade; 3 '' close; 4} 5 6server { 7 listen 80; 8 listen [::]:80; 9 server_name domain.com;10 server_tokens off;11 root /home/forge/domain.com/public;12 13 index index.php;14 15 charset utf-8;16 17 location /index.php {18 try_files /not_exists @octane;19 }20 21 location / {22 try_files $uri $uri/ @octane;23 }24 25 location = /favicon.ico { access_log off; log_not_found off; }26 location = /robots.txt { access_log off; log_not_found off; }27 28 access_log off;29 error_log /var/log/nginx/domain.com-error.log error;30 31 error_page 404 /index.php;32 33 location @octane {34 set $suffix "";35 36 if ($uri = /index.php) {37 set $suffix ?$query_string;38 }39 40 proxy_http_version 1.1;41 proxy_set_header Host $http_host;42 proxy_set_header Scheme $scheme;43 proxy_set_header SERVER_PORT $server_port;44 proxy_set_header REMOTE_ADDR $remote_addr;45 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;46 proxy_set_header Upgrade $http_upgrade;47 proxy_set_header Connection $connection_upgrade;48 49 proxy_pass http://127.0.0.1:8000$suffix;50 }51}
監看檔案變更
由於您的應用程式在 Octane 伺服器啟動時會載入到記憶體中一次,因此當您刷新瀏覽器時,對應用程式檔案的任何變更都不會反映出來。例如,新增到您的 routes/web.php
檔案的路由定義將不會反映出來,直到伺服器重新啟動。為了方便起見,您可以使用 --watch
標誌來指示 Octane 在應用程式中的任何檔案變更時自動重新啟動伺服器
1php artisan octane:start --watch
在使用此功能之前,您應確保 Node 安裝在您的本機開發環境中。此外,您應該在您的專案中安裝 Chokidar 檔案監看程式庫
1npm install --save-dev chokidar
您可以使用應用程式 config/octane.php
設定檔中的 watch
設定選項,來設定應監看的目錄和檔案。
指定 Worker 數量
預設情況下,Octane 將為您的機器提供的每個 CPU 核心啟動一個應用程式請求 worker。然後,這些 workers 將用於在傳入的 HTTP 請求進入您的應用程式時服務這些請求。您可以在調用 octane:start
命令時使用 --workers
選項,手動指定您想要啟動多少個 workers
1php artisan octane:start --workers=4
如果您使用的是 Swoole 應用程式伺服器,您還可以指定要啟動多少個 「任務 workers」
1php artisan octane:start --workers=4 --task-workers=6
指定最大請求計數
為了幫助防止不必要的記憶體洩漏,Octane 會在 worker 處理 500 個請求後優雅地重新啟動它。若要調整此數字,您可以使用 --max-requests
選項
1php artisan octane:start --max-requests=250
重新載入 Workers
您可以使用 octane:reload
命令優雅地重新啟動 Octane 伺服器的應用程式 workers。通常,這應該在部署後完成,以便將您新部署的程式碼載入到記憶體中,並用於服務後續請求
1php artisan octane:reload
停止伺服器
您可以使用 octane:stop
Artisan 命令停止 Octane 伺服器
1php artisan octane:stop
檢查伺服器狀態
您可以使用 octane:status
Artisan 命令檢查 Octane 伺服器的目前狀態
1php artisan octane:status
依賴注入與 Octane
由於 Octane 只啟動您的應用程式一次,並在服務請求時將其保存在記憶體中,因此在構建應用程式時,您應該考慮一些注意事項。例如,應用程式服務提供者的 register
和 boot
方法只會在請求 worker 初始啟動時執行一次。在後續請求中,將重複使用相同的應用程式實例。
鑑於此,當將應用程式服務容器或請求注入到任何物件的建構子中時,您應特別小心。這樣做可能會導致該物件在後續請求中具有過時版本的容器或請求。
Octane 將自動處理在請求之間重設任何第一方框架狀態。但是,Octane 並非總是知道如何重設應用程式建立的全域狀態。因此,您應該了解如何以 Octane 友善的方式構建您的應用程式。下面,我們將討論在使用 Octane 時可能導致問題的最常見情況。
容器注入
一般來說,您應該避免將應用程式服務容器或 HTTP 請求實例注入到其他物件的建構子中。例如,以下綁定將整個應用程式服務容器注入到作為單例綁定的物件中
1use App\Service; 2use Illuminate\Contracts\Foundation\Application; 3 4/** 5 * Register any application services. 6 */ 7public function register(): void 8{ 9 $this->app->singleton(Service::class, function (Application $app) {10 return new Service($app);11 });12}
在此範例中,如果在應用程式啟動過程中解析 Service
實例,則容器將注入到服務中,並且該相同的容器將由後續請求中的 Service
實例持有。這對於您的特定應用程式 可能 不是問題;但是,它可能會導致容器意外遺失在啟動週期稍後或由後續請求新增的綁定。
作為一種解決方法,您可以停止將綁定註冊為單例,或者您可以將容器解析器閉包注入到始終解析目前容器實例的服務中
1use App\Service; 2use Illuminate\Container\Container; 3use Illuminate\Contracts\Foundation\Application; 4 5$this->app->bind(Service::class, function (Application $app) { 6 return new Service($app); 7}); 8 9$this->app->singleton(Service::class, function () {10 return new Service(fn () => Container::getInstance());11});
全域 app
輔助函數和 Container::getInstance()
方法將始終傳回最新版本的應用程式容器。
請求注入
一般來說,您應該避免將應用程式服務容器或 HTTP 請求實例注入到其他物件的建構子中。例如,以下綁定將整個請求實例注入到作為單例綁定的物件中
1use App\Service; 2use Illuminate\Contracts\Foundation\Application; 3 4/** 5 * Register any application services. 6 */ 7public function register(): void 8{ 9 $this->app->singleton(Service::class, function (Application $app) {10 return new Service($app['request']);11 });12}
在此範例中,如果在應用程式啟動過程中解析 Service
實例,則 HTTP 請求將注入到服務中,並且該相同的請求將由後續請求中的 Service
實例持有。因此,所有標頭、輸入和查詢字串資料以及所有其他請求資料都將不正確。
作為一種解決方法,您可以停止將綁定註冊為單例,或者您可以將請求解析器閉包注入到始終解析目前請求實例的服務中。或者,最推薦的方法只是將您的物件所需之特定請求資訊傳遞到物件在運行時的方法之一
1use App\Service; 2use Illuminate\Contracts\Foundation\Application; 3 4$this->app->bind(Service::class, function (Application $app) { 5 return new Service($app['request']); 6}); 7 8$this->app->singleton(Service::class, function (Application $app) { 9 return new Service(fn () => $app['request']);10});11 12// Or...13 14$service->method($request->input('name'));
全域 request
輔助函數將始終傳回應用程式目前正在處理的請求,因此在您的應用程式中使用它是安全的。
在您的控制器方法和路由閉包中類型提示 Illuminate\Http\Request
實例是可以接受的。
組態儲存庫注入
一般來說,您應該避免將設定儲存庫實例注入到其他物件的建構子中。例如,以下綁定將設定儲存庫注入到作為單例綁定的物件中
1use App\Service; 2use Illuminate\Contracts\Foundation\Application; 3 4/** 5 * Register any application services. 6 */ 7public function register(): void 8{ 9 $this->app->singleton(Service::class, function (Application $app) {10 return new Service($app->make('config'));11 });12}
在此範例中,如果請求之間的設定值發生變更,則該服務將無法存取新值,因為它依賴於原始儲存庫實例。
作為一種解決方法,您可以停止將綁定註冊為單例,或者您可以將設定儲存庫解析器閉包注入到類別中
1use App\Service; 2use Illuminate\Container\Container; 3use Illuminate\Contracts\Foundation\Application; 4 5$this->app->bind(Service::class, function (Application $app) { 6 return new Service($app->make('config')); 7}); 8 9$this->app->singleton(Service::class, function () {10 return new Service(fn () => Container::getInstance()->make('config'));11});
全域 config
將始終傳回最新版本的設定儲存庫,因此在您的應用程式中使用它是安全的。
管理記憶體洩漏
請記住,Octane 會在請求之間將您的應用程式保存在記憶體中;因此,將資料新增到靜態維護的陣列將導致記憶體洩漏。例如,以下控制器具有記憶體洩漏,因為對應用程式的每個請求都會繼續將資料新增到靜態 $data
陣列
1use App\Service; 2use Illuminate\Http\Request; 3use Illuminate\Support\Str; 4 5/** 6 * Handle an incoming request. 7 */ 8public function index(Request $request): array 9{10 Service::$data[] = Str::random(10);11 12 return [13 // ...14 ];15}
在構建應用程式時,您應特別注意避免建立這些類型的記憶體洩漏。建議您在本機開發期間監控應用程式的記憶體使用量,以確保您沒有將新的記憶體洩漏引入到您的應用程式中。
並行任務
此功能需要 Swoole。
使用 Swoole 時,您可以透過輕量級背景任務同時執行操作。您可以使用 Octane 的 concurrently
方法來完成此操作。您可以將此方法與 PHP 陣列解構結合使用,以檢索每個操作的結果
1use App\Models\User;2use App\Models\Server;3use Laravel\Octane\Facades\Octane;4 5[$users, $servers] = Octane::concurrently([6 fn () => User::all(),7 fn () => Server::all(),8]);
由 Octane 處理的並行任務利用 Swoole 的「任務 workers」,並在與傳入請求完全不同的進程中執行。可用於處理並行任務的 workers 數量由 octane:start
命令上的 --task-workers
指令決定
1php artisan octane:start --workers=4 --task-workers=6
在調用 concurrently
方法時,由於 Swoole 任務系統施加的限制,您不應提供超過 1024 個任務。
Ticks 與 Intervals(嘀嗒與間隔)
此功能需要 Swoole。
使用 Swoole 時,您可以註冊「tick」操作,這些操作將在每個指定的秒數執行一次。您可以透過 tick
方法註冊「tick」回呼。提供給 tick
方法的第一個引數應該是一個字串,表示 ticker 的名稱。第二個引數應該是一個可調用的函數,它將在指定的間隔被調用。
在此範例中,我們將註冊一個閉包,使其每 10 秒調用一次。通常,應該在應用程式的其中一個服務提供者的 boot
方法中調用 tick
方法
1Octane::tick('simple-ticker', fn () => ray('Ticking...'))2 ->seconds(10);
使用 immediate
方法,您可以指示 Octane 在 Octane 伺服器初始啟動時以及之後每 N 秒立即調用 tick 回呼
1Octane::tick('simple-ticker', fn () => ray('Ticking...'))2 ->seconds(10)3 ->immediate();
Octane 快取
此功能需要 Swoole。
使用 Swoole 時,您可以利用 Octane 快取驅動程式,它提供高達每秒 200 萬次操作的讀寫速度。因此,對於需要從快取層獲得極端讀取/寫入速度的應用程式來說,此快取驅動程式是一個絕佳的選擇。
此快取驅動程式由 Swoole 表格 驅動。儲存在快取中的所有資料都可供伺服器上的所有 workers 使用。但是,當伺服器重新啟動時,快取資料將被清除
1Cache::store('octane')->put('framework', 'Laravel', 30);
Octane 快取中允許的最大條目數可以在應用程式的 octane
設定檔中定義。
快取間隔
除了 Laravel 快取系統提供的典型方法外,Octane 快取驅動程式還具有基於間隔的快取。這些快取會在指定的間隔自動刷新,應在應用程式的其中一個服務提供者的 boot
方法中註冊。例如,以下快取將每五秒刷新一次
1use Illuminate\Support\Str;2 3Cache::store('octane')->interval('random', function () {4 return Str::random(10);5}, seconds: 5);
表格
此功能需要 Swoole。
使用 Swoole 時,您可以定義和與您自己的任意 Swoole 表格 互動。Swoole 表格提供極高的效能吞吐量,並且伺服器上的所有 workers 都可以存取這些表格中的資料。但是,其中的資料將在伺服器重新啟動時遺失。
表格應在應用程式 octane
設定檔的 tables
設定陣列中定義。一個允許最多 1000 行的範例表格已為您設定。字串欄位的最大大小可以透過在欄位類型後指定欄位大小來設定,如下所示
1'tables' => [2 'example:1000' => [3 'name' => 'string:1000',4 'votes' => 'int',5 ],6],
若要存取表格,您可以使用 Octane::table
方法
1use Laravel\Octane\Facades\Octane;2 3Octane::table('example')->set('uuid', [4 'name' => 'Nuno Maduro',5 'votes' => 1000,6]);7 8return Octane::table('example')->get('uuid');
Swoole 表格支援的欄位類型為:string
、int
和 float
。