Skip to content

Laravel Octane

简介

Laravel Octane 通过使用高性能应用服务器来提升你的应用程序性能,包括 FrankenPHPOpen SwooleSwooleRoadRunner。Octane 会一次性启动你的应用程序,将其保存在内存中,然后以超音速的速度处理请求。

安装

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

shell
composer require laravel/octane

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

shell
php artisan octane:install

服务器先决条件

FrankenPHP

FrankenPHP 是一个用 Go 编写的 PHP 应用服务器,支持 early hints、Brotli 和 Zstandard 压缩等现代 Web 功能。当你安装 Octane 并选择 FrankenPHP 作为服务器时,Octane 会自动为你下载并安装 FrankenPHP 二进制文件。

通过 Laravel Sail 使用 FrankenPHP

如果你计划使用 Laravel Sail 开发应用程序,你应该运行以下命令来安装 Octane 和 FrankenPHP:

shell
./vendor/bin/sail up

./vendor/bin/sail composer require laravel/octane

接下来,你应该使用 octane:install Artisan 命令来安装 FrankenPHP 二进制文件:

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

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

yaml
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}'" # [tl! add]
      XDG_CONFIG_HOME:  /var/www/html/config # [tl! add]
      XDG_DATA_HOME:  /var/www/html/data # [tl! add]

要启用 HTTPS、HTTP/2 和 HTTP/3,请改为应用以下修改:

yaml
services:
  laravel.test:
    ports:
        - '${APP_PORT:-80}:80'
        - '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
        - '443:443' # [tl! add]
        - '443:443/udp' # [tl! add]
    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" # [tl! add]
      XDG_CONFIG_HOME:  /var/www/html/config # [tl! add]
      XDG_DATA_HOME:  /var/www/html/data # [tl! add]

通常,你应该通过 https://localhost 访问你的 FrankenPHP Sail 应用程序,因为使用 https://127.0.0.1 需要额外配置且不推荐

通过 Docker 使用 FrankenPHP

使用 FrankenPHP 的官方 Docker 镜像可以提供更好的性能,并使用静态安装的 FrankenPHP 中未包含的额外扩展。此外,官方 Docker 镜像支持在 FrankenPHP 原生不支持的平台(如 Windows)上运行。FrankenPHP 的官方 Docker 镜像适用于本地开发和生产使用。

你可以使用以下 Dockerfile 作为容器化 FrankenPHP 驱动的 Laravel 应用程序的起点:

dockerfile
FROM dunglas/frankenphp

RUN install-php-extensions \
    pcntl
    # 在此添加其他 PHP 扩展...

COPY . /app

ENTRYPOINT ["php", "artisan", "octane:frankenphp"]

然后,在开发期间,你可以使用以下 Docker Compose 文件来运行你的应用程序:

yaml
# 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 的更多信息。

自定义 Caddyfile 配置

使用 FrankenPHP 时,你可以在启动 Octane 时使用 --caddyfile 选项指定自定义的 Caddyfile:

shell
php 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:

shell
./vendor/bin/sail up

./vendor/bin/sail composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http

接下来,你应该启动 Sail shell 并使用 rr 可执行文件来获取最新的基于 Linux 的 RoadRunner 二进制文件:

shell
./vendor/bin/sail shell

# 在 Sail shell 中...
./vendor/bin/rr get-binary

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

yaml
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}'" # [tl! add]

最后,确保 rr 二进制文件可执行并构建你的 Sail 镜像:

shell
chmod +x ./rr

./vendor/bin/sail build --no-cache

Swoole

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

shell
pecl install swoole

Open Swoole

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

shell
pecl install openswoole

将 Laravel Octane 与 Open Swoole 一起使用可以获得与 Swoole 相同的功能,例如并发任务、ticks 和 intervals。

通过 Laravel Sail 使用 Swoole

WARNING

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

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

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

yaml
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}'" # [tl! add]

最后,构建你的 Sail 镜像:

shell
./vendor/bin/sail build --no-cache

Swoole 配置

Swoole 支持一些额外的配置选项,如有必要,你可以将其添加到 octane 配置文件中。因为它们很少需要修改,所以这些选项未包含在默认配置文件中:

php
'swoole' => [
    'options' => [
        'log_file' => storage_path('logs/swoole_http.log'),
        'package_max_length' => 10 * 1024 * 1024,
    ],
],

