介绍

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

安装

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

  1. composer require laravel/octane

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

  1. php artisan octane:install

服务器先决条件


警告
Laravel Octane 需要 PHP 8.1+

FrankenPHP

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

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

  1. ./vendor/bin/sail up
  2. ./vendor/bin/sail composer require laravel/octane

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

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

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

  1. services:
  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=80" # [tl! add]
  5. XDG_CONFIG_HOME: /var/www/html/config # [tl! add]
  6. XDG_DATA_HOME: /var/www/html/data # [tl! add]

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

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

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

通过 Docker 使用 FrankenPHP
使用 FrankenPHP 的官方 Docker 镜像可以提供更好的性能,并使用附带的额外扩展,这些扩展在静态安装的 FrankenPHP 中不包含。此外,官方 Docker 镜像支持在它本身不支持的平台上运行 FrankenPHP,比如 Windows。FrankenPHP 的官方 Docker 镜像适用于本地开发和生产用途。

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

  1. FROM dunglas/frankenphp
  2. RUN install-php-extensions \
  3. pcntl
  4. # 在这里添加其他 PHP 扩展...
  5. COPY . /app
  6. ENTRYPOINT ["php", "artisan", "octane:frankenphp"]

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

  1. # compose.yaml
  2. services:
  3. frankenphp:
  4. build:
  5. context: .
  6. entrypoint: php artisan octane:frankenphp --max-requests=1
  7. ports:
  8. - "8000:8000"
  9. volumes:
  10. - .:/app

你可以查阅 官方 FrankenPHP 文档,了解更多关于使用 Docker 运行 FrankenPHP 的信息。

RoadRunner

RoadRunner 使用由 Go 构建的 RoadRunner 二进制文件。第一次启动基于 RoadRunner 的 Octane 服务器时,Octane 会提供下载和安装 RoadRunner 二进制文件的选项。

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

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

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

  1. ./vendor/bin/sail shell
  2. # 在 Sail shell 中...
  3. ./vendor/bin/rr get-binary

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

  1. services:
  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=80" # [tl! add]

最后,确保 php rr 可执行并构建你的 Sail 镜像:

  1. chmod +x ./rr
  2. ./vendor/bin/sail build --no-cache

Swoole

如果你计划使用 Swoole 应用服务器为你的 Laravel Octane 应用提供服务,你必须安装 Swoole PHP 扩展。通常情况下,可以通过 PECL 完成:

  1. pecl install swoole

Open Swoole
如果你想使用 Open Swoole 应用服务器为你的 Laravel Octane 应用提供服务,你必须安装 Open Swoole PHP 扩展。通常情况下,可以通过 PECL 完成:

  1. pecl install openswoole

使用 Laravel Octane 与 Open Swoole 提供了与 Swoole 相同的功能,如并发任务、时钟周期和间隔。

通过 Laravel Sail 使用 Swoole


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


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

要开始,向你应用程序的 php docker-compose.yml 文件中的 php laravel.test 服务定义中添加一个 php SUPERVISOR_PHP_COMMAND 环境变量。这个环境变量将包含 Sail 用来使用 Octane 为你的应用提供服务的命令,而不是使用 PHP 开发服务器:

  1. services:
  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=80" # [tl! add]

最后,构建你的 Sail 镜像:

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

Swoole 配置
Swoole 支持一些额外的配置选项,如果需要,你可以将它们添加到你的 php octane 配置文件中。由于它们很少需要被修改,这些选项没有包含在默认配置文件中:

  1. 'swoole' => [
  2. 'options' => [
  3. 'log_file' => storage_path('logs/swoole_http.log'),
  4. 'package_max_length' => 10 * 1024 * 1024,
  5. ],
  6. ],

为你的应用提供服务

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

  1. php artisan octane:start

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

通过 HTTPS 提供你的应用程序

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

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

通过 Nginx 为你的应用提供服务


注意
如果你还没有准备好管理自己的服务器配置,或者不熟悉配置运行强大的 Laravel Octane 应用所需的各种服务,请查看 Laravel Forge


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

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

  1. map $http_upgrade $connection_upgrade {
  2. default upgrade;
  3. '' close;
  4. }
  5. server {
  6. listen 80;
  7. listen [::]:80;
  8. server_name domain.com;
  9. server_tokens off;
  10. root /home/forge/domain.com/public;
  11. index index.php;
  12. charset utf-8;
  13. location /index.php {
  14. try_files /not_exists @octane;
  15. }
  16. location / {
  17. try_files $uri $uri/ @octane;
  18. }
  19. location = /favicon.ico { access_log off; log_not_found off; }
  20. location = /robots.txt { access_log off; log_not_found off; }
  21. access_log off;
  22. error_log /var/log/nginx/domain.com-error.log error;
  23. error_page 404 /index.php;
  24. location @octane {
  25. set $suffix "";
  26. if ($uri = /index.php) {
  27. set $suffix ?$query_string;
  28. }
  29. proxy_http_version 1.1;
  30. proxy_set_header Host $http_host;
  31. proxy_set_header Scheme $scheme;
  32. proxy_set_header SERVER_PORT $server_port;
  33. proxy_set_header REMOTE_ADDR $remote_addr;
  34. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  35. proxy_set_header Upgrade $http_upgrade;
  36. proxy_set_header Connection $connection_upgrade;
  37. proxy_pass http://127.0.0.1:8000$suffix;
  38. }
  39. }

监视文件更改

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

  1. php artisan octane:start --watch

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

  1. npm install --save-dev chokidar

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

