# Worker 模式

Added in version 4.7.0.

重要

Worker 模式目前处于 实验性阶段。目前官方唯一支持的 Worker 实现是 FrankenPHP，该项目由 PHP 基金会支持。

## 简介

### 什么是 Worker 模式？

Worker 模式是一种性能优化特性，允许 CodeIgniter 在同一个 PHP 进程中处理多个 HTTP 请求，而不是像传统的 PHP-FPM 那样为每个请求都启动一个新进程。

#### 传统 PHP 与 Worker 模式对比

传统 PHP（PHP-FPM）

在传统 PHP 环境中，每个 HTTP 请求都会经历以下周期：

1. Web 服务器接收请求并派生一个新的 PHP 进程

2. PHP 加载并解析所有需要的文件

3. 框架引导：加载自动加载器、配置、服务、路由

4. 建立数据库连接

5. 处理请求并发送响应

6. 关闭所有连接

7. PHP 进程终止，释放所有内存

这种“无状态”架构简单且安全，但在高并发应用中效率较低，因为步骤 2-4 会在每个请求中重复执行。

Worker 模式

在 Worker 模式下，生命周期发生了巨大变化：

1. Worker 进程启动并执行一次性初始化：

   - 加载并解析所有需要的文件（缓存到 OPcache 中）

   - 框架引导：加载自动加载器、配置、服务、路由

   - 建立数据库和缓存连接

2. 针对每个进入的请求：

   - 复用现有的连接和缓存资源

   - 仅重置与请求相关的状态（全局变量、请求/响应对象）

   - 处理请求并发送响应

   - 清理请求特定数据

3. Worker 进程继续运行，等待下一个请求

这种方式消除了冗余的初始化工作和连接开销。对于典型的数据库驱动型应用，性能通常可提升 2 到 3 倍。

### CodeIgniter 如何管理状态

Worker 模式的核心挑战是防止状态在不同请求之间泄露。CodeIgniter 通过以下几种机制处理此问题：

服务重置

  大多数服务会在每个请求结束后销毁并重新创建。只有 `$persistentServices` 列表中列出的服务会在请求之间常驻。

工厂重置

  所有工厂（模型、Config 实例）都会在请求之间重置，确保获取不含陈旧数据的全新实例。

全局变量隔离

  请求数据（`$_GET`、`$_POST`、`$_SERVER` 等）通过 Superglobals 服务在每个请求中实现妥善隔离。

连接持久化

  数据库和缓存连接会在请求开始时进行验证，如果连接健康则复用；若连接失效，则会自动重新建立。

## 快速入门

### 安装步骤

1. 参考 官方文档 安装 FrankenPHP。

   可使用静态二进制文件或 Docker。在此示例中，假设使用 直接下载 的静态二进制文件。

2. 使用 spark 命令安装 Worker 模式模板文件：

       php spark worker:install

   该命令会创建两个文件：

   - Caddyfile：启用了 Worker 模式的 FrankenPHP 配置文件

   - public/frankenphp-worker.php：处理请求循环的 Worker 入口文件

3. 根据需要，在 app/Config/WorkerMode.php 中配置 Worker 设置。对于大多数应用，建议保持默认配置。

### 运行 Worker

使用生成的 Caddyfile 启动 FrankenPHP：

    frankenphp run

服务器将以 Worker 模式启动，并通过 Worker 入口文件处理请求。

若需在后台运行（守护进程模式）：

    frankenphp start

### 卸载

若要移除 Worker 模式模板文件：

    php spark worker:uninstall

此操作将删除 Caddyfile 和 public/frankenphp-worker.php 文件。

## 性能基准

对于数据库驱动型应用，Worker 模式通常能提供 2 到 3 倍的性能提升。实际增益取决于应用的特性：

场景 | 预期提升
--- | ---
简单接口 | 返回极简 JSON 响应的应用提升较小（10-30%）， 因为可消除的引导开销本就很少。
数据库查询 | 执行数据库查询的接口通常会有 2 到 3 倍 的提升， 这得益于连接复用和初始化开销的减少。
复杂引导流程 | 拥有大量服务、路由或配置的应用获益最大，因为这些开销被完全消除了。

### Worker 数量考量

总吞吐量随 Worker 数量增加而扩展。请根据典型的并发模式和可用的 CPU 核心数来匹配 Worker 数量。Worker 过少会限制并发能力，过多则会浪费内存。

### 首个请求延迟

每个 Worker 处理的第一个请求会稍慢，因为需要执行引导程序并建立连接。后续请求则会受益于缓存资源和持久连接。

## 配置

所有 Worker 模式的配置均通过 app/Config/WorkerMode.php 文件管理。

### 配置选项

