---
title: "PHP на сервере: FPM, Swoole, воркеры и асинхронные среды | DevSense"
description: "Изучаем архитектуру окружения PHP: сравниваем PHP-FPM, долгоживущие серверы приложений (Swoole, RoadRunner, FrankenPHP) и асинхронные циклы событий ReactPHP/AMPHP."
faq:
  - question: "В чем разница между PHP-FPM и Swoole?"
    answer: "PHP-FPM запускает новый процесс или очищает состояние процесса для каждого входящего запроса, освобождая память по его завершении. Swoole — это долгоживущий сервер приложений, который загружает код приложения в память один раз и обрабатывает запросы без перезагрузки среды."
  - question: "Что такое утечка памяти в долгоживущих процессах PHP?"
    answer: "Поскольку процессы не завершаются после каждого запроса, любые статические свойства, синглтоны или глобальные переменные, накапливающие данные, будут постоянно расти в объеме, что приведет к исчерпанию лимита памяти и падению воркера."
  - question: "Как RoadRunner управляет процессами PHP?"
    answer: "RoadRunner написан на Go и выполняет роль балансировщика нагрузки и диспетчера процессов. Он общается с долгоживущими PHP-воркерами по бинарному протоколу Goridge, вынося обработку HTTP, gRPC и очередей наружу."
  - question: "Почему блокирующие функции «вешают» ReactPHP или AMPHP?"
    answer: "ReactPHP и AMPHP работают в однопоточном цикле событий (event loop). Любой блокирующий вызов (например, `sleep()` или синхронный запрос к БД) полностью останавливает поток выполнения, замораживая сервер для всех остальных клиентов."
---

# PHP на сервере: FPM, Swoole, воркеры и циклы событий

Многие разработчики до сих пор считают, что PHP умеет работать только по классической схеме «share-nothing» (без совместного использования ресурсов), где состояние сбрасывается, а все переменные уничтожаются сразу после отдачи страницы пользователю. Однако современный PHP успешно держит высоконагруженные WebSockets-соединения, обрабатывает тысячи HTTP-запросов в секунду в одном потоке с помощью Go-воркеров и функционирует в асинхронных событийных циклах. Но если запустить такое приложение, спроектированное по старым лекалам, обычная утечка памяти в статическом свойстве может мгновенно обрушить весь ваш продакшн-кластер.

С появлением Swoole, RoadRunner, FrankenPHP и ReactPHP язык PHP вышел далеко за рамки классического PHP-FPM. Но долгоживущие среды принесли новые риски — утечки памяти, взаимное загрязнение состояния между пользователями и блокировки потока ввода-вывода (I/O), парализующие асинхронный цикл.

> [!IMPORTANT]
> Современный PHP больше не привязан к одной модели выполнения. Выбор правильной среды требует понимания разницы между короткоживущими запросами, постоянными воркерами и асинхронными циклами событий.

---

