Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 113 additions & 36 deletions docs/cn/worker.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
---
title: FrankenPHP worker 模式:保持你的 PHP 应用程序在内存中
description: 以 worker 模式运行 FrankenPHP,使你的 PHP 应用程序在请求之间保持启动状态,减少启动开销,并在几毫秒内提供响应。
---

# 使用 FrankenPHP workers

启动一次应用程序并将其保存在内存中。
启动你的应用程序一次并将其保存在内存中。
FrankenPHP 将在几毫秒内处理传入请求。

## 启动 worker 脚本
## 启动 FrankenPHP worker 脚本

### Docker
### 使用 Docker 运行 FrankenPHP worker

将 `FRANKENPHP_CONFIG` 环境变量的值设置为 `worker /path/to/your/worker/script.php`:

```console
```bash
docker run \
-e FRANKENPHP_CONFIG="worker /app/path/to/your/worker/script.php" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```

### 独立二进制文件
### 使用独立二进制文件运行 FrankenPHP worker

使用 `php-server` 命令的 `--worker` 选项通过 worker 为当前目录的内容提供服务:
使用 `php-server` 命令的 `--worker` 选项,通过 worker 为当前目录的内容提供服务:

```console
```bash
frankenphp php-server --worker /path/to/your/worker/script.php
```

