Представь: ты запускаешь сайт, а он начинает тормозить. Пользователи жалуются, что письма приходят через час, а картинки загружаются полдня. Знакомая боль? Чаще всего проблема в том, что PHP делает всё сразу: и письма шлёт, и картинки ресайзит, и данные обрабатывает. А нужно просто разделить задачи. Вот тут и приходят на помощь очереди. В этой статье я расскажу, как очереди спасают производительность, и покажу на примерах, как их внедрить в PHP-приложение.
Что такое очередь и зачем она нужна в PHP
Очередь — это как стопка тарелок в ресторане. Повар готовит блюда, официант забирает их по очереди. В программировании то же самое: ты кладёшь задачу в очередь, а специальный обработчик (воркер) забирает её и выполняет. При этом PHP-скрипт, который обрабатывает запрос пользователя, не ждёт завершения задачи. Он просто кладёт её в очередь и идёт дальше.
Зачем это нужно? Представь, что пользователь регистрируется на сайте. Нужно отправить письмо, создать папку для файлов, записать в лог. Если делать всё сразу, ответ пользователю придёт через 10 секунд. А если отправить задачу в очередь — ответ придёт мгновенно, а письмо уйдёт через пару секунд.
Очереди незаменимы в таких сценариях:
- Отправка email-уведомлений
- Обработка изображений (ресайз, сжатие)
- Генерация отчётов
- Импорт больших файлов
- Взаимодействие с внешними API
Основные компоненты очереди
Любая очередь состоит из трёх частей:
- Продюсер — это твой PHP-код, который кладёт задачу в очередь.
- Брокер сообщений — сервер, который хранит задачи. Например, RabbitMQ, Redis или база данных.
- Консюмер (воркер) — скрипт, который забирает задачи и выполняет их.
В PHP чаще всего используют Redis или RabbitMQ. Redis — это быстрое хранилище в памяти. RabbitMQ — полноценный брокер с маршрутизацией. Выбор зависит от задачи.
Как это работает на практике: пример с Redis
Давай посмотрим на простой пример. Допустим, нужно отправлять письма после регистрации. Мы будем использовать библиотеку predis/predis для работы с Redis.
Сначала устанавливаем библиотеку через Composer:
composer require predis/predisТеперь напишем продюсера — код, который добавляет задачу в очередь:
<?php
require 'vendor/autoload.php';
$client = new Predis\Client([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);
// Данные для письма
$emailData = [
'to' => 'user@example.com',
'subject' => 'Добро пожаловать!',
'body' => 'Спасибо за регистрацию.',
];
// Добавляем задачу в список Redis
$client->rpush('email_queue', json_encode($emailData));
echo 'Задача добавлена в очередь.';Этот код просто кладёт данные в список Redis. Теперь напишем консюмера — скрипт, который будет забирать задачи и отправлять письма:
<?php
require 'vendor/autoload.php';
$client = new Predis\Client([
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
]);
while (true) {
// Ждём задачу из списка (блокирующий вызов)
$task = $client->blpop('email_queue', 0);
if ($task) {
$emailData = json_decode($task[1], true);
// Здесь должна быть функция отправки письма
// mail($emailData['to'], $emailData['subject'], $emailData['body']);
echo 'Отправлено письмо на ' . $emailData['to'] . PHP_EOL;
}
}Этот скрипт запускается в фоне и бесконечно проверяет очередь. Как только появляется задача — он её выполняет. Такой подход называется «воркер». Его можно запустить через командную строку: php worker.php.
Продвинутый вариант: RabbitMQ и библиотека php-amqplib
Redis — это хорошо для простых задач. Но если нужно управлять маршрутизацией, подтверждениями или настраивать обменники, лучше взять RabbitMQ. Он сложнее, но гибче.
Установим библиотеку:
composer require php-amqplib/php-amqplibПродюсер на RabbitMQ:
<?php
require 'vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
// Объявляем очередь
$channel->queue_declare('task_queue', false, true, false, false);
$data = json_encode([
'user_id' => 123,
'action' => 'send_welcome_email',
]);
$msg = new AMQPMessage($data, ['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]);
$channel->basic_publish($msg, '', 'task_queue');
echo 'Задача отправлена в RabbitMQ.';
$channel->close();
$connection->close();Консюмер для RabbitMQ:
<?php
require 'vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('task_queue', false, true, false, false);
echo 'Ожидание задач...';
$callback = function ($msg) {
$data = json_decode($msg->body, true);
echo 'Обрабатываю задачу для user_id: ' . $data['user_id'] . PHP_EOL;
// Подтверждаем выполнение
$msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
};
$channel->basic_qos(null, 1, null);
$channel->basic_consume('task_queue', '', false, false, false, false, $callback);
while ($channel->is_consuming()) {
$channel->wait();
}
$channel->close();
$connection->close();Разница в том, что RabbitMQ гарантирует доставку даже при падении сервера. И поддерживает сложные маршруты.
Сравнение популярных брокеров для PHP
| Брокер | Скорость | Надёжность | Сложность настройки | Популярность в PHP |
|---|---|---|---|---|
| Redis (списки) | Высокая | Средняя | Низкая | Очень высокая |
| RabbitMQ | Средняя | Высокая | Средняя | Высокая |
| Beanstalkd | Высокая | Средняя | Низкая | Средняя |
| БД (MySQL, PostgreSQL) | Низкая | Высокая | Низкая | Средняя |
Лайфхаки для работы с очередями
- Обрабатывай ошибки. Если задача упала, не теряй её. Используй механизм повторных попыток (retry). В RabbitMQ для этого есть dead letter queues.
- Не делай задачи слишком маленькими. Если каждая задача выполняется меньше секунды, накладные расходы на очередь будут больше пользы. Группируй.
- Мониторь очередь. Ставь метрики: количество задач, время выполнения. Иначе однажды очередь завалится, а ты узнаешь об этом от пользователей.
- Используй супервизор. Запускай воркеры через supervisor или systemd. Если воркер упадёт — он перезапустится автоматически.
BRPOPLPUSH. Он атомарно переносит задачу в отдельный список для обработки. Если воркер упадёт, задача не потеряется — она останется во втором списке.Что в итоге
Очереди — это не rocket science. Это просто способ не делать всё сразу. PHP отлично с ними дружит, если использовать правильные инструменты. Начни с Redis и простого продюсера-консюмера. Когда проект вырастет — перейдёшь на RabbitMQ. Главное — не бойся очередей. Они сэкономят тебе кучу нервов и времени.
Студия WNDER