## Содержание
* [PHP-FPM (FastCGI Process Manager)](#php-fpm)
* [Долгоживущие серверы (Swoole и Laravel Octane)](#swoole)
* [RoadRunner (Диспетчер на Go)](#roadrunner)
* [FrankenPHP (Воркеры на базе Caddy)](#frankenphp)
* [Асинхронные движки (ReactPHP и AMPHP)](#event-loop)
* [Утечки памяти: скрытый убийца](#memory-leaks)
* [Частые ошибки](#common-mistakes)
* [Практические рецепты](#practical-recipes)
* [🧠 Вопросы для самопроверки](#self-check)

---

<a id="php-fpm"></a>
## PHP-FPM (FastCGI Process Manager)

PHP-FPM — классический, проверенный временем менеджер процессов. Веб-сервер nginx принимает входящий HTTP-трафик и перенаправляет запросы к файлам `.php` на воркеры FPM через UNIX-сокет или TCP-порт.

```nginx
# /etc/nginx/sites-available/default
location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
```

* **Принцип работы**: Каждый процесс-воркер обрабатывает строго один запрос за раз, отдает ответ клиенту, сбрасывает все переменные состояния и возвращается в пул свободных воркеров.
* **Почему это важно**: Полностью отсутствует риск накопления утечек памяти между запросами. Если скрипт падает по ошибке, процесс перезапускается с чистого листа.
* **Ограничения**: Высокие накладные расходы на инициализацию (например, фреймворки Laravel или Symfony вынуждены полностью загружаться с диска при каждом новом запросе).

---

<a id="swoole"></a>
## Долгоживущие серверы (Swoole и Laravel Octane)

Swoole — это расширение на C, превращающее PHP в высокопроизводительный сетевой сервер с поддержкой корутин.

```php
// app/Servers/HttpServer.php
$server = new Swoole\Http\Server("127.0.0.1", 9501);
$server->on("request", function ($request, $response) {
    $response->header("Content-Type", "text/plain");
    $response->end("Hello World");
});
$server->start();
```

* **Принцип работы**: Код PHP считывается и инициализируется в памяти **один раз** при старте сервера. Все последующие запросы обрабатываются уже запущенными процессами в оперативной памяти.
* **Почему это важно**: Избавление от инициализации фреймворка на каждом запросе дает прирост производительности до 10 раз.
* **Ограничения**: Любая утечка памяти в синглтонах или статических свойствах классов будет накапливаться до тех пор, пока процесс не упадет по нехватке памяти.

---

<a id="roadrunner"></a>
## RoadRunner (Диспетчер на Go)

RoadRunner — высокопроизводительный сервер приложений PHP и менеджер процессов, написанный на Go.

```yaml
# .rr.yaml
server:
  command: "php worker.php"
http:
  address: "0.0.0.0:8080"
  pool:
    num_workers: 4
```

* **Принцип работы**: Слой на Go принимает HTTP-запросы, соединения WebSockets или вызовы gRPC, а затем передает их долгоживущим PHP-воркерам по бинарному протоколу Goridge.
* **Почему это важно**: Позволяет сочетать надежность многопоточного Go в качестве диспетчера и простоту разработки на PHP.

---

<a id="frankenphp"></a>
## FrankenPHP (Воркеры на базе Caddy)

FrankenPHP — современный сервер приложений PHP, интегрированный непосредственно в веб-сервер Caddy. Его «режим воркера» (worker mode) удерживает запущенное приложение в оперативной памяти.

* **Почему это важно**: Упрощает развертывание до запуска одного бинарного файла. Из коробки поддерживает HTTP/1.1, HTTP/2, HTTP/3 и автоматическую генерацию SSL-сертификатов Let's Encrypt.

---

<a id="event-loop"></a>
## Асинхронные движки (ReactPHP и AMPHP)

В отличие от Swoole, ReactPHP и AMPHP реализуют асинхронность на чистом PHP с использованием механизма совместной многозадачности без дополнительных расширений C.

```php
// app/Servers/ReactServer.php
require __DIR__ . '/vendor/autoload.php';

$loop = React\EventLoop\Loop::get();
$loop->addPeriodicTimer(1.0, function () {
    echo "Tick\n";
});
$loop->run();
```

* **Почему это важно**: Идеальный инструмент для написания микросервисов, чувствительных к вводу-выводу (I/O-bound), серверов очередей сообщений и прокси-серверов.
* **Ограничения**: Категорически запрещено использовать блокирующий код (например, стандартные запросы PDO к базе данных или `file_get_contents`). Все запросы к БД и API должны осуществляться через асинхронные клиенты.

---

<a id="memory-leaks"></a>
## Утечки памяти: скрытый убийца

В долгоживущих средах выполнения (Swoole, RoadRunner, режим воркера FrankenPHP, CLI-демоны) память после завершения обработки HTTP-запроса автоматически не освобождается.

```php
// app/Services/DataLeak.php
class DataLeak
{
    private static array $cache = [];

    public function cacheRequest(array $data)
    {
        // ❌ Утечка памяти! Этот массив будет расти с каждым запросом
        self::$cache[] = $data; 
    }
}
```

> [!NOTE]
> Чтобы избежать утечек памяти, необходимо ограничивать кэши в оперативной памяти (например, алгоритмом вытеснения LRU), полностью отказаться от статических свойств классов для хранения данных текущего запроса и использовать инструменты контроля памяти вроде `memory_get_usage(true)`.

---

<a id="common-mistakes"></a>
## ⚠️ Частые ошибки

### 1. Блокирование цикла событий в ReactPHP / AMPHP
Вызов любой синхронной блокирующей функции полностью останавливает событийный цикл, парализуя работу сервера для всех остальных пользователей.

```php
// app/Http/Handler.php
// ❌ Опасно: останавливает работу всего сервера на время внешнего запроса
$data = file_get_contents("http://external-api.com/data");

// ✅ Правильный подход
// Используйте асинхронные клиенты:
$browser = new React\Http\Browser();
$browser->get("http://external-api.com/data")->then(function ($response) {
    // Асинхронная обработка ответа
});
```

### 2. Сохранение данных запроса в статических свойствах
Запись сессии или учетных данных пользователя в статические свойства приведет к тому, что другие пользователи смогут получить доступ к чужим приватным данным.

```php
// app/Services/AuthService.php
// ❌ Опасно: Пользователь Б может получить сессию Пользователя А!
class AuthService
{
    public static ?User $currentUser = null;
}
```

---

<a id="practical-recipes"></a>
## Практические рецепты

### Безопасная перезагрузка воркеров
Для борьбы с возможными микроутечками памяти настройте периодический перезапуск воркеров по достижении определенного лимита обработанных запросов или порога занятой оперативной памяти.

```ini
# /etc/php/8.3/fpm/pool.d/www.conf (PHP-FPM)
; Перезапускать воркер каждые 500 запросов
pm.max_requests = 500
```

```yaml
# .rr.yaml (RoadRunner)
http:
  pool:
    # Перезапускать воркер при превышении лимита в 1000 запросов или 100МБ памяти
    max_jobs: 1000
    allocate_timeout: 60s
    destroy_timeout: 60s
    supervisor:
      watch_interval: 1s
      max_worker_memory: 100 # МБ
```

---

## 🧠 Вопросы для самопроверки

1. **Верно или нет?** В среде PHP-FPM статические свойства классов могут стать причиной утечки памяти между запросами разных пользователей.
2. Что произойдет, если выполнить вызов `sleep(5)` внутри обработчика запроса в ReactPHP?
3. Каким образом режим воркера FrankenPHP достигает прироста скорости по сравнению с PHP-FPM?
4. Для чего используется директива `pm.max_requests` в настройках PHP-FPM?

<details>
<summary><b>Показать ответы</b></summary>

1. **Неверно.** В PHP-FPM вся память процесса полностью очищается и сбрасывается между HTTP-запросами, поэтому статические свойства не сохраняются.
2. Это заблокирует однопоточный событийный цикл на 5 секунд. В течение этого времени сервер не сможет принимать новые подключения и отвечать другим пользователям.
3. Он загружает код приложения и библиотеки фреймворка в память один раз, избавляя сервер от необходимости считывать файлы с диска и повторно компилировать их при каждом запросе.
4. Она перезапускает процесс-воркер PHP-FPM после обработки определенного числа запросов, что позволяет нивелировать утечки памяти в нестабильных расширениях C или коде сторонних пакетов.
</details>
