Эта страница была переведена сообществом и не обязательно является актуальной. Обратитесь к справочной версии.
Эта страница была переведена сообществом и не обязательно является актуальной. Обратитесь к справочной версии.
Эта страница была переведена сообществом и не обязательно является актуальной. Обратитесь к справочной версии.
Загрузите приложение один раз и держите его в памяти. FrankenPHP будет обрабатывать входящие запросы за несколько миллисекунд.
Установите значение переменной окружения FRANKENPHP_CONFIG на worker /path/to/your/worker/script.php:
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
Используйте опцию --worker команды php-server для обслуживания содержимого текущей директории в режиме воркера:
frankenphp php-server --worker /path/to/your/worker/script.php
Если ваше PHP-приложение встроено в бинарник, вы можете добавить пользовательский Caddyfile в корневую директорию приложения.
Он будет использоваться автоматически.
Также можно перезапускать воркер при изменении файлов с помощью опции --watch.
Следующая команда выполнит перезапуск, если будет изменён любой файл с расширением .php в директории /path/to/your/app/ или её поддиректориях:
frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to/your/app/**/*.php"
Эта функция часто используется в сочетании с горячей перезагрузкой.
См. документацию по worker-режиму FrankenPHP для Symfony.
См. документацию по Laravel Octane для FrankenPHP.
Следующий пример показывает, как создать собственный worker-скрипт без использования сторонних библиотек:
<?php
// public/index.php
// Инициализация вашего приложения
require __DIR__.'/vendor/autoload.php';
$myApp = new \App\Kernel();
$myApp->boot();
// Обработчик запросов за пределами цикла для повышения производительности (выполняет меньше работы)
$handler = static function () use ($myApp) {
try {
// Выполняется при получении запроса,
// суперглобальные переменные, php://input и прочие сбрасываются
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
} catch (\Throwable $exception) {
// `set_exception_handler` вызывается только когда worker-скрипт завершается,
// что может быть не тем, что вы ожидаете, поэтому перехватывайте и обрабатывайте исключения здесь
(new \MyCustomExceptionHandler)->handleException($exception);
}
};
$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
$keepRunning = \frankenphp_handle_request($handler);
// Действия после отправки HTTP-ответа
$myApp->terminate();
// Вызов сборщика мусора, чтобы снизить вероятность его запуска в процессе генерации страницы
gc_collect_cycles();
if (!$keepRunning) break;
}
// Очистка
$myApp->shutdown();
Запустите ваше приложение и используйте переменную окружения FRANKENPHP_CONFIG для настройки вашего воркера:
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
По умолчанию запускается 2 воркера на каждый CPU. Вы также можете настроить количество запускаемых воркеров:
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 вместо суперглобальных переменных, преобразуйте запрос/ответ на границах обработчика с помощью nyholm/psr7 и nyholm/psr7-server:
composer require nyholm/psr7 nyholm/psr7-server psr/http-server-handler
<?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;
}
PHP изначально не предназначался для долгоживущих процессов, поэтому есть ещё много библиотек и устаревшего кода, которые вызывают утечки памяти. Обходной путь для использования такого кода в режиме воркера состоит в перезапуске скрипта воркера после обработки определённого количества запросов:
Предыдущий сниппет воркера позволяет настроить максимальное количество запросов для обработки, установив переменную окружения с именем MAX_REQUESTS.
Хотя можно перезапускать воркеры при изменении файлов, также можно корректно перезапустить все воркеры через API администрирования Caddy. Если администрирование включено в вашем Caddyfile, вы можете отправить запрос POST на конечную точку перезапуска следующим образом:
curl -X POST http://localhost:2019/frankenphp/workers/restart
Если worker-скрипт завершится с ненулевым кодом выхода, FrankenPHP перезапустит его со стратегией экспоненциальной задержки.
Если скрипт воркера остается активным дольше, чем время последней задержки × 2, FrankenPHP не будет применять штраф к worker-скрипту и перезапустит его снова.
Однако, если worker-скрипт продолжает завершаться с ненулевым кодом выхода в течение короткого промежутка времени
(например, из-за опечатки в скрипте), FrankenPHP завершит работу с ошибкой: too many consecutive failures.
Количество последовательных сбоев можно настроить в вашем Caddyfile с помощью опции max_consecutive_failures:
frankenphp {
worker {
# ...
max_consecutive_failures 10
}
}
Когда включен режим воркера, требуется дополнительный поток для обработки входящих запросов, поэтому значение по умолчанию для num_threads составляет 1 + 2x(количество доступных CPU).
Таким образом, в этом режиме, если num_threads и num заданы явно, num_threads должно быть строго больше, чем num.
PHP суперглобальные переменные ($_SERVER, $_ENV, $_GET…) ведут себя следующим образом:
frankenphp_handle_request(), суперглобальные переменные содержат значения, связанные с самим worker-скриптомfrankenphp_handle_request(), суперглобальные переменные содержат значения, сгенерированные на основе обработанного HTTP-запроса, каждый вызов frankenphp_handle_request() изменяет значения суперглобальных переменныхЧтобы получить доступ к суперглобальным переменным worker-скрипта внутри колбэка, необходимо скопировать их и импортировать копию в область видимости колбэка:
<?php
// Копирование суперглобальной переменной $_SERVER воркера перед первым вызовом frankenphp_handle_request()
$workerServer = $_SERVER;
$handler = static function () use ($workerServer) {
var_dump($_SERVER); // $_SERVER, привязанный к запросу
var_dump($workerServer); // $_SERVER worker-скрипта
};
// ...
Большинство суперглобальных переменных ($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER, $_REQUEST) автоматически сбрасываются между запросами. Однако, $_ENV в настоящее время не сбрасывается между запросами. Это означает, что любые изменения, внесённые в $_ENV во время запроса, будут сохраняться и быть видны последующим запросам, обрабатываемым тем же потоком воркера. Избегайте хранения данных, специфичных для запроса или конфиденциальных, в $_ENV.
Поскольку режим воркера сохраняет PHP-процесс активным между запросами, следующее состояние сохраняется между запросами:
static внутри функций или методов, сохраняют свои значения между запросами.ini_set(), stream_context_set_default(), date_default_timezone_set(), chdir(), stream_wrapper_register(), set_error_handler() или set_exception_handler(), остаются в силе для последующих запросов. Никогда не храните там значения, специфичные для запроса или пользователя, например, учетные данные в контексте потока по умолчанию: вместо этого передавайте явный контекст при каждом вызове.Это сделано намеренно и является тем, что делает режим воркера быстрым. Однако это требует внимания, чтобы избежать непредвиденных побочных эффектов:
<?php
function getCounter(): int {
static $count = 0;
return ++$count; // Увеличивается между запросами!
}
$handler = static function () {
echo getCounter(); // 1, 2, 3, ... для каждого запроса в этом потоке
};
while (\frankenphp_handle_request($handler)) {
// ...
}
При написании worker-скриптов убедитесь, что вы сбрасываете любое состояние, специфичное для запроса, между запросами. Фреймворки, такие как Symfony и Laravel Octane, позаботятся о сбросе большинства состояний за вас, но вам всё равно может потребоваться сбросить свои собственные сервисы. В Symfony сервисы, которые содержат состояние, специфичное для запроса, должны реализовывать Symfony\Contracts\Service\ResetInterface, чтобы они сбрасывались ядром между запросами.