跳到內容

Laravel Octane

簡介

Laravel Octane 透過使用高效能應用程式伺服器(包含 FrankenPHPOpen SwooleSwooleRoadRunner)服務您的應用程式,從而大幅提升應用程式效能。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 up
2 
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/frankenphp
2 
3RUN install-php-extensions \
4 pcntl
5 # Add other PHP extensions here...
6 
7COPY . /app
8 
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 up
2 
3./vendor/bin/sail composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http

接下來,您應該啟動 Sail shell 並使用 rr 可執行檔來檢索 RoadRunner 二進制檔案的最新基於 Linux 的版本

1./vendor/bin/sail shell
2 
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 ./rr
2 
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 只啟動您的應用程式一次,並在服務請求時將其保存在記憶體中,因此在構建應用程式時,您應該考慮一些注意事項。例如,應用程式服務提供者的 registerboot 方法只會在請求 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 表格支援的欄位類型為:stringintfloat

Laravel 是最有效率的方式來
建構、部署和監控軟體。