服务你的应用程序

Octane 服务器可以通过 octane:start Artisan 命令启动。默认情况下,此命令将使用应用程序 octane 配置文件的 server 配置选项指定的服务器:

shell
php artisan octane:start

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

在生产环境中保持 Octane 运行

如果你要将 Octane 应用程序部署到生产环境,你应该使用进程监控器(如 Supervisor)来确保 Octane 服务器保持运行。Octane 的示例 Supervisor 配置文件可能如下所示:

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

通过 HTTPS 服务你的应用程序

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

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

通过 Nginx 服务你的应用程序

NOTE

如果你还没有准备好管理自己的服务器配置,或者不习惯配置运行健壮的 Laravel Octane 应用程序所需的各种服务,请查看 Laravel Cloud,它提供完全托管的 Laravel Octane 支持。

在生产环境中,你应该在传统的 Web 服务器(如 Nginx 或 Apache)后面服务你的 Octane 应用程序。这样做将允许 Web 服务器提供静态资源(如图片和样式表),以及管理 SSL 证书终止。

在下面的 Nginx 配置示例中,Nginx 将提供站点的静态资源并将请求代理到运行在端口 8000 上的 Octane 服务器:

nginx
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 在应用程序内的任何文件更改时自动重启服务器:

shell
php artisan octane:start --watch

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

shell
npm install --save-dev chokidar

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

指定 Worker 数量

默认情况下,Octane 会为你机器提供的每个 CPU 核心启动一个应用程序请求 worker。然后,这些 worker 将用于在传入的 HTTP 请求进入你的应用程序时提供服务。你可以在调用 octane:start 命令时使用 --workers 选项手动指定要启动多少个 worker:

shell
php artisan octane:start --workers=4

如果你使用 Swoole 应用服务器,你还可以指定要启动多少个「任务 worker」

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

指定最大请求数

为了帮助防止零散的内存泄漏,Octane 会在 worker 处理 500 个请求后优雅地重启它。要调整此数字,你可以使用 --max-requests 选项:

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

指定最大执行时间

默认情况下,Laravel Octane 通过应用程序 config/octane.php 配置文件中的 max_execution_time 选项为传入请求设置 30 秒的最大执行时间:

php
'max_execution_time' => 30,

此设置定义了允许传入请求执行的最大秒数,超过此时间将被终止。将此值设置为 0 将完全禁用执行时间限制。此配置选项对于处理长时间运行请求的应用程序特别有用,例如文件上传、数据处理或对外部服务的 API 调用。

WARNING

修改 max_execution_time 配置时,你必须重启 Octane 服务器才能使更改生效。

重新加载 Workers

你可以使用 octane:reload 命令优雅地重启 Octane 服务器的应用程序 worker。通常,这应该在部署后完成,以便你新部署的代码被加载到内存中并用于服务后续请求:

shell
php artisan octane:reload

停止服务器

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

shell
php artisan octane:stop

检查服务器状态

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

shell
php artisan octane:status

依赖注入和 Octane

由于 Octane 会一次性启动你的应用程序并在服务请求时将其保存在内存中,因此在构建应用程序时需要考虑一些注意事项。例如,应用程序服务提供者的 registerboot 方法仅在请求 worker 最初启动时执行一次。在后续请求中,将重用相同的应用程序实例。

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

Octane 会自动处理在请求之间重置任何第一方框架状态。但是,Octane 并不总是知道如何重置你的应用程序创建的全局状态。因此,你应该了解如何以对 Octane 友好的方式构建你的应用程序。下面,我们将讨论使用 Octane 时可能导致问题的最常见情况。

容器注入

通常,你应该避免将应用程序服务容器或 HTTP 请求实例注入到其他对象的构造函数中。例如,以下绑定将整个应用程序服务容器注入到一个作为单例绑定的对象中:

php
use App\Service;
use Illuminate\Contracts\Foundation\Application;

/**
 * 注册任何应用程序服务。
 */
public function register(): void
{
    $this->app->singleton(Service::class, function (Application $app) {
        return new Service($app);
    });
}

在此示例中,如果 Service 实例在应用程序启动过程中被解析,容器将被注入到服务中,并且同一个容器将在后续请求中被 Service 实例持有。这对你的特定应用程序可能不是问题;但是,它可能导致容器意外缺少在启动周期后期或后续请求中添加的绑定。

