跳轉至內容

Laravel Octane

簡介

Laravel Octane 透過使用高效能應用伺服器(包括 FrankenPHPOpen SwooleSwooleRoadRunner)來提升您的應用效能。Octane 只需啟動應用一次,將其保留在記憶體中,然後以超音速處理請求。

安裝

可透過 Composer 包管理器安裝 Octane

1composer require laravel/octane

安裝 Octane 後,您可以執行 octane:install Artisan 命令,將 Octane 的配置檔案安裝到您的應用中

1php artisan octane:install

伺服器先決條件

FrankenPHP

FrankenPHP 是一個用 Go 編寫的 PHP 應用伺服器,支援現代 Web 特性,如早期提示 (Early Hints)、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:// 訪問您的 FrankenPHP Sail 應用,因為使用 https://127.0.0.1 需要額外配置且不被推薦

透過 Docker 使用 FrankenPHP

使用 FrankenPHP 的官方 Docker 映象可以提供更好的效能,並使用 FrankenPHP 靜態安裝中未包含的額外擴充套件。此外,官方 Docker 映象支援在 FrankenPHP 本身不支援的平臺(如 Windows)上執行。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 的更多資訊。

自定義 Caddyfile 配置

使用 FrankenPHP 時,您可以在啟動 Octane 時透過 --caddyfile 選項指定自定義的 Caddyfile

1php artisan octane:start --server=frankenphp --caddyfile=/path/to/your/Caddyfile

這允許您在預設設定之外自定義 FrankenPHP 的配置,例如新增自定義中介軟體、配置高階路由或設定自定義指令。您可以查閱 Caddy 官方文件以瞭解更多有關 Caddyfile 語法和配置選項的資訊。

RoadRunner

RoadRunner 由基於 Go 構建的 RoadRunner 二進位制檔案驅動。首次啟動基於 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 可執行檔案獲取最新的 Linux 版 RoadRunner 二進位制檔案

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

在 Open Swoole 上使用 Laravel Octane 提供了與 Swoole 相同的功能,例如併發任務、Ticks 和間隔。

透過 Laravel Sail 使用 Swoole

在透過 Sail 執行 Octane 應用之前,請確保擁有最新版本的 Laravel Sail,並在應用根目錄下執行 ./vendor/bin/sail build --no-cache

或者,您可以使用官方的基於 Docker 的 Laravel 開發環境 Laravel Sail 來開發基於 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://:8000 在瀏覽器中訪問您的應用。

在生產環境中保持 Octane 執行

如果您將 Octane 應用部署到生產環境,應使用 Supervisor 等程序監控工具來確保 Octane 伺服器保持執行。一個簡單的 Octane Supervisor 配置檔案示例如下

1[program:octane]
2process_name=%(program_name)s_%(process_num)02d
3command=php /home/forge/example.com/artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8000
4autostart=true
5autorestart=true
6user=forge
7redirect_stderr=true
8stdout_logfile=/home/forge/example.com/storage/logs/octane.log
9stopwaitsecs=3600

透過 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 支援。

在生產環境中,您應該在傳統的 Web 伺服器(如 Nginx 或 Apache)之後執行 Octane 應用。這樣 Web 伺服器可以處理靜態資源(如圖片和樣式表),並管理 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。這些 Worker 將用於處理進入應用傳入的 HTTP 請求。您可以在呼叫 octane:start 命令時使用 --workers 選項手動指定要啟動的 Worker 數量

1php artisan octane:start --workers=4

如果您使用 Swoole 應用伺服器,還可以指定要啟動多少個 “任務 Worker”

1php artisan octane:start --workers=4 --task-workers=6

指定最大請求數

為了防止記憶體洩漏,Octane 會在 Worker 處理 500 個請求後優雅地重啟它。要調整此數字,可以使用 --max-requests 選項

1php artisan octane:start --max-requests=250

指定最大執行時間

預設情況下,Laravel Octane 透過應用 config/octane.php 配置檔案中的 max_execution_time 選項為傳入請求設定 30 秒的最大執行時間

1'max_execution_time' => 30,

此設定定義了傳入請求在終止前允許執行的最大秒數。將此值設定為 0 將完全停用執行時間限制。此配置選項對於處理長時間執行的請求(例如檔案上傳、資料處理或對外部服務的 API 呼叫)的應用特別有用。

當您修改 max_execution_time 配置時,必須重啟 Octane 伺服器以使更改生效。

過載 Workers

您可以使用 octane:reload 命令優雅地重啟 Octane 伺服器的 Worker。通常,這應該在部署後執行,以便將新部署的程式碼載入到記憶體中,並用於處理後續請求

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 的“任務 Worker”,並在與傳入請求完全不同的程序中執行。可用於處理併發任務的 Worker 數量由 octane:start 命令上的 --task-workers 指令決定

1php artisan octane:start --workers=4 --task-workers=6

呼叫 concurrently 方法時,由於 Swoole 任務系統的限制,您不應提供超過 1024 個任務。

Ticks 與間隔

此功能需要 Swoole

使用 Swoole 時,您可以註冊每隔指定秒數執行一次的“Tick”操作。您可以透過 tick 方法註冊“Tick”回撥。提供給 tick 方法的第一個引數應該是表示 Tick 名稱的字串。第二個引數應該是將在指定間隔呼叫的可呼叫物件。

在此示例中,我們將註冊一個每 10 秒呼叫一次的閉包。通常,tick 方法應在應用的服務提供者的 boot 方法中呼叫

1Octane::tick('simple-ticker', fn () => ray('Ticking...'))
2 ->seconds(10);

使用 immediate 方法,您可以指示 Octane 在 Octane 伺服器最初啟動時立即呼叫 Tick 回撥,此後每隔 N 秒呼叫一次

1Octane::tick('simple-ticker', fn () => ray('Ticking...'))
2 ->seconds(10)
3 ->immediate();

Octane 快取

此功能需要 Swoole

使用 Swoole 時,您可以利用 Octane 快取驅動,它提供高達每秒 200 萬次操作的讀寫速度。因此,該快取驅動對於需要快取層具有極致讀/寫速度的應用來說是一個絕佳選擇。

此快取驅動由 Swoole 資料表驅動。儲存在快取中的所有資料對伺服器上的所有 Worker 均可見。但是,快取資料將在伺服器重啟時被清空

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);

資料表 (Tables)

此功能需要 Swoole

使用 Swoole 時,您可以定義並與自己的任意 Swoole 資料表進行互動。Swoole 資料表提供極高的吞吐效能,並且這些表中的資料可以被伺服器上的所有 Worker 訪問。但是,其中的資料將在伺服器重啟時丟失。

表應在應用 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