Expand All @@ -31,40 +36,21 @@ frankenphp php-server --worker /path/to/your/worker/script.php
还可以使用 `--watch` 选项在[文件更改时重启 worker](config.md#watching-for-file-changes)。
如果 `/path/to/your/app/` 目录或子目录中任何以 `.php` 结尾的文件被修改,以下命令将触发重启:

```console
```bash
frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to/your/app/**/*.php"
```

此功能通常与[热重载](hot-reload.md)结合使用。

## Symfony Runtime

> [!TIP]
> 以下部分仅在 Symfony 7.4 之前是必需的,因为 Symfony 7.4 引入了对 FrankenPHP worker 模式的原生支持。
FrankenPHP 的 worker 模式由 [Symfony Runtime Component](https://symfony.com/doc/current/components/runtime.html) 支持。
要在 worker 中启动任何 Symfony 应用程序,请安装 [PHP Runtime](https://github.com/php-runtime/runtime) 的 FrankenPHP 包:

```console
composer require runtime/frankenphp-symfony
```

通过定义 `APP_RUNTIME` 环境变量来使用 FrankenPHP Symfony Runtime 启动你的应用服务器:
## 适用于 Symfony 的 Worker 模式

```console
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
-e APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```
请参阅[FrankenPHP Symfony worker 模式文档](symfony.md#symfony-worker-mode-with-frankenphp)。

## Laravel Octane
## 适用于 Laravel Octane 的 Worker 模式

请参阅[专门的文档](laravel.md#laravel-octane)。
请参阅[FrankenPHP Laravel Octane 文档](laravel.md#laravel-octane)。

## 自定义应用程序
## 编写自定义 FrankenPHP worker 脚本

以下示例展示了如何创建自己的 worker 脚本而不依赖第三方库:

Expand Down Expand Up @@ -110,7 +96,7 @@ $myApp->shutdown();

然后,启动你的应用程序并使用 `FRANKENPHP_CONFIG` 环境变量配置你的 worker:

```console
```bash
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
-v $PWD:/app \
Expand All @@ -121,17 +107,65 @@ docker run \
默认情况下,每个 CPU 启动 2 个 worker。
你也可以配置要启动的 worker 数量:

```console
```bash
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php 42" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
```

### 使用 PSR-15

如果你的应用程序使用 [PSR-15](https://www.php-fig.org/psr/psr-15/) 而不是超全局变量,请使用 [`nyholm/psr7`](https://github.com/Nyholm/psr7) 和 [`nyholm/psr7-server`](https://github.com/Nyholm/psr7-server) 在处理器的边缘转换请求/响应:

```console
composer require nyholm/psr7 nyholm/psr7-server psr/http-server-handler
```

```php
<?php
// public/index.php

require __DIR__.'/vendor/autoload.php';

use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7Server\ServerRequestCreator;

$myApp = new \App\Kernel(); // implements Psr\Http\Server\RequestHandlerInterface
$myApp->boot();

$psr17Factory = new Psr17Factory();
$creator = new ServerRequestCreator(
$psr17Factory, // ServerRequestFactory
$psr17Factory, // UriFactory
$psr17Factory, // UploadedFileFactory
$psr17Factory, // StreamFactory
);

$handler = static function () use ($myApp, $creator) {
$response = $myApp->handle($creator->fromGlobals());

http_response_code($response->getStatusCode());
foreach ($response->getHeaders() as $name => $values) {
foreach ($values as $value) {
header("$name: $value", false);
}
}
echo $response->getBody();
};

$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
$keepRunning = \frankenphp_handle_request($handler);
gc_collect_cycles();
if (!$keepRunning) break;
}
```

### 在处理一定数量的请求后重启 worker

由于 PHP 最初不是为长时间运行的进程而设计的,仍有许多库和传统代码会泄漏内存。
由于 PHP 最初不是为长时间运行的进程而设计的,许多库和传统代码仍可能导致内存泄漏。
在 worker 模式下使用此类代码的一个解决方法是在处理一定数量的请求后重启 worker 脚本:

前面的 worker 代码片段允许通过设置名为 `MAX_REQUESTS` 的环境变量来配置要处理的最大请求数。
Expand All @@ -140,7 +174,7 @@ docker run \

虽然可以在[文件更改时重启 workers](config.md#watching-for-file-changes),但也可以通过 [Caddy admin API](https://caddyserver.com/docs/api) 优雅地重启所有 workers。如果在你的 [Caddyfile](config.md#caddyfile-config) 中启用了 admin,你可以通过简单的 POST 请求 ping 重启端点,如下所示:

```console
```bash
curl -X POST http://localhost:2019/frankenphp/workers/restart
```

Expand All @@ -163,6 +197,13 @@ frankenphp {
}
```

### 线程数

当 worker 模式启用时,需要一个额外的线程来处理传入请求,
因此默认的 `num_threads` 值为 `1 + 2x(可用 CPU 数量)`。

因此,在该模式下,如果 `num_threads` 和 `num` 被显式设置,则 `num_threads` 必须严格大于 `num`。

## 超全局变量行为

[PHP 超全局变量](https://www.php.net/manual/language.variables.superglobals.php)(`$_SERVER`、`$_ENV`、`$_GET`...)
Expand All @@ -185,3 +226,39 @@ $handler = static function () use ($workerServer) {

// ...
```

大多数超全局变量(`$_GET`、`$_POST`、`$_COOKIE`、`$_FILES`、`$_SERVER`、`$_REQUEST`)会在请求之间自动重置。
然而,**`$_ENV` 当前不会在请求之间重置**。
这意味着在请求期间对 `$_ENV` 所做的任何修改都将持续存在,并对同一 worker 线程处理的后续请求可见。
避免在 `$_ENV` 中存储与请求相关或敏感的数据。

## 状态持久性

由于 worker 模式使 PHP 进程在请求之间保持活动状态,以下状态会在请求之间持久化:

- **静态变量**:函数或方法中用 `static` 声明的变量在请求之间保持其值。
- **类静态属性**:类的静态属性在请求之间持久化。
- **全局变量**:worker 脚本全局范围内的变量在请求之间持久化。
- **内存缓存**:在请求处理器之外存储在内存中的任何数据(数组、对象)都将持久化。
- **运行时引擎设置**:使用 `ini_set()`、`stream_context_set_default()`、`date_default_timezone_set()`、`chdir()`、`stream_wrapper_register()`、`set_error_handler()` 或 `set_exception_handler()` 进行的更改对后续请求仍然有效。切勿在此处存储与请求或用户相关的特定值,例如默认流上下文中的凭据:请为每个调用传递一个显式上下文。

这是设计使然,也是 worker 模式快速的原因。然而,它需要注意避免意外的副作用:

```php
<?php
function getCounter(): int {
static $count = 0;
return ++$count; // 在请求之间递增!
}

$handler = static function () {
echo getCounter(); // 此线程上的每个请求分别为 1、2、3...
};

while (\frankenphp_handle_request($handler)) {
// ...
}
```

在编写 worker 脚本时,请确保在请求之间重置任何与请求相关的状态。
像 [Symfony](symfony.md) 和 [Laravel Octane](laravel.md) 这样的框架会为你处理大部分状态重置,但你可能仍然需要重置自己的服务。对于 Symfony,持有与请求相关状态的服务应实现 [`Symfony\Contracts\Service\ResetInterface`](https://github.com/symfony/contracts/blob/main/Service/ResetInterface.php),以便它们在请求之间由内核重置。
Loading
Loading