PHP Academy · Урок

Высокопроизводительные серверы со Swoole

Обслуживайте тысячи соединений с помощью корутин Swoole

Урок 4 из 413 шагов

«Высокопроизводительные серверы со 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 — локальная установка не требуется.

Все уроки этого курса

  1. Модель конкурентности PHP
  2. Волокна PHP 8.1
  3. Циклы событий с ReactPHP
  4. Высокопроизводительные серверы со Swoole
← Назад к PHP Academy