Высокопроизводительные серверы со Swoole
Обслуживайте тысячи соединений с помощью корутин Swoole
«Высокопроизводительные серверы со Swoole» — бесплатный урок PHP Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения PHP Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс PHP Academy содержит 4 уроков всего.
Что такое Swoole
Swoole (и его форк OpenSwoole) — это расширение на C, которое превращает PHP в высокопроизводительную среду выполнения на основе корутин и событий. Вместо того чтобы PHP-FPM создавал отдельный процесс для каждого запроса, сервер Swoole один раз запускает приложение и сохраняет его в памяти, обслуживая запросы пулом долгоживущих рабочих процессов со встроенными корутинами. В результате пропускная способность возрастает на порядок, а внутри одного запроса появляется нативная конкурентность.
Постоянный HTTP-сервер
Классический пример — полноценный HTTP-сервер в одном скрипте. Он принимает соединения, передаёт каждый запрос рабочему процессу и не запускает фреймворк заново. Обратите внимание: для этого требуется ext-swoole — на стандартном PHP CLI такой сервер не работает.
<?php
$server = new Swoole\Http\Server('0.0.0.0', 9501);
$server->set(['worker_num' => 4]);
$server->on('request', function ($request, $response) {
$response->header('Content-Type', 'application/json');
$response->end(json_encode(['hello' => $request->server['request_uri']]));
});
echo "listening on :9501\n";
$server->start();Корутин вместо обратных вызовов
Главное преимущество Swoole — это корутины: Вы пишете последовательный код в блокирующем стиле, а среда выполнения планирует его кооперативно. Coroutine\run() открывает контекст корутины, а go() запускает новую корутину. У каждой корутины есть собственный стек, как у волокна, но Swoole автоматически переключает их в точках ввода-вывода.
<?php
use Swoole\Coroutine as Co;
Co\run(function () {
go(function () { Co::sleep(1); echo "A done\n"; });
go(function () { Co::sleep(1); echo "B done\n"; });
// Both sleep concurrently -> total ~1s, not 2s
});Перехват блокирующих функций
Именно хуки среды выполнения делают работу с корутинами удобной. Swoole прозрачно переписывает блокирующие вызовы — PDO, Redis, curl, sleep, file_get_contents — в неблокирующие версии, учитывающие корутины. Обычный вызов $pdo->query() приостанавливает корутину, а не замораживает рабочий процесс.
<?php
use Swoole\Runtime;
use Swoole\Coroutine as Co;
Runtime::enableCoroutine(SWOOLE_HOOK_ALL); // hook PDO, curl, sleep, etc.
Co\run(function () {
go(function () { sleep(1); echo "task 1\n"; }); // sleep() is now async!
go(function () { sleep(1); echo "task 2\n"; });
}); // finishes in ~1s because sleep was hookedКонкурентный ввод-вывод с WaitGroup
Чтобы распределять задачи и затем дождаться их завершения, Swoole предоставляет WaitGroup (аналог sync.WaitGroup из Go). Запускайте корутины, вызывайте add() для каждой задачи, done() после её завершения и wait(), чтобы дождаться завершения всех задач — одновременно.
<?php
use Swoole\Coroutine as Co;
use Swoole\Coroutine\WaitGroup;
Co\run(function () {
$wg = new WaitGroup();
$results = [];
foreach (['eu', 'us', 'asia'] as $region) {
$wg->add();
go(function () use ($wg, $region, &$results) {
Co::sleep(0.5); // simulated API call
$results[$region] = "ok-$region";
$wg->done();
});
}
$wg->wait(); // all three ran in parallel (~0.5s)
var_dump($results);
});Каналы для взаимодействия
Корутины взаимодействуют через каналы — типизированные очереди ограниченного размера, которые при заполнении приостанавливают производителя, а при опустошении — потребителя. Это обмен сообщениями в стиле CSP, полностью исключающий необходимость блокировок общего состояния.
<?php
use Swoole\Coroutine as Co;
use Swoole\Coroutine\Channel;
Co\run(function () {
$chan = new Channel(2);
go(function () use ($chan) {
foreach (range(1, 3) as $n) $chan->push($n);
$chan->push(null); // sentinel
});
go(function () use ($chan) {
while (($v = $chan->pop()) !== null) echo "got $v\n";
});
});Пулы соединений
Постоянный сервер не должен открывать соединение с базой данных для каждого запроса. Swoole предоставляет безопасные для корутин пулы соединений, благодаря которым рабочие процессы повторно используют фиксированный набор соединений для тысяч одновременно выполняющихся корутин, устраняя постоянное создание и закрытие соединений.
<?php
use Swoole\Database\PDOConfig;
use Swoole\Database\PDOPool;
use Swoole\Coroutine as Co;
Co\run(function () {
$pool = new PDOPool((new PDOConfig())
->withHost('127.0.0.1')->withDbname('app')
->withUsername('u')->withPassword('p'), 8);
go(function () use ($pool) {
$pdo = $pool->get(); // borrow
$row = $pdo->query('SELECT NOW()')->fetch();
$pool->put($pdo); // return to pool
var_dump($row);
});
});Утечки состояния: ловушка постоянной памяти
Поскольку процесс продолжает работать между запросами, глобальное и статическое состояние сохраняется. Неограниченно растущий статический кэш, одиночный объект с данными конкретного запроса или незавершённая транзакция базы данных могут нарушить последующие запросы и исчерпать память. Правила таковы: не храните данные, ограниченные областью запроса, в статических свойствах и одиночных объектах, сбрасывайте контекст для каждого запроса и задавайте max_request, чтобы рабочие процессы периодически перезапускались.
<?php
$server = new Swoole\Http\Server('0.0.0.0', 9501);
$server->set([
'worker_num' => 8,
'max_request' => 10000, // recycle a worker after N requests to bound leaks
]);
// Never store $request data in a static property between requests!Контекст, локальный для корутины
Поскольку один рабочий процесс чередует множество корутин, обычные статические свойства нельзя использовать для данных конкретного запроса. Swoole предоставляет каждой корутине контекст (Coroutine::getContext()) — объект, время жизни которого совпадает с временем жизни корутины; он идеально подходит для хранения текущего пользователя, идентификатора запроса или дескриптора транзакции.
<?php
use Swoole\Coroutine as Co;
Co\run(function () {
go(function () {
$ctx = Co::getContext();
$ctx['request_id'] = 'req-123';
// ... deep call chain can read Co::getContext()['request_id']
echo Co::getContext()['request_id'], PHP_EOL;
});
});Модель процессов и рабочие процессы задач
Swoole запускает процесс master (управляет потоками цикла событий), процесс manager (создаёт и контролирует рабочие процессы), процессы worker (выполняют обработчики запросов и корутины), а также, при необходимости, рабочие процессы задач для тяжёлых, блокирующих или ограниченных процессором задач, переданных через $server->task(). Используйте рабочие процессы задач для обработки изображений или длительных вычислений, чтобы они не задерживали корутины запросов, — так Вы получите и конкурентность, и параллелизм.
Отложенное выполнение и таймеры
Swoole позволяет запланировать работу после завершения текущей корутины с помощью Coroutine::defer() — это удобно для надёжного освобождения ресурсов из пула, даже если возникло исключение. Общесерверные таймеры (Timer::tick) выполняют периодические задачи, например обновление кэша, внутри рабочего процесса без внешнего планировщика cron.
<?php
use Swoole\Coroutine as Co;
Co\run(function () {
go(function () {
$conn = 'borrowed-connection';
Co::defer(function () use ($conn) {
echo "released $conn\n"; // runs when coroutine exits, even on error
});
echo "using $conn\n";
});
});Быстрая проверка
Почему в сервере Swoole необходимо избегать хранения данных запроса в статических свойствах?
Повторение
Swoole превращает PHP в постоянно работающий сервер, управляемый корутинами:
- Приложение запускается один раз; пул рабочих процессов обслуживает запросы со встроенными корутинами (
Co\run/go()). - Хуки среды выполнения делают обычные вызовы PDO, curl и sleep неблокирующими; WaitGroup и каналы координируют конкурентную работу.
- Пулы соединений повторно используют соединения с базой данных между корутинами.
- Постоянное хранение в памяти означает, что необходимо предотвращать утечки состояния: используйте
Coroutine::getContext()для данных конкретного запроса, задавайтеmax_requestи передавайте тяжёлую работу рабочим процессам задач.
Изучай PHP с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 49
- Уроки
- 195
Часто задаваемые вопросы
Урок «Высокопроизводительные серверы со Swoole» бесплатный?
Да — полный текст урока «Высокопроизводительные серверы со Swoole» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс PHP Academy, подпишись на CoddyKit PRO. Курс PHP Academy содержит 4 уроков всего.
Чему я научусь в уроке «Высокопроизводительные серверы со Swoole»?
Обслуживайте тысячи соединений с помощью корутин Swoole Ты практикуешь PHP Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать PHP Academy?
Предыдущий опыт не требуется. PHP Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Высокопроизводительные серверы со Swoole»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке PHP Academy?
Да. Каждый урок PHP Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Модель конкурентности PHP
- Волокна PHP 8.1
- Циклы событий с ReactPHP
- Высокопроизводительные серверы со Swoole