Laravel Octane
介绍
Laravel Octane 通过使用高性能应用服务器(包括 FrankenPHP、Open Swoole、Swoole 和 RoadRunner)来服务您的应用程序,从而提升应用程序的性能。Octane 启动您的应用程序一次,将其保存在内存中,然后以超音速速度处理请求。
安装
Octane 可以通过 Composer 包管理器安装:
composer require laravel/octane
安装 Octane 后,您可以执行 octane:install
Artisan 命令,该命令会将 Octane 的配置文件安装到您的应用程序中:
php artisan octane:install
服务器先决条件
Laravel Octane 需要 PHP 8.1+。
FrankenPHP
FrankenPHP 的 Octane 集成处于测试阶段,在生产环境中使用时应谨慎。
FrankenPHP 是一个用 Go 编写的 PHP 应用服务器,支持现代 Web 功能,如早期提示和 Zstandard 压缩。当您安装 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=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 镜像提供了在不支持的原生平台(如 Windows)上运行 FrankenPHP 的支持。FrankenPHP 的官方 Docker 镜像适用于本地开发和生产使用。
您可以使用以下 Dockerfile 作为容器化您的 FrankenPHP 驱动的 Laravel 应用程序的起点:
FROM dunglas/frankenphp
RUN install-php-extensions \
pcntl
# 在此处添加其他 PHP 扩展...
COPY . /app
ENTRYPOINT ["php", "artisan", "octane:frankenphp"]
然后,在开发过程中,您可以使用以下 Docker Compose 文件来运行您的应用程序:
# compose.yaml
services:
frankenphp:
build:
context: .
entrypoint: php artisan octane:frankenphp --max-requests=1
ports:
- "8000:8000"
volumes:
- .:/app
您可以查阅官方 FrankenPHP 文档以获取有关使用 Docker 运行 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
可执行文件来获取 RoadRunner 二进制文件的最新 Linux 构建:
./vendor/bin/sail shell
# 在 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=80"
最后,确保 rr
二进制文件是可执行的,并构建您的 Sail 镜像:
chmod +x ./rr
./vendor/bin/sail build --no-cache
Swoole
如果您计划使用 Swoole 应用服务器来服务您的 Laravel Octane 应用程序,您必须安装 Swoole PHP 扩展。通常,这可以通过 PECL 完成:
pecl install swoole
Open Swoole
如果您想使用 Open Swoole 应用服务器来服务您的 Laravel Octane 应用程序,您必须安装 Open Swoole PHP 扩展。通常,这可以通过 PECL 完成:
pecl install openswoole
使用 Laravel Octane 和 Open Swoole 提供了 Swoole 提供的相同功能,例如并发任务、滴答和间隔。
通过 Laravel Sail 使用 Swoole
在通过 Sail 服务 Octane 应用程序之前,请确保您拥有最新版本的 Laravel Sail,并在应用程序的根目录中执行 ./vendor/bin/sail build --no-cache
。
或者,您可以使用 Laravel Sail 开发基于 Swoole 的 Octane 应用程序,这是 Laravel 的官方基于 Docker 的开发环境。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=80"
最后,构建您的 Sail 镜像:
./vendor/bin/sail build --no-cache
Swoole 配置
Swoole 支持一些您可以根据需要添加到 octane
配置文件中的额外配置选项。由于它们很少需要修改,这些选项未包含在默认配置文件中:
'swoole' => [
'options' => [
'log_file' => storage_path('logs/swoole_http.log'),
'package_max_length' => 10 * 1024 * 1024,
],
],
服务您的应用程序
Octane 服务器可以通过 octane:start
Artisan 命令启动。默认情况下,此命令将使用应用程序的 octane
配置文件中指定的服务器:
php artisan octane:start
默认情况下,Octane 将在端口 8000 上启动服务器,因此您可以通过 http://localhost:8000
在 Web 浏览器中访问您的应用程序。
通过 HTTPS 服务您的应用程序
默认情况下,通过 Octane 运行的应用程序生成的链接前缀为 http://
。当通过 HTTPS 服务您的应用程序时,可以在应用程序的 config/octane.php
配置文件中将 OCTANE_HTTPS
环境变量设置为 true
。当此配置值设置为 true
时,Octane 将指示 Laravel 将所有生成的链接前缀为 https://
:
'https' => env('OCTANE_HTTPS', false),
通过 Nginx 服务您的应用程序
如果您还没有准备好管理自己的服务器配置,或者不熟悉配置运行强大 Laravel Octane 应用程序所需的各种服务,请查看 Laravel Forge。
在生产环境中,您应该在传统 Web 服务器(如 Nginx 或 Apache)后面服务您的 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
配置选项来配置应监视的目录和文件。
指定工作者数量
默认情况下,Octane 将为您的机器提供的每个 CPU 核心启动一个应用程序请求工作者。这些工作者将用于服务进入应用程序的 HTTP 请求。您可以在调用 octane:start
命令时使用 --workers
选项手动指定要启动的工作者数量:
php artisan octane:start --workers=4
如果您使用的是 Swoole 应用服务器,您还可以指定要启动的 "任务工作者" 数量:
php artisan octane:start --workers=4 --task-workers=6
指定最大请求数量
为了帮助防止意外的内存泄漏,Octane 在处理 500 个请求后会优雅地重新启动任何工作者。要调整此数字,您可以使用 --max-requests
选项:
php artisan octane:start --max-requests=250
重新加载工作者
您可以使用 octane:reload
命令优雅地重新启动 Octane 服务器的应用程序工作者。通常,这应该在部署后进行,以便将新部署的代码加载到内存中,并用于服务后续请求:
php artisan octane:reload
停止服务器
您可以使用 octane:stop
Artisan 命令停止 Octane 服务器:
php artisan octane:stop
检查服务器状态
您可以使用 octane:status
Artisan 命令检查 Octane 服务器的当前状态:
php artisan octane:status
依赖注入与 Octane
由于 Octane 启动您的应用程序一次,并在服务请求时将其保存在内存中,因此在构建应用程序时需要考虑一些注意事项。例如,应用程序的服务提供者的 register
和 boot
方法只会在请求工作者最初启动时执行。在后续请求中,将重用相同的应用程序实例。
鉴于此,您在将应用程序服务容器或请求注入到任何对象的构造函数时应特别小心。这样做可能会导致对象在后续请求中持有过时的容器或请求。
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
实例,容器将被注入到服务中,并且在后续请求中该服务将持有相同的容器。这可能不会对您的特定应用程序造成问题;然而,它可能导致容器意外地缺少在启动周期后期或由后续请求添加的绑定。
作为解决方法,您可以停止将绑定注册为单例,或者可以将容器解析器闭包注入到服务中,以始终解析当前的容器实例:
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;
/**
* 注册任何应用程序服务。
*/
public function register(): void
{
$this->app->singleton(Service::class, function (Application $app) {
return new Service($app['request']);
});
}
在此示例中,如果在应用程序启动过程中解析 Service
实例,HTTP 请求将被注入到服务中,并且在后续请求中该服务将持有相同的请求。因此,所有头信息、输入和查询字符串数据将不正确,以及所有其他请求数据。
作为解决方法,您可以停止将绑定注册为单例,或者可以将请求解析器闭包注入到服务中,以始终解析当前的请求实例。或者,最推荐的方法是简单地在运行时将对象需要的特定请求信息传递给对象的一个方法:
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
助手将始终返回应用程序当前处理的请求,因此在应用程序中使用是安全的。
在控制器方法和路由闭包中对 Illuminate\Http\Request
实例进行类型提示是可以接受的。
配置库注入
通常,您应该避免将配置库实例注入到其他对象的构造函数中。例如,以下绑定将配置库注入到作为单例绑定的对象中:
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'));
});
}
在此示例中,如果配置值在请求之间发生变化,该服务将无法访问新值,因为它依赖于原始的库实例。
作为解决方法,您可以停止将绑定注册为单例,或者可以将配置库解析器闭包注入到类中:
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;
/**
* 处理传入请求。
*/
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
方法的第一个参数应该是一个表示滴答器名称的字符串。第二个参数应该是将在指定间隔调用的可调用对象。
在此示例中,我们将注册一个每 10 秒调用一次的闭包。通常,tick
方法应在应用程序的一个服务提供者的 boot
方法中调用:
Octane::tick('simple-ticker', fn () => ray('Ticking...'))
->seconds(10);
使用 immediate
方法,您可以指示 Octane 在 Octane 服务器最初启动时立即调用滴答回调,并在每 N 秒后调用:
Octane::tick('simple-ticker', fn () => ray('Ticking...'))
->seconds(10)
->immediate();
Octane 缓存
此功能需要 Swoole。
使用 Swoole 时,您可以利用 Octane 缓存驱动程序,该驱动程序提供每秒高达 200 万次操作的读写速度。因此,对于需要从缓存层获得极高读写速度的应用程序来说,此缓存驱动程序是一个极好的选择。
此缓存驱动程序由 Swoole 表 提供支持。存储在缓存中的所有数据对服务器上的所有工作者可用。然而,当服务器重新启动时,缓存的数据将被清除:
Cache::store('octane')->put('framework', 'Laravel', 30);
Octane 缓存中允许的最大条目数可以在应用程序的 octane
配置文件中定义。
缓存间隔
除了 Laravel 缓存系统提供的典型方法外,Octane 缓存驱动程序还具有基于间隔的缓存。这些缓存会在指定的间隔自动刷新,并应在应用程序的一个服务提供者的 boot
方法中注册。例如,以下缓存将每五秒刷新一次:
use Illuminate\Support\Str;
Cache::store('octane')->interval('random', function () {
return Str::random(10);
}, seconds: 5);
表
此功能需要 Swoole。
使用 Swoole 时,您可以定义和与您自己的任意 Swoole 表 进行交互。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
。