跳至内容

Laravel Octane

介绍

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

安装

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

composer require laravel/octane

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

php artisan octane:install

服务器先决条件

exclamation

Laravel Octane 需要 PHP 8.1+

FrankenPHP

FrankenPHP 是用 Go 编写的 PHP 应用程序服务器,它支持现代 Web 功能,例如早期提示、Brotli 和 Zstandard 压缩。当您安装 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=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 由 RoadRunner 二进制文件驱动,该二进制文件是用 Go 构建的。第一次启动基于 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=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=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 提供您的应用程序

lightbulb

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

在生产环境中,您应该在传统的 Web 服务器(如 Nginx 或 Apache)后面提供您的 Octane 应用程序。这样做将允许 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 方法时,您不应该提供超过 1024 个任务,因为 Swoole 的任务系统存在限制。

刻度和间隔

exclamation

此功能需要 Swoole

使用 Swoole 时,您可以注册“tick”操作,这些操作将在指定的秒数后执行。您可以通过 tick 方法注册“tick”回调。提供给 tick 方法的第一个参数应该是表示计时器名称的字符串。第二个参数应该是将在指定间隔内调用的可调用对象。

在此示例中,我们将注册一个将在每 10 秒钟内调用的闭包。通常,tick 方法应该在应用程序服务提供者之一的 boot 方法中调用。

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

使用 immediate 方法,您可以指示 Octane 在 Octane 服务器最初启动时立即调用 tick 回调,并在之后的每 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 方法中注册。例如,以下缓存将在每 5 秒钟刷新一次。

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