跳至内容

Laravel Octane

介绍

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

安装

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

1composer require laravel/octane

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

1php artisan octane:install

服务器先决条件

Laravel Octane 需要PHP 8.1+

FrankenPHP

FrankenPHP是一个用 Go 编写的 PHP 应用服务器,支持早期Prompts、Brotli 和 Zstandard 压缩等现代 Web 功能。当您安装 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:installArtisan 命令来安装 FrankenPHP 二进制文件:

1./vendor/bin/sail artisan octane:install --server=frankenphp

最后,在应用程序文件的服务定义中添加一个SUPERVISOR_PHP_COMMAND环境变量。此环境变量将包含 Sail 使用 Octane 而不是 PHP 开发服务器来服务应用程序的命令:laravel.testdocker-compose.yml

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

通常,您应该通过 访问您的 FrankenPHP Sail 应用程序https://localhost,因为使用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 的更多信息。

跑路者

RoadRunner由 RoadRunner 二进制文件驱动,该二进制文件使用 Go 语言构建。首次启动基于 RoadRunner 的 Octane 服务器时,Octane 会Prompts您下载并安装 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环境变量。此环境变量将包含 Sail 使用 Octane 而不是 PHP 开发服务器来为你的应用程序提供服务的命令:laravel.testdocker-compose.yml

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

打开 Swoole

如果您想使用 Open Swoole 应用服务器来服务您的 Laravel Octane 应用程序,则必须安装 Open Swoole PHP 扩展。通常,可以通过 PECL 完成:

1pecl install openswoole

将 Laravel Octane 与 Open Swoole 结合使用可获得 Swoole 提供的相同功能,例如并发任务、刻度和间隔。

通过 Laravel Sail 使用 Swoole

在通过 Sail 提供 Octane 应用程序之前,请确保您拥有最新版本的 Laravel Sail 并./vendor/bin/sail build --no-cache在应用程序的根目录中执行。

或者,您可以使用Laravel Sail (Laravel 官方基于 Docker 的开发环境)开发基于 Swoole 的 Octane 应用程序。Laravel Sail 默认包含 Swoole 扩展。但是,您仍然需要调整docker-compose.ymlSail 使用的文件。

首先,在应用程序文件的服务定义中添加一个SUPERVISOR_PHP_COMMAND环境变量。此环境变量将包含 Sail 使用 Octane 而不是 PHP 开发服务器来服务应用程序的命令:laravel.testdocker-compose.yml

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 服务器可以通过 Artisan 命令启动。默认情况下,此命令将使用应用程序配置文件中配置选项octane:start指定的服务器serveroctane

1php artisan octane:start

默认情况下,Octane 将在端口 8000 上启动服务器,因此您可以通过 Web 浏览器访问您的应用程序http://localhost:8000

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

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

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

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

如果您尚未准备好管理自己的服务器配置,或者不熟悉配置运行强大的 Laravel Octane 应用程序所需的各种服务,请查看Laravel Cloud,它提供完全托管的 Laravel Octane 支持。

在生产环境中,您应该将 Octane 应用程序部署到传统的 Web 服务器(例如 Nginx 或 Apache)后面。这样,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

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

指定工人数量

默认情况下,Octane 会为你机器的每个 CPU 核心启动一个应用程序请求工作器。这些工作器将用于处理进入应用程序的 HTTP 请求。你可以--workers在调用octane:start命令时使用以下选项手动指定要启动的工作器数量:

1php artisan octane:start --workers=4

如果您正在使用 Swoole 应用服务器,您还可以指定要启动多少个“任务工作者” :

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

指定最大请求数

为了防止内存泄漏,Octane 会在处理完 500 个请求后优雅地重启所有工作进程。要调整此数量,您可以使用以下--max-requests选项:

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

重新加载 Workers

您可以使用以下命令优雅地重启 Octane 服务器的应用程序工作进程octane:reload。通常,此操作应在部署后执行,以便将新部署的代码加载到内存中并用于处理后续请求:

1php artisan octane:reload

停止服务器

您可以使用 Artisan 命令停止 Octane 服务器octane:stop

1php artisan octane:stop

检查服务器状态

您可以使用 Artisan 命令检查 Octane 服务器的当前状态octane:status

1php artisan octane:status

依赖注入和 Octane

由于 Octane 只会启动一次应用程序,并在处理请求时将其保存在内存中,因此在构建应用程序时需要注意一些事项。例如,应用程序服务提供商的registerboot方法只会在请求工作器首次启动时执行一次。在后续请求中,相同的应用程序实例将被重用。

鉴于此,在将应用服务容器或请求注入任何对象的构造函数时,应格外小心。这样做可能会导致该对象在后续请求中保留旧版本的容器或请求。

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

全局requestHelpers将始终返响应用程序当前正在处理的请求,因此可以在您的应用程序中安全使用。

Illuminate\Http\Request在控制器方法和路由闭包上 输入Prompts实例是可以接受的。

配置存储库注入

通常,应避免将配置存储库实例注入到其他对象的构造函数中。例如,以下绑定将配置存储库注入到绑定为单例的对象中:

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 的“任务工作器”,并在与传入请求完全不同的进程中执行。可用于处理并发任务的工作器数量由命令--task-workers中的指令决定octane:start

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

调用该concurrently方法时,由于 Swoole 任务系统的限制,您不应提供超过 1024 个任务。

刻度和间隔

此功能需要Swoole

使用 Swoole 时,您可以注册“tick”操作,这些操作将每隔指定的秒数执行一次。您可以通过tick方法来注册“tick”回调。传递给该方法的第一个参数tick应该是一个字符串,表示 ticker 的名称。第二个参数应该是一个可调用函数,它将以指定的间隔调用。

在这个例子中,我们将注册一个每 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 Cache

此功能需要Swoole

使用 Swoole 时,您可以利用 Octane 缓存驱动程序,它提供高达每秒 200 万次操作的读写速度。因此,对于需要极快缓存层读写速度的应用程序来说,此缓存驱动程序是绝佳选择。

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

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 表提供极致的性能吞吐量,并且这些表中的数据可以被服务器上的所有 Worker 访问。但是,服务器重启后,其中的数据将会丢失。

tables表格应该在应用程序配置文件的配置数组中定义octane。我们已为您配置了一个最多允许 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 表支持的列类型有:stringint、 和float