Skip to content
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

Laravel Octane

简介

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

安装

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

shell
composer require laravel/octane

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

shell
php artisan octane:install

服务器前提条件

FrankenPHP

FrankenPHP 是一个用 Go 编写的 PHP 应用服务器,支持现代 Web 功能,如 Early Hints、Brotli 和 Zstandard 压缩。当你安装 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}'" 
      XDG_CONFIG_HOME:  /var/www/html/config 
      XDG_DATA_HOME:  /var/www/html/data 

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

yaml
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://localhost 访问你的 FrankenPHP Sail 应用程序,因为使用 https://127.0.0.1 需要额外配置,并且不推荐

通过 Docker 使用 FrankenPHP

使用 FrankenPHP 的官方 Docker 镜像可以提供更好的性能,并可以使用静态安装的 FrankenPHP 未包含的额外扩展。此外,官方 Docker 镜像支持在 FrankenPHP 本身不支持的平台上运行 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}'" 

最后,确保 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 提供的相同功能,例如并发任务、定时器和间隔。

通过 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}'" 

最后,构建你的 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 端口上启动服务器,因此你可以通过 Web 浏览器在 http://localhost:8000 访问你的应用程序。

在生产环境中保持 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:// 为前缀。当通过 HTTPS 服务你的应用程序时,可以在应用程序的 config/octane.php 配置文件中使用的 OCTANE_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 配置选项来配置应监听的目录和文件。

指定工作进程数量

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

shell
php artisan octane:start --workers=4

如果你使用的是 Swoole 应用服务器,你还可以指定要启动多少个"任务工作进程"

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

指定最大请求数

为了帮助防止散逸的内存泄漏,Octane 在任何工作进程处理了 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 服务器才能使更改生效。

重新加载工作进程

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

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 方法仅在请求工作进程初始启动时执行一次。在后续请求中,相同的应用程序实例将被重用。

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

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

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

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

定时器和间隔

WARNING

此功能需要 Swoole

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

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

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

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

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

Octane 缓存

WARNING

此功能需要 Swoole

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

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

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

表格

WARNING

此功能需要 Swoole

使用 Swoole 时,你可以定义并与你自己的任意 Swoole 表交互。Swoole 表提供极高的性能吞吐量,并且这些表中的数据可以被服务器上的所有工作进程访问。但是,当服务器重启时,它们中的数据将会丢失。

表格应该在你的应用程序的 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 表支持的列类型有:stringintfloat