选项 | 类型 | 描述
--- | --- | ---
$persistentServices | array | 在请求之间常驻且不被重置的服务。不在该列表中的服务将在每次请求后销毁， 以防止状态泄露。 默认值：`['autoloader', 'locator', 'exceptions', 'commands', 'codeigniter', 'superglobals', 'routes', 'cache']`
$resetEventListeners | array | 在请求之间需要移除监听器的事件名称。如果在其他事件回调内部（而不是在 Config/Events.php 的顶层）注册了事件监听器，请使用此选项， 否则监听器会在请求之间不断堆积。默认值：`[]`
$forceGarbageCollection | bool | 是否在每次请求后强制执行垃圾回收。 `true` （默认值，推荐）：防止内存泄漏。 `false`：依赖 PHP 的自动垃圾回收机制。

### 常驻服务

`$persistentServices` 数组控制哪些服务在请求之间保持存活。默认配置包括：

服务 | 用途
--- | ---
`autoloader` | PSR-4 自动加载配置。由于类映射不会改变，持久化是安全的。
`locator` | 用于查找框架文件的文件定位器。缓存文件路径以提高性能。
`exceptions` | 异常处理器。无状态，可安全复用。
`commands` | CLI 命令注册表。仅在 Worker 启动时使用。
`codeigniter` | 主应用实例。编排请求/响应周期。
`superglobals` | 全局变量包装器。内部已针对每个请求实现妥善隔离。
`routes` | 路由配置。路由定义在请求之间不会改变。
`cache` | 缓存服务。保持与缓存后端（Redis、Memcached）的连接。

警告

在不了解服务状态管理的情况下，将服务添加到 `$persistentServices` 可能会导致请求之间的数据泄露。请仅持久化那些真正无状态或自行管理请求隔离的服务。

### 重置事件监听器

Added in version 4.7.1.

在 Config/Events.php 顶层注册的事件监听器会在 Worker 启动时加载一次，并能跨请求正确常驻。但是，如果在另一个事件的回调内部注册监听器，它将在每次请求时被重复注册并累积：

    <?php

    use CodeIgniter\Events\Events;

    // This runs every request — the 'my_event' listener stacks up indefinitely
    Events::on('pre_system', static function (): void {
        Events::on('my_event', 'MyClass::myMethod');
    });

为了在请求之间清理此类监听器，请将事件名称添加到 app/Config/WorkerMode.php 中的 `$resetEventListeners`：

    <?php

    namespace Config;

    class WorkerMode extends \CodeIgniter\Config\WorkerMode
    {
        public array $resetEventListeners = ['my_event'];
    }

备注

推荐的做法是在 Config/Events.php 的顶层注册监听器，而不是在回调内部。仅当无法避免在回调内注册时，才使用 `$resetEventListeners`。

## 优化配置

`app/Config/Optimize.php` 中的配置选项（配置缓存和文件定位器缓存） 不得 与 Worker 模式同时使用。

这些优化是为每个请求都重新开始的传统 PHP 设计的。在 Worker 模式下，持久进程已经自然地提供了这些优势，启用它们反而可能导致陈旧数据的问题。

警告

使用 Worker 模式时，请勿在 app/Config/Optimize.php 中启用 `$configCacheEnabled` 或 `$locatorCacheEnabled`。

### OPcache 设置

Worker 模式能显著受益于 OPcache。请确保已启用并正确配置：

    opcache.enable=1
    opcache.memory_consumption=256
    opcache.max_accelerated_files=20000
    opcache.validate_timestamps=0  ; 仅限生产环境

## 重要注意事项

### 状态管理

由于 PHP 进程在请求之间常驻，因此务必关注状态管理：

- 避免对请求特定数据使用静态属性，因为它们会在请求之间留存

- 谨慎使用单例，因为单例可能会保留请求状态

- 清理资源，例如在每个请求结束后关闭文件句柄和数据库游标

- 不要将用户数据存储在常驻请求的类属性中

### 注册器

配置注册器（`Config/Registrar.php` 文件）在 Worker 模式下可以正常工作。注册器文件在 Worker 启动时被发现一次，而注册器逻辑会在每次实例化 Config 类时应用。每个请求都会获得一个应用了注册逻辑的全新 Config 实例。

### 内存管理

监控内存使用情况：

    ps aux | grep frankenphp

如果内存持续增长：

- 检查是否启用了强制垃圾回收（`$forceGarbageCollection = true`）

- 确保资源在使用后已妥善关闭

- 在不再需要大型对象时对其执行 `unset`

- 检查是否存在阻止垃圾回收的循环引用

### 调试

由于进程跨请求常驻，Worker 模式下的调试可能更具挑战性：

- 修改代码后重启 Worker：Worker 不会自动重新加载修改后的文件

- 检查日志：查看 writable/logs/ 以获取与连接和状态相关的问题

- 善用日志记录：追踪请求在持久进程中的流转过程

### Session 与缓存处理器

基于文件的处理器 可能会在 Worker 之间遇到文件锁竞争。对于生产环境，建议使用：

- Redis 或 Memcached 处理 Session 和缓存

- 这些工具能提供更好的并发性，并支持自动连接持久化

### 数据库连接

数据库连接会自动管理：

- 连接跨请求常驻以提高性能

- 在每个请求开始时验证连接

- 失败的连接会自动重新建立

- 未提交的事务会自动回滚，并记录警告日志