Laravel Octane
简介
Laravel Octane 通过使用高性能应用服务器(包括 FrankenPHP、Open Swoole、Swoole 和 RoadRunner)服务您的应用,从而极大地提升应用性能。Octane 只需启动您的应用一次并将其保存在内存中,然后以超音速的速度将请求馈送给它。
安装
Octane 可以通过 Composer 包管理器安装
1composer require laravel/octane
安装 Octane 后,您可以执行 octane:install
Artisan 命令,这会将 Octane 的配置文件安装到您的应用中
1php 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 up2 3./vendor/bin/sail composer require laravel/octane
接下来,您应该使用 octane:install
Artisan 命令来安装 FrankenPHP 二进制文件
1./vendor/bin/sail artisan octane:install --server=frankenphp
最后,在您的应用的 docker-compose.yml
文件中,将 SUPERVISOR_PHP_COMMAND
环境变量添加到 laravel.test
服务定义中。此环境变量将包含 Sail 将用于使用 Octane 而不是 PHP 开发服务器来服务您的应用的命令
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
通常,您应该通过 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 应用的起点
1FROM dunglas/frankenphp2 3RUN install-php-extensions \4 pcntl5 # Add other PHP extensions here...6 7COPY . /app8 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 由 RoadRunner 二进制文件驱动,该二进制文件使用 Go 构建。首次启动基于 RoadRunner 的 Octane 服务器时,Octane 将提示您下载并安装 RoadRunner 二进制文件。
通过 Laravel Sail 使用 RoadRunner
如果您计划使用 Laravel Sail 开发您的应用,您应该运行以下命令来安装 Octane 和 RoadRunner
1./vendor/bin/sail up2 3./vendor/bin/sail composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http
接下来,您应该启动 Sail shell 并使用 rr
可执行文件来检索最新的基于 Linux 的 RoadRunner 二进制文件构建版本
1./vendor/bin/sail shell2 3# Within the Sail shell...4./vendor/bin/rr get-binary
然后,在您的应用的 docker-compose.yml
文件中,将 SUPERVISOR_PHP_COMMAND
环境变量添加到 laravel.test
服务定义中。此环境变量将包含 Sail 将用于使用 Octane 而不是 PHP 开发服务器来服务您的应用的命令
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 ./rr2 3./vendor/bin/sail build --no-cache
Swoole
如果您计划使用 Swoole 应用服务器来服务您的 Laravel Octane 应用,您必须安装 Swoole PHP 扩展。通常,可以通过 PECL 完成此操作
1pecl install swoole
Open Swoole
如果您想使用 Open Swoole 应用服务器来服务您的 Laravel Octane 应用,您必须安装 Open Swoole PHP 扩展。通常,可以通过 PECL 完成此操作
1pecl install openswoole
将 Laravel Octane 与 Open Swoole 结合使用,可以获得与 Swoole 相同的功能,例如并发任务、ticks 和 intervals。
通过 Laravel Sail 使用 Swoole
在通过 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
文件中,将 SUPERVISOR_PHP_COMMAND
环境变量添加到 laravel.test
服务定义中。此环境变量将包含 Sail 将用于使用 Octane 而不是 PHP 开发服务器来服务您的应用的命令
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:start
Artisan 命令启动 Octane 服务器。默认情况下,此命令将使用您应用的 octane
配置文件的 server
配置选项指定的服务器
1php artisan octane:start
默认情况下,Octane 将在端口 8000 上启动服务器,因此您可以通过 https://127.0.0.1:8000
在 Web 浏览器中访问您的应用。
通过 HTTPS 服务您的应用
默认情况下,通过 Octane 运行的应用生成的链接以 http://
为前缀。当通过 HTTPS 服务您的应用时,可以在您的应用的 config/octane.php
配置文件中使用的 OCTANE_HTTPS
环境变量可以设置为 true
。当此配置值设置为 true
时,Octane 将指示 Laravel 将所有生成的链接都以 https://
为前缀
1'https' => env('OCTANE_HTTPS', false),
通过 Nginx 服务您的应用
如果您尚未准备好管理自己的服务器配置,或者不习惯配置运行强大的 Laravel Octane 应用所需的所有各种服务,请查看 Laravel Cloud,它提供完全托管的 Laravel Octane 支持。
在生产环境中,您应该在传统的 Web 服务器(例如 Nginx 或 Apache)后面服务您的 Octane 应用。这样做将允许 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
您可以使用应用的 config/octane.php
配置文件中的 watch
配置选项来配置应监视的目录和文件。
指定 Worker 数量
默认情况下,Octane 将为您的机器提供的每个 CPU 核心启动一个应用请求 worker。然后,这些 worker 将用于在传入的 HTTP 请求进入您的应用时对其进行服务。您可以在调用 octane:start
命令时使用 --workers
选项手动指定要启动的 worker 数量
1php artisan octane:start --workers=4
如果您使用的是 Swoole 应用服务器,您还可以指定要启动的 “任务 worker” 的数量
1php artisan octane:start --workers=4 --task-workers=6
指定最大请求数
为了帮助防止意外的内存泄漏,Octane 会在 worker 处理 500 个请求后优雅地重启它。要调整此数字,您可以使用 --max-requests
选项
1php artisan octane:start --max-requests=250
重新加载 Workers
您可以使用 octane:reload
命令优雅地重启 Octane 服务器的应用 workers。通常,这应该在部署后完成,以便将您新部署的代码加载到内存中,并用于为后续请求提供服务
1php artisan octane:reload
停止服务器
您可以使用 octane:stop
Artisan 命令停止 Octane 服务器
1php artisan octane:stop
检查服务器状态
您可以使用 octane:status
Artisan 命令检查 Octane 服务器的当前状态
1php artisan octane:status
依赖注入和 Octane
由于 Octane 只启动您的应用一次并将其保存在内存中以服务请求,因此在构建应用时,您应考虑一些注意事项。例如,您的应用的服务提供者的 register
和 boot
方法仅在请求 worker 首次启动时执行一次。在后续请求中,将重用相同的应用实例。
鉴于此,在将应用服务容器或请求注入到任何对象的构造函数中时,您应格外小心。这样做可能会导致该对象在后续请求中具有过时的容器或请求版本。
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'));
全局 request
助手函数将始终返回应用当前正在处理的请求,因此在您的应用中可以安全使用。
在您的控制器方法和路由闭包中类型提示 Illuminate\Http\Request
实例是可以接受的。
配置仓库注入
一般来说,您应该避免将配置仓库实例注入到其他对象的构造函数中。例如,以下绑定将配置仓库注入到作为单例绑定的对象中
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 的“任务 worker”,并在与传入请求完全不同的进程中执行。可用于处理并发任务的 worker 数量由 octane:start
命令上的 --task-workers
指令确定
1php artisan octane:start --workers=4 --task-workers=6
调用 concurrently
方法时,由于 Swoole 任务系统的限制,您不应提供超过 1024 个任务。
Ticks 和 Intervals
此功能需要 Swoole。
使用 Swoole 时,您可以注册“tick”操作,这些操作将每隔指定的秒数执行一次。您可以通过 tick
方法注册“tick”回调。提供给 tick
方法的第一个参数应是一个字符串,表示 ticker 的名称。第二个参数应是一个可调用对象,它将在指定的时间间隔被调用。
在此示例中,我们将注册一个闭包,每 10 秒调用一次。通常,应在您的应用的服务提供者之一的 boot
方法中调用 tick
方法
1Octane::tick('simple-ticker', fn () => ray('Ticking...'))2 ->seconds(10);
使用 immediate
方法,您可以指示 Octane 在 Octane 服务器首次启动时以及之后每 N 秒立即调用 tick 回调
1Octane::tick('simple-ticker', fn () => ray('Ticking...'))2 ->seconds(10)3 ->immediate();
Octane 缓存
此功能需要 Swoole。
使用 Swoole 时,您可以利用 Octane 缓存驱动程序,该驱动程序提供高达每秒 200 万次操作的读写速度。因此,对于需要从其缓存层获得极高读/写速度的应用来说,此缓存驱动程序是一个绝佳的选择。
此缓存驱动程序由 Swoole tables 提供支持。缓存在缓存中的所有数据都可供服务器上的所有 worker 使用。但是,当服务器重新启动时,缓存的数据将被刷新
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 都可以访问这些表中的数据。但是,当服务器重新启动时,其中的数据将丢失。
表应在您的应用的 octane
配置文件的 tables
配置数组中定义。已经为您配置了一个允许最多 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 表支持的列类型为:string
、int
和 float
。