作为解决方法,你可以停止将绑定注册为单例,或者你可以向服务注入一个容器解析器闭包,该闭包始终解析当前的容器实例:

php
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 请求实例注入到其他对象的构造函数中。例如,以下绑定将整个请求实例注入到一个作为单例绑定的对象中:

php
use App\Service;
use Illuminate\Contracts\Foundation\Application;

/**
 * 注册任何应用程序服务。
 */
public function register(): void
{
    $this->app->singleton(Service::class, function (Application $app) {
        return new Service($app['request']);
    });
}

在此示例中,如果 Service 实例在应用程序启动过程中被解析,HTTP 请求将被注入到服务中,并且同一个请求将在后续请求中被 Service 实例持有。因此,所有头信息、输入和查询字符串数据都将是不正确的,以及所有其他请求数据。

作为解决方法,你可以停止将绑定注册为单例,或者你可以向服务注入一个请求解析器闭包,该闭包始终解析当前的请求实例。或者,最推荐的方法是简单地在运行时将对象需要的特定请求信息传递给对象的某个方法:

php
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']);
});

// 或者...

$service->method($request->input('name'));

全局 request 辅助函数将始终返回应用程序当前正在处理的请求,因此可以安全地在应用程序中使用。

WARNING

在控制器方法和路由闭包中类型提示 Illuminate\Http\Request 实例是可以接受的。

配置仓库注入

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

php
use App\Service;
use Illuminate\Contracts\Foundation\Application;

/**
 * 注册任何应用程序服务。
 */
public function register(): void
{
    $this->app->singleton(Service::class, function (Application $app) {
        return new Service($app->make('config'));
    });
}

在此示例中,如果配置值在请求之间发生更改,该服务将无法访问新值,因为它依赖于原始仓库实例。

作为解决方法,你可以停止将绑定注册为单例,或者你可以向类注入一个配置仓库解析器闭包:

php
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 数组添加数据:

php
use App\Service;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

/**
 * 处理传入请求。
 */
public function index(Request $request): array
{
    Service::$data[] = Str::random(10);

    return [
        // ...
    ];
}

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

并发任务

WARNING

此功能需要 Swoole

使用 Swoole 时,你可以通过轻量级后台任务并发执行操作。你可以使用 Octane 的 concurrently 方法来实现。你可以将此方法与 PHP 数组解构结合使用来获取每个操作的结果:

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

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

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

Ticks 和 Intervals

WARNING

此功能需要 Swoole

使用 Swoole 时,你可以注册将在每个指定秒数执行的「tick」操作。你可以通过 tick 方法注册「tick」回调。提供给 tick 方法的第一个参数应该是一个表示 ticker 名称的字符串。第二个参数应该是一个将在指定间隔被调用的可调用对象。

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

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

使用 immediate 方法,你可以指示 Octane 在 Octane 服务器最初启动时立即调用 tick 回调,之后每 N 秒调用一次:

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

Octane 缓存

WARNING

此功能需要 Swoole

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

此缓存驱动程序由 Swoole tables 驱动。存储在缓存中的所有数据对服务器上的所有 worker 都可用。但是,当服务器重启时,缓存数据将被清除:

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

NOTE

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

缓存间隔

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

php
use Illuminate\Support\Str;

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

Tables

WARNING

此功能需要 Swoole

使用 Swoole 时,你可以定义和交互你自己的任意 Swoole tables。Swoole tables 提供极高的性能吞吐量,服务器上的所有 worker 都可以访问这些表中的数据。但是,当服务器重启时,其中的数据将丢失。

Tables 应该在应用程序的 octane 配置文件的 tables 配置数组中定义。一个允许最多 1000 行的示例表已经为你配置好了。字符串列的最大大小可以通过在列类型后指定列大小来配置,如下所示:

php
'tables' => [
    'example:1000' => [
        'name' => 'string:1000',
        'votes' => 'int',
    ],
],

要访问表,你可以使用 Octane::table 方法:

php
use Laravel\Octane\Facades\Octane;

Octane::table('example')->set('uuid', [
    'name' => 'Nuno Maduro',
    'votes' => 1000,
]);

return Octane::table('example')->get('uuid');

WARNING

Swoole tables 支持的列类型为:stringintfloat