Что такое Swoole и OpenSwoole
Swoole и OpenSwoole — это не фреймворки в стиле Laravel или Symfony, а PHP-расширения и runtime для долгоживущих сетевых приложений. Они добавляют в PHP встроенные HTTP, WebSocket, TCP/UDP-серверы, coroutine runtime, таймеры, process pool, task workers, shared memory-структуры и coroutine-friendly клиенты.
Практически это означает: PHP-процесс не обязан жить только один request, как в классической модели PHP-FPM. Он может поднять сервер, держать соединения, принимать события и обслуживать много I/O-bound задач внутри одного набора воркеров. Поэтому Swoole/OpenSwoole логически стоят рядом с Асинхронный PHP и event loop, WebSocket, SSE и realtime в PHP и PHP-FPM, RoadRunner и долгоживущие воркеры.
Исторически Swoole и OpenSwoole тесно связаны, но в живых проектах лучше относиться к ним как к отдельным дистрибутивам с похожей моделью. В коде это обычно видно по namespace: Swoole\* или OpenSwoole\*. Нельзя слепо копировать пример из одной документации в проект на другой сборке: проверьте установленное расширение, namespace, версию и доступные compile flags.
Модель выполнения
В PHP-FPM web server передаёт запрос в FPM worker, приложение обрабатывает request, отдаёт response, а затем request-scoped состояние должно исчезнуть. В Swoole/OpenSwoole сам PHP-процесс может быть сервером. Он стартует один раз, создаёт master/manager/worker-процессы, слушает порт и вызывает ваши callbacks при событиях: Request, Open, Message, Close, Receive, Task, Finish.
flowchart TD
A[Reverse proxy / client] --> B[OpenSwoole или Swoole server]
B --> C[Master process]
C --> D[Manager process]
D --> E[Worker process 1]
D --> F[Worker process 2]
D --> G[Task workers]
E --> H[Coroutine: HTTP request]
E --> I[Coroutine: DB / HTTP I/O]
F --> J[Coroutine: WebSocket connection]
H --> K[Response]
I --> K
J --> L[push frame]
E -. shared only via special structures .-> M[Table / Atomic / IPC / Redis]
F -. shared only via special structures .-> MВнутри worker могут создаваться coroutines. Coroutine похожа на очень лёгкий поток userland-уровня: она может приостановиться на ожидании I/O и дать runtime выполнить другую coroutine. Но, как и в предыдущей статье про event loop, это cooperative concurrency. CPU-heavy цикл, неподходящая блокирующая функция или синхронный клиент базы всё равно способны остановить worker.
Минимальный HTTP-сервер
Пример ниже показывает не «роутер», а саму идею: файл запускается из CLI и остаётся жить как сервер.
<?php
use OpenSwoole\Http\Request;
use OpenSwoole\Http\Response;
use OpenSwoole\Http\Server;
use OpenSwoole\Coroutine;
$server = new Server('127.0.0.1', 9501);
$server->set([
'worker_num' => 2,
'enable_coroutine' => true,
]);
$server->on('Start', function (Server $server): void {
echo "HTTP server: http://127.0.0.1:9501\n";
});
$server->on('Request', function (Request $request, Response $response): void {
// Имитация неблокирующего ожидания внутри coroutine.
Coroutine::sleep(0.05);
$response->header('Content-Type', 'application/json; charset=utf-8');
$response->end(json_encode([
'path' => $request->server['request_uri'] ?? '/',
'worker' => posix_getpid(),
'time' => date(DATE_ATOM),
], JSON_UNESCAPED_SLASHES));
});
$server->start();Для Swoole namespace будет Swoole\Http\Server, Swoole\Coroutine и так далее. В реальном приложении над таким сервером часто ставят reverse proxy, а фреймворк загружают один раз при старте worker. Это ускоряет bootstrap, но приносит главный риск долгоживущих процессов: stale state.
Coroutine runtime и hooks
OpenSwoole-документация подчёркивает удобство coroutine-подхода: код выглядит почти синхронно, но runtime переключает coroutines на ожидании I/O. Это не то же самое, что yield в генераторах и не то же самое, что Fiber сам по себе. Fiber в PHP даёт низкоуровневую возможность приостановки стека; Swoole/OpenSwoole добавляют вокруг этого сетевой runtime, серверы, scheduler и набор I/O API.
Есть два практических стиля:
<?php
use OpenSwoole\Coroutine;
use OpenSwoole\Coroutine\Channel;
Coroutine\run(function (): void {
$channel = new Channel(1);
Coroutine::create(function () use ($channel): void {
Coroutine::sleep(0.1);
$channel->push('profile loaded');
});
echo $channel->pop() . PHP_EOL;
});Первый стиль — явно пользоваться coroutine API: Coroutine::create(), Coroutine::sleep(), Coroutine\Channel, coroutine clients. Второй — включать runtime hooks, чтобы часть привычных PHP-функций или расширений в coroutine context работала неблокирующе. Hooks удобны, но их нельзя считать магической совместимостью со всем кодом: проверяйте конкретные функции, драйверы и расширения.
WebSocket, TCP и таймеры
Swoole/OpenSwoole особенно полезны там, где PHP должен держать соединение, а не просто быстро отдать HTML. WebSocket-сервер получает события открытия соединения, входящего frame и закрытия. На каждое соединение есть file descriptor, по которому сервер может отправить сообщение обратно.
<?php
use OpenSwoole\WebSocket\Frame;
use OpenSwoole\WebSocket\Server;
$server = new Server('127.0.0.1', 9502);
$server->on('Open', function (Server $server, $request): void {
echo "open: {$request->fd}\n";
});
$server->on('Message', function (Server $server, Frame $frame): void {
$server->push($frame->fd, 'echo: ' . $frame->data);
});
$server->on('Close', function (Server $server, int $fd): void {
echo "close: {$fd}\n";
});
$server->start();Таймеры (tick, after, clear) нужны для heartbeat, периодической очистки, отложенных действий, проверки состояния соединений. Важно не превращать timer callback в тяжёлую задачу. Для долгой CPU-bound работы лучше использовать task workers, отдельные процессы или обычные Очереди, фоновые задачи и воркеры.
TCP/UDP-серверы находятся уровнем ниже HTTP. Они подходят для кастомных протоколов, IoT, внутренних бинарных протоколов, игровых или realtime-шлюзов. Цена — вы сами отвечаете за framing, timeouts, backpressure и протокол ошибок.
Channels, shared state и процессы
Есть два разных смысла «shared».
Первый — shared state внутри одного worker-процесса. Coroutines разделяют память процесса: объекты, singleton’ы, статические свойства, контейнер, кеши. Это быстро, но опасно. Если coroutine A положила текущего пользователя в глобальный сервис, затем уступила управление на I/O, coroutine B может увидеть или изменить это состояние. Request data должны быть локальными для request/coroutine, а не лежать в случайном singleton.
<?php
final class CurrentUser
{
public static ?int $id = null;
}
// Плохо для coroutine runtime: это состояние разделяет весь worker.
CurrentUser::$id = $requestUserId;
Coroutine::sleep(0.05);
// За время ожидания другая coroutine могла записать сюда другого пользователя.
doSomethingFor(CurrentUser::$id);Второй — обмен между процессами. Worker-процессы не делят обычную PHP-память. Для координации нужны специальные механизмы: Table, Atomic, locks, process pool, task workers, IPC-каналы или внешнее хранилище вроде Redis. Coroutine\Channel — это в первую очередь очередь синхронизации между coroutines; не надо считать её универсальной заменой брокера сообщений.
Отличие от PHP-FPM
Главное отличие не в скорости, а в lifecycle. В PHP-FPM плохой singleton чаще всего умирает вместе с request. В Swoole/OpenSwoole он может жить часами. Это меняет требования к контейнеру приложения, middleware, подключению к базе, кешам, логированию, locale, timezone, current tenant и current user.
Типовые правила такие:
- bootstrap можно делать один раз, но request-specific данные нужно сбрасывать на каждый request;
- соединения к БД и Redis надо проверять на разрыв и reconnect;
- нельзя хранить
Request, пользователя или DTO ответа в статических свойствах; - memory leaks становятся production-проблемой, а не мелкой небрежностью;
- graceful reload, лимиты памяти и health checks обязательны, а не «потом добавим».
Поэтому Swoole/OpenSwoole редко стоит выбирать только потому, что «быстрее». Они уместны для realtime, большого fan-out к внешним сервисам, high-concurrency I/O, WebSocket gateway, TCP-сервисов и приложений, где выигрыш от долгоживущего runtime превышает эксплуатационную сложность. Для обычного CRUD через PHP-FPM проще, предсказуемее и дешевле поддерживать классическую модель.
См. также
- Асинхронный PHP и event loop — cooperative concurrency, event loop, promises, coroutines и Fiber.
- PHP-FPM, RoadRunner и долгоживущие воркеры — request isolation, worker model, stale state и graceful reload.
- WebSocket, SSE и realtime в PHP — realtime-подходы и жизненный цикл соединений.
- Очереди, фоновые задачи и воркеры — retry, backoff, idempotency и вынесение тяжёлой работы из request.
- Генераторы и Fiber — чем Fiber отличается от generator и почему сам по себе не является event loop.
- Наблюдаемость и эксплуатация PHP-сервисов — logs, metrics, tracing, memory limits и рестарты долгоживущих воркеров.