Laravel Octane
简介
Laravel Octane 通过高性能应用服务器为你的应用提速,支持 FrankenPHP、Open Swoole、Swoole 和 RoadRunner。Octane 只需启动应用一次,将其常驻内存,然后以极高速度处理请求。
安装
可通过 Composer 安装 Octane:
composer require laravel/octane安装 Octane 后,可运行 octane:install Artisan 命令,将 Octane 配置文件安装到应用中:
php artisan octane:install服务器前置要求
WARNING
Laravel Octane 需要 PHP 8.1+。
FrankenPHP
FrankenPHP 是用 Go 编写的 PHP 应用服务器,支持 early hints、Brotli 和 Zstandard 压缩等现代 Web 特性。安装 Octane 并选择 FrankenPHP 作为服务器时,Octane 会自动下载并安装 FrankenPHP 二进制文件。
通过 Laravel Sail 使用 FrankenPHP
若计划使用 Laravel Sail 开发应用,请运行以下命令安装 Octane 和 FrankenPHP:
./vendor/bin/sail up
./vendor/bin/sail composer require laravel/octane接下来,使用 octane:install Artisan 命令安装 FrankenPHP 二进制文件:
./vendor/bin/sail artisan octane:install --server=frankenphp最后,在应用的 docker-compose.yml 中为 laravel.test 服务添加 SUPERVISOR_PHP_COMMAND 环境变量。该变量包含 Sail 用于通过 Octane(而非 PHP 开发服务器)提供应用的命令:
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,请改用以下配置:
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 原生不支持的平台(如 Windows)上运行,适用于本地开发和生产环境。
以下 Dockerfile 可作为将 FrankenPHP Laravel 应用容器化的起点:
FROM dunglas/frankenphp
RUN install-php-extensions \
pcntl
# Add other PHP extensions here...
COPY . /app
ENTRYPOINT ["php", "artisan", "octane:frankenphp"]开发时,可使用以下 Docker Compose 文件运行应用:
# compose.yaml
services:
frankenphp:
build:
context: .
entrypoint: php artisan octane:frankenphp --workers=1 --max-requests=1
ports:
- "8000:8000"
volumes:
- .:/app若向 php artisan octane:start 显式传入 --log-level 选项,Octane 会使用 FrankenPHP 原生日志器,除非另行配置,否则会输出结构化 JSON 日志。
有关使用 Docker 运行 FrankenPHP 的更多信息,请参阅 FrankenPHP 官方文档。
RoadRunner
RoadRunner 由 Go 构建的 RoadRunner 二进制驱动。首次启动基于 RoadRunner 的 Octane 服务器时,Octane 会提示下载并安装 RoadRunner 二进制文件。
通过 Laravel Sail 使用 RoadRunner
若计划使用 Laravel Sail 开发应用,请运行以下命令安装 Octane 和 RoadRunner:
./vendor/bin/sail up
./vendor/bin/sail composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http接下来,启动 Sail shell,并使用 rr 可执行文件获取最新 Linux 版 RoadRunner 二进制:
./vendor/bin/sail shell
# Within the Sail shell...
./vendor/bin/rr get-binary然后,在应用的 docker-compose.yml 中为 laravel.test 服务添加 SUPERVISOR_PHP_COMMAND 环境变量:
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 镜像:
chmod +x ./rr
./vendor/bin/sail build --no-cacheSwoole
若计划使用 Swoole 应用服务器运行 Laravel Octane 应用,必须安装 Swoole PHP 扩展。通常可通过 PECL 安装:
pecl install swooleOpen Swoole
若计划使用 Open Swoole 应用服务器,必须安装 Open Swoole PHP 扩展。通常可通过 PECL 安装:
pecl install openswooleLaravel Octane 配合 Open Swoole 提供与 Swoole 相同的功能,例如并发任务、tick 和 interval。
通过 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 环境变量:
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 镜像:
./vendor/bin/sail build --no-cacheSwoole 配置
Swoole 支持一些额外配置选项,必要时可添加到 octane 配置文件。由于很少需要修改,默认配置文件中未包含这些选项:
'swoole' => [
'options' => [
'log_file' => storage_path('logs/swoole_http.log'),
'package_max_length' => 10 * 1024 * 1024,
],
],运行应用
可通过 octane:start Artisan 命令启动 Octane 服务器。默认情况下,该命令使用应用 octane 配置文件中 server 选项指定的服务器:
php artisan octane:start默认情况下,Octane 在 8000 端口启动服务器,可通过 http://localhost:8000 在浏览器中访问应用。
通过 HTTPS 运行应用
默认情况下,通过 Octane 运行的应用生成的链接以 http:// 为前缀。在 config/octane.php 中使用的 OCTANE_HTTPS 环境变量可在通过 HTTPS 提供应用时设为 true。设为 true 后,Octane 会指示 Laravel 将所有生成的链接前缀为 https://:
'https' => env('OCTANE_HTTPS', false),通过 Nginx 运行应用
INFO
若尚未准备好自行管理服务器配置,或不熟悉运行稳健 Laravel Octane 应用所需的各项服务,可了解 Laravel Forge。
在生产环境中,应在 Nginx 或 Apache 等传统 Web 服务器后运行 Octane 应用。这样 Web 服务器可处理图片、样式表等静态资源,并管理 SSL 证书终止。
以下 Nginx 配置示例中,Nginx 提供站点静态资源,并将请求代理到运行在 8000 端口的 Octane 服务器:
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 在应用内文件变更时自动重启服务器:
php artisan octane:start --watchnpm install --save-dev chokidar可在应用 config/octane.php 的 watch 配置选项中指定要监听的目录和文件。
指定 Worker 数量
默认情况下,Octane 会为机器的每个 CPU 核心启动一个应用请求 worker。这些 worker 用于处理进入应用的 HTTP 请求。调用 octane:start 时,可通过 --workers 选项手动指定 worker 数量:
php artisan octane:start --workers=4若使用 Swoole 应用服务器,还可指定要启动的 "task worker" 数量:
php artisan octane:start --workers=4 --task-workers=6指定最大请求数
为帮助防止内存泄漏,Octane 会在 worker 处理 500 个请求后优雅重启。可通过 --max-requests 选项调整此数值:
php artisan octane:start --max-requests=250重载 Worker
可使用 octane:reload 命令优雅重启 Octane 服务器的应用 worker。通常应在部署后执行,以便新代码加载到内存并用于后续请求:
php artisan octane:reload停止服务器
可使用 octane:stop Artisan 命令停止 Octane 服务器:
php artisan octane:stop检查服务器状态
可使用 octane:status Artisan 命令检查 Octane 服务器当前状态:
php artisan octane:status依赖注入与 Octane
由于 Octane 启动应用一次并在处理请求时将其保留在内存中,构建应用时需注意一些事项。例如,应用服务提供者的 register 和 boot 方法仅在请求 worker 首次启动时执行一次,后续请求会复用同一应用实例。
因此,向对象构造函数注入应用服务容器或请求时需特别谨慎,否则该对象在后续请求中可能持有过时的容器或请求实例。
Octane 会自动在请求之间重置框架自身的状态,但并不总能重置应用创建的全局状态。因此,需要了解如何以 Octane 友好的方式构建应用。下面讨论使用 Octane 时最常见的潜在问题。
容器注入
通常应避免向其他对象的构造函数注入应用服务容器或 HTTP 请求实例。例如,以下绑定将整个应用服务容器注入绑定为单例的对象:
use App\Service;
use Illuminate\Contracts\Foundation\Application;
/**
* Register any application services.
*/
public function register(): void
{
$this->app->singleton(Service::class, function (Application $app) {
return new Service($app);
});
}在此示例中,若 Service 实例在应用启动过程中被解析,容器会被注入该服务,并在后续请求中由 Service 实例持有同一容器。这对你的应用可能不是问题,但可能导致容器意外缺少在启动周期后期或后续请求中添加的绑定。
作为变通方案,可以不再将绑定注册为单例,或向服务注入始终解析当前容器实例的容器解析器闭包:
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 请求实例。例如,以下绑定将整个请求实例注入绑定为单例的对象:
use App\Service;
use Illuminate\Contracts\Foundation\Application;
/**
* Register any application services.
*/
public function register(): void
{
$this->app->singleton(Service::class, function (Application $app) {
return new Service($app['request']);
});
}在此示例中,若 Service 实例在应用启动过程中被解析,HTTP 请求会被注入该服务,并在后续请求中由 Service 实例持有同一请求。因此,所有请求头、输入和查询字符串数据以及其他请求数据都会不正确。
作为变通方案,可以不再将绑定注册为单例,或向服务注入始终解析当前请求实例的请求解析器闭包。更推荐的做法是在运行时向对象方法传入所需的特定请求信息:
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']);
});
// Or...
$service->method($request->input('name'));全局 request 辅助函数始终返回应用当前正在处理的请求,因此在应用中使用是安全的。
WARNING
在控制器方法和路由闭包中对 Illuminate\Http\Request 实例进行类型提示是可以接受的。
配置仓库注入
通常应避免向其他对象的构造函数注入配置仓库实例。例如,以下绑定将配置仓库注入绑定为单例的对象:
use App\Service;
use Illuminate\Contracts\Foundation\Application;
/**
* Register any application services.
*/
public function register(): void
{
$this->app->singleton(Service::class, function (Application $app) {
return new Service($app->make('config'));
});
}在此示例中,若配置值在请求之间发生变化,该服务将无法访问新值,因为它依赖原始仓库实例。
作为变通方案,可以不再将绑定注册为单例,或向类注入配置仓库解析器闭包:
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 数组添加数据:
use App\Service;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
/**
* Handle an incoming request.
*/
public function index(Request $request): array
{
Service::$data[] = Str::random(10);
return [
// ...
];
}构建应用时,应特别注意避免此类内存泄漏。建议在本地开发期间监控应用内存使用情况,确保未引入新的内存泄漏。
并发任务
WARNING
此功能需要 Swoole。
使用 Swoole 时,可通过轻量级后台任务并发执行操作。可使用 Octane 的 concurrently 方法,并结合 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 的「task worker」,在与传入请求完全不同的进程中执行。可用于处理并发任务的 worker 数量由 octane:start 命令的 --task-workers 选项决定:
php artisan octane:start --workers=4 --task-workers=6调用 concurrently 方法时,由于 Swoole 任务系统的限制,不应提供超过 1024 个任务。
Tick 与 Interval
WARNING
此功能需要 Swoole。
使用 Swoole 时,可注册每隔指定秒数执行的「tick」操作。可通过 tick 方法注册 tick 回调。tick 方法的第一个参数应为表示 ticker 名称的字符串,第二个参数应为在指定间隔调用的可调用对象。
以下示例注册一个每 10 秒调用一次的闭包。通常应在应用某个服务提供者的 boot 方法中调用 tick 方法:
Octane::tick('simple-ticker', fn () => ray('Ticking...'))
->seconds(10);使用 immediate 方法,可指示 Octane 在服务器首次启动时立即调用 tick 回调,此后每 N 秒调用一次:
Octane::tick('simple-ticker', fn () => ray('Ticking...'))
->seconds(10)
->immediate();Octane 缓存
WARNING
此功能需要 Swoole。
使用 Swoole 时,可利用 Octane 缓存驱动,读写速度可达每秒 200 万次操作,非常适合需要极高缓存读写速度的应用。
该缓存驱动由 Swoole table 驱动。缓存中的所有数据对服务器上的所有 worker 可用,但服务器重启时缓存数据会被清空:
Cache::store('octane')->put('framework', 'Laravel', 30);INFO
Octane 缓存允许的最大条目数可在应用的 octane 配置文件中定义。
缓存 Interval
除 Laravel 缓存系统提供的常规方法外,Octane 缓存驱动还支持基于 interval 的缓存。这些缓存会在指定间隔自动刷新,应在应用某个服务提供者的 boot 方法中注册。例如,以下缓存每 5 秒刷新一次:
use Illuminate\Support\Str;
Cache::store('octane')->interval('random', function () {
return Str::random(10);
}, seconds: 5);Table
WARNING
此功能需要 Swoole。
使用 Swoole 时,可定义并操作自定义 Swoole table。Swoole table 提供极高吞吐性能,表中数据可被服务器上所有 worker 访问,但服务器重启后数据会丢失。
Table 应在应用 octane 配置文件的 tables 配置数组中定义。已为你配置一个最多 1000 行的示例 table。字符串列的最大长度可在列类型后指定列大小,如下所示:
'tables' => [
'example:1000' => [
'name' => 'string:1000',
'votes' => 'int',
],
],要访问 table,可使用 Octane::table 方法:
use Laravel\Octane\Facades\Octane;
Octane::table('example')->set('uuid', [
'name' => 'Nuno Maduro',
'votes' => 1000,
]);
return Octane::table('example')->get('uuid');WARNING
Swoole table 支持的列类型为:string、int 和 float。