Режим воркеров
Режим воркеров запускает постоянные PHP-процессы, которые инициализируются один раз, а затем обрабатывают множество запросов, поэтому стоимость запуска PHP оплачивается однократно, а не при каждом запросе. Вместо того чтобы разрушать и заново выстраивать состояние PHP на каждый запрос, ваше приложение загружает автозагрузчик, конфигурацию и подключения к базе данных один раз и переиспользует их на протяжении всего времени жизни воркера.
Как это работает
- Включите режим воркеров. Установите
WORKER_MODE_ENABLED=trueи укажите вENTRY_FILEваш скрипт инициализации. Это включает режим воркеров для всех PHP-воркеров в пуле. - Инициализация один раз. PHP запускается и выполняет внешнюю область видимости один раз. Регистрация автозагрузчика, загрузка конфигурации, подключения к базе данных и любой другой код инициализации выполняются однократно.
- Вход в цикл обработки запросов. Вызовите
oxphp_worker(callback). OxPHP начинает направлять входящие HTTP-запросы в ваш колбэк. - Сброс между запросами. Суперглобальные переменные (
$_GET,$_POST,$_SERVER,$_COOKIE,$_FILES,php://input), буферы вывода и заголовки ответа сбрасываются автоматически. Мягкий сброс очищает состояние отдельного запроса, сохраняя при этом инициализированные ресурсы во внешней области видимости. - Внешняя область видимости сохраняется. Переменные, определённые до
oxphp_worker(), статические свойства, подключения к базе данных и автозагрузчики остаются доступными для всех запросов, обрабатываемых этим воркером.
Режим воркеров меняет поведение маршрутизации. Все запросы, которые не соответствуют статическому файлу на диске, направляются воркеру вместо возврата 404. Подробнее см. Маршрутизация.
Конфигурация
| Переменная | По умолчанию | Описание |
|---|---|---|
WORKER_MODE_ENABLED |
false |
Включает постоянный режим воркеров. Принимает true, 1, yes. Требует, чтобы ENTRY_FILE указывал на .php-скрипт |
ENTRY_FILE |
(не задано) | Путь к скрипту инициализации воркера. Для относительных путей разрешается относительно DOCUMENT_ROOT; сегменты .. и абсолютные пути допускаются (расположение скриптов инициализации воркера за пределами публичного корня документов — поддерживаемая схема) |
WORKER_MAX_MEMORY_MIB |
0 |
Максимальный объём памяти PHP на воркер в МиБ до перезапуска. 0 = без ограничений |
Устаревшая переменная WORKER_FILE по-прежнему разбирается (с выводом WARN при запуске) и ведёт себя как WORKER_MODE_ENABLED=true ENTRY_FILE=$WORKER_FILE. В новых развёртываниях следует использовать явную пару переменных; устаревшая форма будет удалена в одном из будущих релизов.
Для перезапуска, управляемого приложением, вызовите OxPHP\Server\Worker::scheduleExit() внутри обработчика запроса. Воркер завершится корректно после того, как текущий запрос будет обработан.
Написание скрипта воркера
Скрипт воркера состоит из двух частей: внешней области видимости, которая выполняется один раз при запуске, и колбэка, передаваемого в oxphp_worker(), который выполняется при каждом запросе.
<?php
// Outer scope: runs once at startup
require __DIR__ . '/../vendor/autoload.php';
$config = parse_ini_file(__DIR__ . '/../config/app.ini');
$db = new PDO($config['dsn'], $config['user'], $config['pass'], [
PDO::ATTR_PERSISTENT => true,
]);
$app = new MyApp\Application($config, $db);
// Request loop: runs for every request
oxphp_worker(function () use ($app) {
$app->handle();
});
// Shutdown: runs when the worker exits
$app->terminate();Что сбрасывается, а что сохраняется
OxPHP выполняет мягкий сброс между запросами. Состояние отдельного запроса очищается автоматически, тогда как всё, что было инициализировано во внешней области видимости, сохраняется на протяжении всего времени жизни воркера.
- Суперглобальные переменные —
$_GET,$_POST,$_SERVER,$_COOKIE,$_FILESиphp://inputзаполняются заново данными нового запроса - Буферы вывода — все буферы вывода сбрасываются и очищаются
- Заголовки ответа — HTTP-код состояния и заголовки возвращаются к значениям по умолчанию
- Состояние ошибок — информация о последней ошибке (сообщение, файл, строка, тип) и статус соединения очищаются. Пользовательские обработчики ошибок (
set_error_handler()), обработчики исключений (set_exception_handler()) и уровеньerror_reporting()сохраняются между запросами
- Переменные во внешней области видимости — всё, что определено до
oxphp_worker()и захвачено черезuse - Статические свойства — статические свойства классов сохраняют свои значения
- Подключения к базе данных — PDO, MySQLi и другие постоянные соединения остаются открытыми
- Автозагрузчики — зарегистрированные автозагрузчики (Composer, собственные) остаются активными
- Загруженные классы и функции — все ранее загруженные классы, интерфейсы, трейты и функции
Перезапуск
Воркеры автоматически перезапускаются (перезапускаются с новым PHP-процессом) при выполнении любого из следующих условий:
- Превышен лимит памяти — использование памяти PHP воркером превышает
WORKER_MAX_MEMORY_MIBМиБ - Приложение запросило завершение — обработчик вызвал
Worker::scheduleExit(). Полезно для управляемой приложением горячей перезагрузки, перезагрузки на основе mtime файлов или повторного выполнения инициализации на каждый запрос - Последовательные ошибки — воркер сталкивается с 3 подряд сбоями обработчика (фатальные ошибки, таймауты или необработанные исключения). Обратите внимание, что вызовы
exit()/die()не считаются сбоями
Когда воркер перезапускается, PHP-процесс завершается и запускается новый, повторно выполняя внешнюю область видимости скрипта воркера. При перезапуске по памяти и по запланированному завершению текущий запрос обрабатывается штатно до конца, после чего воркер завершается. При перезапуске из-за ошибок воркер завершается после сбойного запроса.
Перезагрузка при разработке
Режим воркеров хранит состояние инициализации (автозагрузчик, DI-контейнер, подключения к БД) в памяти, поэтому одного лишь opcache.validate_timestamps=1 недостаточно, чтобы подхватить изменения в коде, выполнявшемся во внешней области видимости. Для циклов разработки есть два варианта:
- Перезапуск на каждый запрос. Вызывайте
OxPHP\Server\Worker::current()->scheduleExit()в конце каждого вызова обработчика (например, ограничив это env-флагомOXPHP_DEV). Текущий запрос обрабатывается штатно до конца, затем воркер завершается и запускается заново, повторно выполняя внешнюю область видимости. Это меняет выигрыш в производительности от режима воркеров на семантику перезагрузки в стиле FPM. Это самый простой и надёжный подход для активной разработки. - Держите воркер «тёплым», перезагружайте обработчики запросов. Полностью откажитесь от
scheduleExit(), включитеopcache.validate_timestamps=1и держите инициализацию минимальной. Код, загруженный внутри колбэка запроса, будет обновлён OPcache при следующем запросе; код, загруженный один раз во внешней области видимости, — нет. Полный список нюансов см. в OPcache и JIT → Настройки для разработки.
Устранение неполадок
Запросы зависают и никогда не завершаются
Если oxphp_worker() никогда не вызывается в скрипте инициализации, ни один запрос не направляется, и каждый запрос ждёт бесконечно. Убедитесь, что ваш скрипт вызывает oxphp_worker() безусловно в нормальном пути выполнения кода.
Состояние протекает между запросами
Переменные, определённые внутри колбэка oxphp_worker(), очищаются сборщиком мусора PHP, но статические свойства и глобальные переменные, определённые во внешней области видимости, сохраняются. Если вы видите, что данные из одного запроса появляются в другом, проверьте статические свойства или глобальные переменные, накапливающие состояние между вызовами.
Решение: явно сбрасывайте статическое состояние в начале каждого колбэка запроса или не храните состояние отдельного запроса в статических свойствах.
Воркер немедленно перезапускается (лимит памяти)
Лимит памяти воркера проверяется после каждого запроса на основе данных PHP об использовании памяти. Если фаза инициализации выделяет большой объём памяти (например, загружает большой кеш), начальный объём занимаемой памяти может уже быть близок к лимиту.
Решение: увеличьте WORKER_MAX_MEMORY_MIB или отложите крупные выделения памяти до первого запроса.
Воркер немедленно перезапускается (лимит ошибок)
Три подряд сбоя обработчика вызывают перезапуск. Проверьте логи вашего приложения на предмет исключений или фатальных ошибок, происходящих в колбэке запроса.
Проверка: ищите ошибки в логе доступа или структурированном выводе логов:
docker logs <container> 2>&1 | grep '"level":"error"'Подключение к базе данных обрывается после простоя
Если ваш сервер базы данных закрывает простаивающие соединения, попытки переподключения в следующем запросе могут завершиться неудачей. Используйте пул соединений, который обрабатывает переподключение, или перехватывайте исключение и переподключайтесь вручную.
Пример для Docker
services:
app:
image: ghcr.io/oxphp/oxphp:0.10.0
ports:
- "8080:80"
volumes:
- ./src:/var/www/html
environment:
- DOCUMENT_ROOT=/var/www/html/public
- WORKER_MODE_ENABLED=true
- ENTRY_FILE=/var/www/html/worker.php
- WORKER_MAX_MEMORY_MIB=128PHP API
Интроспекция воркера и точка входа воркера доступны через класс OxPHP\Server\Worker.
<?php
$worker = OxPHP\Server\Worker::current();
$worker->serve(function () {
handleRequest();
});Устаревшие свободные функции (oxphp_is_worker, oxphp_worker_id, oxphp_worker) по-прежнему доступны и работают через то же внутреннее состояние. В новом коде следует предпочитать API класса.
Класс также предоставляет интроспекцию во время выполнения, полезную для корректного самоперезапуска, наблюдаемости и проверок работоспособности:
| Метод | Возвращает |
|---|---|
Worker::isWorkerMode(): bool |
Работает ли сервер в режиме воркеров |
$worker->id(): int |
Стабильный идентификатор воркера в рамках потока |
$worker->startTime(): float |
Unix-метка времени запуска этого воркера |
$worker->requestCount(): int |
Количество запросов, обработанных этим воркером |
$worker->memoryUsage(): int |
Текущее значение memory_get_usage(true) для этого воркера |
$worker->rss(): int |
Текущий размер резидентной памяти в байтах (Linux/macOS) |
$worker->maxMemoryBytes(): int |
Порог перезапуска — WORKER_MAX_MEMORY_MIB × 1 МиБ, или 0 при отсутствии ограничений |
$worker->isExitScheduled(): bool |
Был ли вызван scheduleExit() |
$worker->exitReason(): ?string |
null во время работы; "scheduled", "max_memory" или "error", когда воркер завершается |
Полные сигнатуры и разобранные примеры см. в OxPHP\Server\Worker.
Примеры на PHP
Определение режима воркеров
Используйте OxPHP\Server\Worker::isWorkerMode(), чтобы проверить, работает ли текущий процесс в режиме воркеров. Это полезно для написания кода, который работает как в традиционном режиме, так и в режиме воркеров.
<?php
if (OxPHP\Server\Worker::isWorkerMode()) {
// Reuse a persistent connection
$redis = new Redis();
$redis->pconnect('redis', 6379);
} else {
// Traditional mode: connect per request
$redis = new Redis();
$redis->connect('redis', 6379);
}Скрипт воркера для Symfony
<?php
use App\Kernel;
require __DIR__ . '/../vendor/autoload.php';
$kernel = new Kernel('prod', false);
$kernel->boot();
oxphp_worker(function () use ($kernel) {
$request = Symfony\Component\HttpFoundation\Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
});
$kernel->shutdown();Рекомендации
- Задайте
WORKER_MAX_MEMORY_MIB(например,128), чтобы утекающий воркер перезапускался автоматически, а не поглощал ресурсы хоста. Дополните этоWorker::scheduleExit()для перезапуска, управляемого приложением. - Не храните состояние отдельного запроса в статических свойствах или глобальных переменных. Поскольку они сохраняются между запросами, оставшееся состояние от одного запроса может протечь в другой.
- Проверяйте мягкий сброс на раннем этапе. Добавьте
Worker::current()->scheduleExit()в ваш обработчик под флагом разработки и прогоните приложение целиком, от начала до конца. Это ловит ошибки протекания состояния до того, как вы перейдёте на долгоживущие воркеры. - Обрабатывайте таймауты простоя базы данных. Если ваш драйвер базы данных отключается после периода простоя, перехватывайте исключение и переподключайтесь или используйте пул соединений, который обрабатывает переподключение автоматически.
- Держите внешнюю область видимости минимальной. Инициализируйте только то, что действительно должно сохраняться: автозагрузчики, конфигурацию и разделяемые сервисы. Отложите настройку, специфичную для запроса, в колбэк.
См. также
- Маршрутизация — как режим воркеров встраивается в маршрутизацию URL
- Ранний ответ — отправить ответ немедленно и продолжить фоновую обработку
- Функции PHP — полный справочник по
oxphp_worker(),oxphp_is_worker()и другим встроенным функциям - Справочник по конфигурации — полный список переменных окружения