跳转到内容

Laravel Octane

简介

Laravel Octane 通过使用功能强大的应用服务器(包括 FrankenPHPOpen SwooleSwooleRoadRunner)来提供您的应用程序服务,从而大大提升应用程序的性能。Octane 会启动您的应用程序一次,将其保存在内存中,然后以超音速的速度向其提供请求。

安装

Octane 可以通过 Composer 包管理器安装

composer require laravel/octane

安装 Octane 后,您可以执行 octane:install Artisan 命令,该命令会将 Octane 的配置文件安装到您的应用程序中

php artisan octane:install

服务器先决条件

exclamation

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 映像还支持在它本身不支持的平台(例如 Windows)上运行 FrankenPHP。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 的更多信息。

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 相同的功能,例如并发任务、滴答和间隔。

通过 Laravel Sail 使用 Swoole

exclamation

在通过 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 开发服务器来提供您的应用程序服务的命令

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 上启动服务器,因此您可以通过 https://127.0.0.1:8000 在 Web 浏览器中访问您的应用程序。

通过 HTTPS 提供您的应用程序服务

默认情况下,通过 Octane 运行的应用程序生成的链接会以 http:// 为前缀。当通过 HTTPS 提供您的应用程序时,可以在应用程序的 config/octane.php 配置文件中使用 OCTANE_HTTPS 环境变量设置为 true。当此配置值设置为 true 时,Octane 将指示 Laravel 为所有生成的链接添加 https:// 前缀。

'https' => env('OCTANE_HTTPS', false),

通过 Nginx 提供您的应用程序服务

lightbulb

如果您尚未准备好管理自己的服务器配置,或者不熟悉配置运行强大的 Laravel Octane 应用程序所需的各种服务,请查看 Laravel Forge

在生产环境中,您应该将 Octane 应用程序部署在传统的 Web 服务器(如 Nginx 或 Apache)之后。这样做可以使 Web 服务器处理您的静态资源(如图像和样式表),并管理您的 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;
}
}

监视文件更改

由于您的应用程序在 Octane 服务器启动时会一次性加载到内存中,因此当您刷新浏览器时,对应用程序文件的任何更改都不会反映出来。例如,添加到 routes/web.php 文件中的路由定义,在服务器重新启动之前不会反映出来。为了方便起见,您可以使用 --watch 标志来指示 Octane 在应用程序中的任何文件更改时自动重新启动服务器。

php artisan octane:start --watch

在使用此功能之前,您应确保您的本地开发环境中安装了 Node。此外,您应该在您的项目中安装 Chokidar 文件监视库。

npm install --save-dev chokidar

您可以使用应用程序 config/octane.php 配置文件中的 watch 配置选项来配置应该监视的目录和文件。

指定工作进程数

默认情况下,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

重新加载工作进程

您可以使用 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 会启动您的应用程序一次并在处理请求时将其保留在内存中,因此在构建应用程序时,您应该考虑一些注意事项。例如,您的应用程序服务提供程序的 registerboot 方法仅在请求工作进程首次启动时执行一次。在后续请求中,将重用相同的应用程序实例。

鉴于此,在将应用程序服务容器或请求注入到任何对象的构造函数时,您应特别注意。这样做可能会导致该对象在后续请求中具有过时的容器或请求版本。

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 辅助函数将始终返回应用程序当前正在处理的请求,因此在您的应用程序中使用它是安全的。

exclamation

在您的控制器方法和路由闭包中类型提示 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 [
// ...
];
}

在构建应用程序时,您应该特别注意避免创建这些类型的内存泄漏。建议您在本地开发期间监视应用程序的内存使用情况,以确保您不会在应用程序中引入新的内存泄漏。

并发任务

exclamation

此功能需要 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 个任务。

滴答和间隔

exclamation

此功能需要 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 缓存

exclamation

此功能需要 Swoole

使用 Swoole 时,您可以利用 Octane 缓存驱动程序,该驱动程序可提供高达每秒 200 万次操作的读写速度。因此,对于需要从缓存层获得极高读/写速度的应用程序,此缓存驱动程序是一个极佳的选择。

此缓存驱动程序由 Swoole 表格 提供支持。缓存中存储的所有数据对服务器上的所有工作进程都可用。但是,缓存的数据将在服务器重新启动时被刷新。

Cache::store('octane')->put('framework', 'Laravel', 30);
lightbulb

Octane 缓存中允许的最大条目数可以在您的应用程序 octane 配置文件中定义。

缓存间隔

除了 Laravel 的缓存系统提供的典型方法外,Octane 缓存驱动程序还具有基于间隔的缓存。这些缓存会在指定的间隔自动刷新,并且应在应用程序的服务提供程序之一的 boot 方法中注册。例如,以下缓存将每五秒刷新一次。

use Illuminate\Support\Str;
 
Cache::store('octane')->interval('random', function () {
return Str::random(10);
}, seconds: 5);

exclamation

此功能需要 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');
exclamation

Swoole 表格支持的列类型为:stringintfloat