Анализ и оптимизация PHP-воркера для RoadRunner

Рассмотрим скрипт, работающий в среде RoadRunner, и разберем ключевые вопросы по его архитектуре.

Исходный код

Исходный скрипт имеет следующую структуру:

use Nyholm\Psr7;
use Psr\Http\Message\ServerRequestInterface;
use Spiral\RoadRunner;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\JsonResponse;

$worker = RoadRunner\Worker::create();
$psrFactory = new Psr7\Factory\Psr17Factory();

$worker = new RoadRunner\Http\PSR7Worker($worker, $psrFactory, $psrFactory, $psrFactory);
$router = new League\Route\Router;

while ($req = $worker->waitRequest()) {
    $response = "";
    try {
        $post = $req->getParsedBody();
        $path = $req->getUri()->getPath();
        $router->map('POST', '/some1[/]', function (ServerRequestInterface $req, array $args) {
            //...
        });
        $router->map('POST', '/some2[/]', function (ServerRequestInterface $req, array $args) {/**/});

        $response = $router->dispatch($req);
        $worker->respond($response);
    } catch (League\Route\Http\Exception\MethodNotAllowedException $e) {
        $worker->respond(new HtmlResponse('<b>Error</b>: ' . $e->getMessage(), 500));
    } catch (League\Route\Http\Exception\NotFoundException $e) {
        $worker->respond(new HtmlResponse('<b>Error</b>: ' . $e->getMessage(), 404));
    } catch (\Throwable $e) {
        // моя обработка
    }
}

Ответы на вопросы

1. Области выполнения кода

Верно: код внутри цикла while ($req = $worker->waitRequest()) выполняется для каждого входящего HTTP-запроса. Код, расположенный до этого цикла, инициализируется один раз при запуске воркера и сохраняется в памяти между запросами.

Это важная особенность работы с RoadRunner и аналогичными решениями (Swoole, ReactPHP), которые позволяют избежать накладных расходов на повторную инициализацию фреймворков, подключение к БД и других ресурсоемких операций.

2. Оптимизация роутинга

Да, это верная рекомендация. Определение маршрутов ($router->map()) следует вынести за пределы цикла обработки запросов. В текущей реализации маршруты переопределяются при каждом запросе, что:

  • Создает ненужную нагрузку на процессор
  • Занимает дополнительную память
  • Не дает никаких преимуществ, так как маршруты обычно статичны

Оптимальный подход: инициализировать роутер один раз, до входа в основной цикл. Это стандартная практика для PSR-совместимых приложений.

3. Работа с базой данных

Предложенный подход с проверкой соединения через mysqli_ping() является разумным, но требует доработки:

  • Инициализация соединения: действительно лучше создавать подключение к БД в глобальной области (до цикла), чтобы использовать одно соединение для всех запросов в рамках жизни воркера.
  • Проверка актуальности: механизм проверки соединения необходим, так как долгоживущие соединения могут разрываться из-за таймаутов сервера БД или сетевых проблем.
  • Рекомендации по реализации:
    • Используйте более современный PDO вместо прямого mysqli для лучшей переносимости и безопасности
    • Рассмотрите возможность использования connection pool, если поддерживается вашим драйвером
    • Добавьте логирование случаев переподключения для мониторинга
    • Убедитесь, что обработка исключений не скрывает реальные проблемы с подключением

Пример улучшенной структуры:

// Инициализация ДО цикла
$db = new PDO($dsn, $user, $password, [
    PDO::ATTR_PERSISTENT => true,
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);

while ($req = $worker->waitRequest()) {
    try {
        // Проверка и восстановление соединения при необходимости
        if (!$db || $db->getAttribute(PDO::ATTR_CONNECTION_STATUS) === false) {
            $db = new PDO($dsn, $user, $password, [
                PDO::ATTR_PERSISTENT => true,
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
            ]);
        }
        // Обработка запроса с использованием $db
    } catch (\Throwable $e) {
        // Обработка ошибок
    }
}

Такая архитектура обеспечивает баланс между производительностью (постоянное соединение) и надежностью (автоматическое восстановление при обрывах).