Laravel Octane
简介
Laravel Octane 通过使用高性能应用服务器(包括 FrankenPHP、Open Swoole、Swoole 和 RoadRunner)来提升您的应用性能。Octane 只需启动应用一次,将其保留在内存中,然后以超音速处理请求。
安装
可通过 Composer 包管理器安装 Octane
1composer require laravel/octane
安装 Octane 后,您可以执行 octane:install Artisan 命令,将 Octane 的配置文件安装到您的应用中
1php artisan octane:install
服务器先决条件
FrankenPHP
FrankenPHP 是一个用 Go 编写的 PHP 应用服务器,支持现代 Web 特性,如早期提示 (Early Hints)、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
最后,将 SUPERVISOR_PHP_COMMAND 环境变量添加到应用 docker-compose.yml 文件中的 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:// 访问您的 FrankenPHP Sail 应用,因为使用 https://127.0.0.1 需要额外配置且不被推荐。
通过 Docker 使用 FrankenPHP
使用 FrankenPHP 的官方 Docker 镜像可以提供更好的性能,并使用 FrankenPHP 静态安装中未包含的额外扩展。此外,官方 Docker 镜像支持在 FrankenPHP 本身不支持的平台(如 Windows)上运行。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 的更多信息。
自定义 Caddyfile 配置
使用 FrankenPHP 时,您可以在启动 Octane 时通过 --caddyfile 选项指定自定义的 Caddyfile
1php artisan octane:start --server=frankenphp --caddyfile=/path/to/your/Caddyfile
这允许您在默认设置之外自定义 FrankenPHP 的配置,例如添加自定义中间件、配置高级路由或设置自定义指令。您可以查阅 Caddy 官方文档以了解更多有关 Caddyfile 语法和配置选项的信息。
RoadRunner
RoadRunner 由基于 Go 构建的 RoadRunner 二进制文件驱动。首次启动基于 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
然后,将 SUPERVISOR_PHP_COMMAND 环境变量添加到应用 docker-compose.yml 文件中的 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
在 Open Swoole 上使用 Laravel Octane 提供了与 Swoole 相同的功能,例如并发任务、Ticks 和间隔。
通过 Laravel Sail 使用 Swoole
在通过 Sail 运行 Octane 应用之前,请确保拥有最新版本的 Laravel Sail,并在应用根目录下执行 ./vendor/bin/sail build --no-cache。
或者,您可以使用官方的基于 Docker 的 Laravel 开发环境 Laravel Sail 来开发基于 Swoole 的 Octane 应用。Laravel Sail 默认包含 Swoole 扩展。但是,您仍然需要调整 Sail 使用的 docker-compose.yml 文件。
首先,将 SUPERVISOR_PHP_COMMAND 环境变量添加到应用 docker-compose.yml 文件中的 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 服务器可以通过 octane:start Artisan 命令启动。默认情况下,此命令将使用应用 octane 配置文件中 server 选项指定的服务器
1php artisan octane:start
默认情况下,Octane 将在 8000 端口启动服务器,因此您可以通过 https://:8000 在浏览器中访问您的应用。
在生产环境中保持 Octane 运行
如果您将 Octane 应用部署到生产环境,应使用 Supervisor 等进程监控工具来确保 Octane 服务器保持运行。一个简单的 Octane Supervisor 配置文件示例如下
1[program:octane]2process_name=%(program_name)s_%(process_num)02d3command=php /home/forge/example.com/artisan octane:start --server=frankenphp --host=127.0.0.1 --port=80004autostart=true5autorestart=true6user=forge7redirect_stderr=true8stdout_logfile=/home/forge/example.com/storage/logs/octane.log9stopwaitsecs=3600
通过 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
指定最大执行时间
默认情况下,Laravel Octane 通过应用 config/octane.php 配置文件中的 max_execution_time 选项为传入请求设置 30 秒的最大执行时间
1'max_execution_time' => 30,
此设置定义了传入请求在终止前允许执行的最大秒数。将此值设置为 0 将完全禁用执行时间限制。此配置选项对于处理长时间运行的请求(例如文件上传、数据处理或对外部服务的 API 调用)的应用特别有用。
当您修改 max_execution_time 配置时,必须重启 Octane 服务器以使更改生效。
重载 Workers
您可以使用 octane:reload 命令优雅地重启 Octane 服务器的 Worker。通常,这应该在部署后执行,以便将新部署的代码加载到内存中,并用于处理后续请求
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 与间隔
此功能需要 Swoole。
使用 Swoole 时,您可以注册每隔指定秒数执行一次的“Tick”操作。您可以通过 tick 方法注册“Tick”回调。提供给 tick 方法的第一个参数应该是表示 Tick 名称的字符串。第二个参数应该是将在指定间隔调用的可调用对象。
在此示例中,我们将注册一个每 10 秒调用一次的闭包。通常,tick 方法应在应用的服务提供者的 boot 方法中调用
1Octane::tick('simple-ticker', fn () => ray('Ticking...'))2 ->seconds(10);
使用 immediate 方法,您可以指示 Octane 在 Octane 服务器最初启动时立即调用 Tick 回调,此后每隔 N 秒调用一次
1Octane::tick('simple-ticker', fn () => ray('Ticking...'))2 ->seconds(10)3 ->immediate();
Octane 缓存
此功能需要 Swoole。
使用 Swoole 时,您可以利用 Octane 缓存驱动,它提供高达每秒 200 万次操作的读写速度。因此,该缓存驱动对于需要缓存层具有极致读/写速度的应用来说是一个绝佳选择。
此缓存驱动由 Swoole 数据表驱动。存储在缓存中的所有数据对服务器上的所有 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);
数据表 (Tables)
此功能需要 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。