指定工作进程数量

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

  1. php artisan octane:start --workers=4

如果你正在使用 Swoole 应用服务器,还可以指定你希望启动多少个 任务工作进程:

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

指定最大请求计数

为了帮助防止内存泄漏,Octane 在处理 500 个请求后会优雅地重新启动任何工作进程。要调整此数字,你可以使用 php --max-requests 选项:

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

重新加载工作进程

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

  1. php artisan octane:reload

停止服务器

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

  1. php artisan octane:stop

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

  1. php artisan octane:status

依赖注入与 Octane

由于 Octane 一次启动你的应用程序并将其保存在内存中,同时为请求提供服务,因此在构建应用程序时应考虑一些注意事项。例如,当请求工作进程初始启动时,你的应用程序服务提供者的 php registerphp boot 方法只会执行一次。在后续请求中,将重用相同的应用程序实例。

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

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

容器注入

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

  1. use App\Service;
  2. use Illuminate\Contracts\Foundation\Application;
  3. /**
  4. * 注册任何应用程序服务。
  5. */
  6. public function register(): void
  7. {
  8. $this->app->singleton(Service::class, function (Application $app) {
  9. return new Service($app);
  10. });
  11. }

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

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

  1. use App\Service;
  2. use Illuminate\Container\Container;
  3. use Illuminate\Contracts\Foundation\Application;
  4. $this->app->bind(Service::class, function (Application $app) {
  5. return new Service($app);
  6. });
  7. $this->app->singleton(Service::class, function () {
  8. return new Service(fn () => Container::getInstance());
  9. });

全局的 php app 助手和 php Container::getInstance() 方法将始终返回应用程序容器的最新版本。

请求注入

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

  1. use App\Service;
  2. use Illuminate\Contracts\Foundation\Application;
  3. /**
  4. * 注册任何应用程序服务。
  5. */
  6. public function register(): void
  7. {
  8. $this->app->singleton(Service::class, function (Application $app) {
  9. return new Service($app['request']);
  10. });
  11. }

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

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

  1. use App\Service;
  2. use Illuminate\Contracts\Foundation\Application;
  3. $this->app->bind(Service::class, function (Application $app) {
  4. return new Service($app['request']);
  5. });
  6. $this->app->singleton(Service::class, function (Application $app) {
  7. return new Service(fn () => $app['request']);
  8. });
  9. // 或者...
  10. $service->method($request->input('name'));

全局的 php request 助手将始终返回应用程序当前处理的请求,因此在应用程序中使用它是安全的。


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

配置存储库注入

一般来说,你应该避免将配置存储库实例注入到其他对象的构造函数中。例如,以下绑定将配置存储库注入到一个被绑定为单例的对象中:

  1. use App\Service;
  2. use Illuminate\Contracts\Foundation\Application;
  3. /**
  4. * 注册任何应用程序服务。
  5. */
  6. public function register(): void
  7. {
  8. $this->app->singleton(Service::class, function (Application $app) {
  9. return new Service($app->make('config'));
  10. });
  11. }

在这个例子中,如果配置数值在请求之间发生变化,那么该服务将无法访问新值,因为它依赖于原始存储库实例。

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

  1. use App\Service;
  2. use Illuminate\Container\Container;
  3. use Illuminate\Contracts\Foundation\Application;
  4. $this->app->bind(Service::class, function (Application $app) {
  5. return new Service($app->make('config'));
  6. });
  7. $this->app->singleton(Service::class, function () {
  8. return new Service(fn () => Container::getInstance()->make('config'));
  9. });

全局的 php config 将始终返回配置存储库的最新版本,因此在应用程序中使用它是安全的。

管理内存泄漏

请记住,Octane 在请求之间保持应用程序在内存中;因此,向静态维护的数组添加数据将导致内存泄漏。例如,以下控制器存在内存泄漏,因为对应用程序的每个请求将继续向静态的 php $data 数组添加数据:

  1. use App\Service;
  2. use Illuminate\Http\Request;
  3. use Illuminate\Support\Str;
  4. /**
  5. * 处理传入请求。
  6. */
  7. public function index(Request $request): array
  8. {
  9. Service::$data[] = Str::random(10);
  10. return [
  11. // ...
  12. ];
  13. }

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

并发任务


警告
此功能需要 Swoole。


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

  1. use App\Models\User;
  2. use App\Models\Server;
  3. use Laravel\Octane\Facades\Octane;
  4. [$users, $servers] = Octane::concurrently([
  5. fn () => User::all(),
  6. fn () => Server::all(),
  7. ]);

由 Octane 处理的并发任务利用了 Swoole 的 “task workers”,并在与传入请求完全不同的进程中执行。用于处理并发任务的工作进程数量由 php octane:start 命令中的 php --task-workers 指令确定:

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

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

Ticks 和 Intervals


警告
此功能需要 Swoole。


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

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

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

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

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

Octane 缓存


警告
此功能需要 Swoole。


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

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

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


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

缓存间隔

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

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


警告
此功能需要 Swoole。


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

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

  1. 'tables' => [
  2. 'example:1000' => [
  3. 'name' => 'string:1000',
  4. 'votes' => 'int',
  5. ],
  6. ],

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

  1. use Laravel\Octane\Facades\Octane;
  2. Octane::table('example')->set('uuid', [
  3. 'name' => 'Nuno Maduro',
  4. 'votes' => 1000,
  5. ]);
  6. return Octane::table('example')->get('uuid');


警告
Swoole 表支持的列类型有:php stringphp intphp float