简介
Laravel Octane 借助高性能应用服务器(包括 FrankenPHP、Open Swoole、Swoole 以及 RoadRunner)来运行应用,从而大幅提升应用的性能。Octane 只需启动应用一次,将其保留在内存中,随后便能以极快的速度处理请求。
安装
可通过 Composer 包管理器安装 Octane:
composer require laravel/octane安装 Octane 后,你可以执行 octane:install Artisan 命令,该命令会将 Octane 的配置文件安装到你的应用中:
php artisan octane:install服务器前提条件
FrankenPHP
FrankenPHP 是一个 PHP 应用服务器,使用 Go 语言编写,支持早期提示(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}'"
XDG_CONFIG_HOME: /var/www/html/config
XDG_DATA_HOME: /var/www/html/data若要启用 HTTPS、HTTP/2 和 HTTP/3,请改用以下配置:
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 原生不支持的平台上运行它,例如 Windows。FrankenPHP 的官方 Docker 镜像既适用于本地开发,也可用于生产环境。
你可以使用以下 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如果显式将 --log-level 选项传递给 php artisan octane:start 命令,Octane 将使用 FrankenPHP 的原生日志记录器,除非另有配置,否则会生成结构化的 JSON 日志。
如需了解使用 Docker 运行 FrankenPHP 的更多信息,可以查阅 FrankenPHP 官方文档。
自定义 Caddyfile 配置
使用 FrankenPHP 时,你可以在启动 Octane 时通过 --caddyfile 选项指定自定义的 Caddyfile:
php artisan octane:start --server=frankenphp --caddyfile=/path/to/your/Caddyfile这允许你在默认设置的基础上自定义 FrankenPHP 的配置,例如添加自定义中间件、配置高级路由或设置自定义指令。如需了解有关 Caddyfile 语法和配置选项的更多信息,可以查阅 Caddy 官方文档。
RoadRunner
RoadRunner 由 RoadRunner 二进制文件驱动,该二进制文件使用 Go 语言构建。当你首次启动基于 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 环境变量。该环境变量将包含 Sail 用来通过 Octane(而非 PHP 开发服务器)运行应用的命令:
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 镜像:
chmod +x ./rr
./vendor/bin/sail build --no-cacheSwoole
如果你计划使用 Swoole 应用服务器来运行 Laravel Octane 应用,那么你必须安装 Swoole PHP 扩展。通常,可以通过 PECL 完成安装:
pecl install swooleOpen Swoole
如果你想使用 Open Swoole 应用服务器来运行 Laravel Octane 应用,那么你必须安装 Open Swoole PHP 扩展。通常,可以通过 PECL 完成安装:
pecl install openswoole将 Laravel Octane 与 Open Swoole 结合使用,可以获得与 Swoole 相同的功能,例如并发任务、tick 以及 interval。
通过 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 文件中,为 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=swoole --host=0.0.0.0 --port='${APP_PORT:-80}'"最后,构建 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 访问应用程序。
在生产环境中保持 Octane 运行
如果要将 Octane 应用程序部署到生产环境,你应该使用 Supervisor 等进程监控工具,确保 Octane 服务器持续运行。适用于 Octane 的 Supervisor 配置文件示例如下:
[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:// 前缀:
'https' => env('OCTANE_HTTPS', false),通过 Nginx 提供服务
如果还未准备好管理自己的服务器配置,或是不熟悉配置运行稳健的 Laravel Octane 应用程序所需的各类服务,可以了解一下 Laravel Cloud,它提供了完全托管的 Laravel Octane 支持。
在生产环境中,应该通过 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 --watch使用该功能前,应确保本地开发环境中已安装 Node。此外,还应在项目中安装 Chokidar 文件监听库:
npm 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 应用程序服务器,还可以指定要启动的“任务 Worker”数量:
php artisan octane:start --workers=4 --task-workers=6指定最大请求数
为帮助防止意外的内存泄漏,Octane 会在任意 Worker 处理完 500 个请求后,优雅地重启该 Worker。要调整该数值,可以使用 --max-requests 选项:
php artisan octane:start --max-requests=250指定最大执行时间
默认情况下,Laravel Octane 会通过应用程序 config/octane.php 配置文件中的 max_execution_time 选项,为传入请求设置 30 秒的最大执行时间:
'max_execution_time' => 30,该配置定义了传入请求在被终止前允许执行的最大秒数。将该值设为 0 将完全禁用执行时间限制。此配置选项对于处理长时间运行请求的应用程序尤为实用,例如文件上传、数据处理,或是对外部服务的 API 调用。
修改 max_execution_time 配置后,必须重启 Octane 服务器,修改才能生效。
重载 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 的方式构建应用程序。下面我们将讨论在使用 Octane 时,可能导致问题的最常见情形。
容器注入
通常情况下,应避免将应用程序服务容器或 HTTP 请求实例注入到其他对象的构造函数中。例如,以下绑定会将整个应用程序服务容器注入到一个以单例形式绑定的对象中:
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 实例持有。这对于你的特定应用程序**可能**不会构成问题;然而,这可能导致容器意外缺失在启动周期后续阶段,或由后续请求添加的绑定。
作为变通方案,你可以选择不再将该绑定注册为单例,也可以向服务中注入一个容器解析器闭包,使其始终解析当前的容器实例:
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 辅助函数将始终返回应用程序当前正在处理的请求,因此可在应用程序内安全使用。
在控制器方法和路由闭包中对 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 [
// ...
];
}在开发应用程序时,需特别注意避免产生此类内存泄漏问题。建议在本地开发过程中监控应用程序的内存使用情况,确保没有向应用程序中引入新的内存泄漏问题。
并发任务
此功能需要 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 的 “任务工作进程”,且在与传入请求完全不同的进程中执行。可用于处理并发任务的工作进程数量,由 octane:start 命令中的 --task-workers 指令决定:
php artisan octane:start --workers=4 --task-workers=6调用 concurrently 方法时,由于 Swoole 任务系统的限制,提供的任务数量不应超过 1024 个。
定时任务与间隔任务
此功能需要 Swoole。
使用 Swoole 时,你可以注册 “tick” 操作,这些操作会每隔指定的秒数执行一次。你可以使用 tick 方法注册 “tick” 回调。传给 tick 方法的第一个参数应为表示定时器名称的字符串。第二个参数应为一个可调用对象,该对象将按指定间隔被调用。
在此示例中,我们将注册一个每 10 秒调用一次的闭包。通常,tick 方法应在应用程序某个服务提供者的 boot 方法中调用:
Octane::tick('simple-ticker', fn () => ray('Ticking...'))
->seconds(10);使用 immediate 方法,你可以让 Octane 在 Octane 服务器首次启动时立即调用 tick 回调,并在此后每隔 N 秒调用一次:
Octane::tick('simple-ticker', fn () => ray('Ticking...'))
->seconds(10)
->immediate();Octane 缓存
此功能需要 Swoole。
使用 Swoole 时,可以利用 Octane 缓存驱动,其读写速度最高可达每秒 200 万次操作。因此,对于需要在缓存层具备极高读写速度的应用程序而言,该缓存驱动是绝佳选择。
此缓存驱动由 Swoole tables 提供支持。缓存中存储的所有数据可供服务器上的所有工作进程访问。不过,服务器重启后,缓存数据将被清除:
Cache::store('octane')->put('framework', 'Laravel', 30); Octane 缓存中允许的最大条目数,可在应用程序的 octane 配置文件中进行定义。
缓存间隔
除了 Laravel 缓存系统提供的常规方法外,Octane 缓存驱动还具备基于间隔的缓存功能。这些缓存会在指定间隔自动刷新,且应注册在应用程序某个服务提供者的 boot 方法中。例如,以下缓存将每 5 秒刷新一次:
use Illuminate\Support\Str;
Cache::store('octane')->interval('random', function () {
return Str::random(10);
}, seconds: 5);表
此功能需要 Swoole。
使用 Swoole 时,你可以定义并与自己的任意 Swoole tables 进行交互。Swoole 表可提供极高的性能吞吐量,且服务器上的所有工作进程都能访问这些表中的数据。不过,服务器重启后,表中的数据将丢失。
表应定义在应用程序 octane 配置文件的 tables 配置数组中。系统已为你预配置了一个最多可容纳 1000 行的示例表。字符串列的最大长度,可通过在列类型后指定列大小来配置,如下所示:
'tables' => [
'example:1000' => [
'name' => 'string:1000',
'votes' => 'int',
],
],若要访问某个表,可以使用 Octane::table 方法:
use Laravel\Octane\Facades\Octane;
Octane::table('example')->set('uuid', [
'name' => 'Nuno Maduro',
'votes' => 1000,
]);
return Octane::table('example')->get('uuid');Swoole 表格支持的列类型有:string、int 和 float。