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

此操作将删除 Caddyfilepublic/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 之间遇到文件锁竞争。对于生产环境,建议使用:

  • RedisMemcached 处理 Session 和缓存

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

数据库连接

数据库连接会自动管理:

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

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

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

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