Зачем нужны асинхронные сообщения
Узнайте, как очереди разделяют производителей и потребителей
«Зачем нужны асинхронные сообщения» — бесплатный урок PHP Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения PHP Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс PHP Academy содержит 4 уроков всего.
Зачем нужны асинхронные сообщения
При синхронном запросе процесс PHP блокируется, пока взаимодействует с почтовыми шлюзами, платёжными системами или нижестоящими сервисами. Под нагрузкой это связывает задержку и доступность Вашего приложения с каждой вызываемой зависимостью. Асинхронные сообщения разрывают эту цепочку: производитель помещает сообщение в очередь и сразу возвращает управление, а потребители обрабатывают его позже, с собственной скоростью.
В этом уроке рассматривается зачем это нужно: для разрыва связей, буферизации и осмысления компромиссов между гарантиями доставки до начала работы с RabbitMQ или Kafka.
Временное и пространственное разделение
Очередь разделяет производителей и потребителей по двум направлениям:
- Пространственное — ни одной стороне не нужен адрес другой; они знают только о брокере.
- Временное — потребитель может быть недоступен в момент публикации сообщения производителем; сообщение подождёт.
В синхронном варианте ниже веб-запрос зависит от доступности и скорости почтового сервера.
<?php
// Synchronous: the HTTP request blocks on SMTP
function registerUser(string $email): void {
saveUser($email);
// If SMTP is slow or down, the user waits or the request fails
sendWelcomeEmail($email); // 800ms blocking call
echo "Registered\n";
}
function saveUser(string $e): void { /* ... */ }
function sendWelcomeEmail(string $e): void { usleep(1); }
registerUser('dev@example.com');Опубликовали — и работаем дальше
Асинхронный вариант сохраняет пользователя, публикует сообщение UserRegistered и возвращает управление. Отдельный рабочий процесс отправляет электронное письмо. Теперь задержка HTTP зависит только от быстрой публикации в локальном брокере, а не от времени обмена с SMTP-сервером.
<?php
function registerUser(string $email): void {
saveUser($email);
// Publish a lightweight event; worker handles email later
publish('user.registered', json_encode(['email' => $email]));
echo "Registered (email queued)\n";
}
function saveUser(string $e): void { /* ... */ }
function publish(string $routingKey, string $payload): void {
echo "-> queued $routingKey: $payload\n";
}
registerUser('dev@example.com');Выравнивание нагрузки (буферизация)
Всплески трафика неравномерны, а производственная мощность ограничена. Очередь выступает в роли буфера: она поглощает всплеск из 10 000 сообщений и позволяет пулу рабочих процессов извлекать их с устойчивой скоростью. Без очереди такой всплеск напрямую перегрузил бы базу данных или нижестоящий API.
Это и есть выравнивание нагрузки: Вы жертвуете задержкой (сообщения могут ненадолго задержаться) ради стабильности (ничего не выходит из строя).
Гарантии доставки
Каждая система обмена сообщениями даёт определённое обещание доставки. Важно знать, какое именно:
- Не более одного раза — отправили и забыли; сообщения могут потеряться, но дубликатов не будет.
- Не менее одного раза — сообщение доставляется повторно, пока не будет подтверждено; дубликаты возможны. Это распространённый вариант по умолчанию.
- Ровно один раз — ни потерь, ни дубликатов; это дорого и на уровне приложения часто оказывается лишь частичной иллюзией.
Поскольку большинство реальных систем обеспечивает доставку не менее одного раза, потребители должны допускать получение одного и того же сообщения дважды.
Идемпотентные потребители
Средство борьбы с дубликатами при доставке не менее одного раза — это идемпотентность: повторная обработка сообщения даёт тот же результат, что и однократная. Обычно используется ключ дедупликации (идентификатор сообщения), сохранённый в уникальном индексе.
<?php
function handle(array $msg, PDO $pdo): void {
$pdo->beginTransaction();
try {
// Unique constraint on message_id makes the insert the dedup gate
$stmt = $pdo->prepare(
'INSERT INTO processed_messages (id) VALUES (?)'
);
$stmt->execute([$msg['id']]);
} catch (PDOException $e) {
$pdo->rollBack();
echo "Duplicate {$msg['id']} skipped\n";
return; // already handled
}
chargeCustomer($msg['amount']);
$pdo->commit();
}
function chargeCustomer(int $a): void {}Подтверждения и повторная доставка
Потребитель сообщает об успехе с помощью подтверждения. Если он аварийно завершится до отправки подтверждения, брокер доставит сообщение повторно другому потребителю. Именно это обеспечивает доставку не менее одного раза, но означает, что ack должен выполняться после фиксации побочного эффекта, а не до неё.
Слишком раннее подтверждение при сбое приведёт к потере сообщения. Слишком позднее подтверждение при сбое приведёт к дубликату — его поглотит Ваш уровень идемпотентности.
<?php
// Pseudocode of the consumer contract
function consumeLoop($channel): void {
while ($msg = $channel->get()) {
try {
processSideEffect($msg); // commit DB write first
$channel->ack($msg); // only then ack
} catch (\Throwable $e) {
$channel->nack($msg, requeue: true); // let it redeliver
}
}
}
function processSideEffect($m): void {}Порядок не даётся бесплатно
Очереди не гарантируют глобальный порядок после масштабирования потребителей. Два рабочих процесса, получающие сообщения из одной очереди, обрабатывают их одновременно, поэтому сообщение B может завершиться раньше сообщения A.
Если порядок важен (например, для изменений баланса счёта), необходимо разделить сообщения по ключу, чтобы все связанные сообщения последовательно обрабатывались одним потребителем. Kafka изначально поддерживает это с помощью разделов; в RabbitMQ можно направлять сообщения по согласованному хешу в очереди для отдельных ключей.
Очереди недоставленных сообщений
Некоторые сообщения никогда не удастся обработать — например, из-за некорректных данных или ссылок на удалённые строки. Бесконечные повторные попытки блокируют очередь (это ядовитое сообщение). Используется следующий шаблон: очередь недоставленных сообщений (DLQ). После N неудачных попыток сообщение направляется отдельно для проверки, а не доставляется повторно.
<?php
function consume(array $msg, $channel): void {
$attempts = ($msg['headers']['x-attempt'] ?? 0) + 1;
try {
process($msg);
$channel->ack($msg);
} catch (\Throwable $e) {
if ($attempts >= 5) {
$channel->deadLetter($msg); // park in DLQ
} else {
$channel->republish($msg, ['x-attempt' => $attempts]);
}
}
}
function process(array $m): void {}Когда НЕ следует использовать очередь
Асинхронный обмен сообщениями создаёт реальные эксплуатационные издержки: необходимо поддерживать брокер, объяснять продукту согласованность со временем и усложнять отладку между границами процессов.
Выбирайте очередь, когда работа медленная, неравномерная, допускает повторные попытки или выполняется в фоновом режиме. Оставляйте её синхронной, когда вызывающей стороне действительно нужен результат немедленно (например, достоверная цена, которую пользователь должен увидеть): помещение вызова, которому нужен ответ, в очередь лишь добавит задержку и сложность.
Очередь и журнал
Эти шаблоны поддерживают две основные разновидности брокеров, которые используются в оставшейся части курса:
- Очередь задач (RabbitMQ) удаляет сообщение после его подтверждения. Она отлично подходит для распределения работы между конкурирующими потребителями, поддерживая маршрутизацию отдельных сообщений и время жизни сообщений.
- Журнал фиксации (Kafka) хранит сообщения в течение заданного срока; каждый потребитель отслеживает собственное смещение и может повторно проигрывать историю, а множество независимых групп потребителей читают один и тот же поток.
Выбирайте очередь для распределения работы, а журнал — для высокопроизводительных потоков событий и повторного проигрывания.
<?php
$useCase = 'replay events for a new analytics service';
$broker = str_contains($useCase, 'replay') || str_contains($useCase, 'stream')
? 'Kafka (commit log)'
: 'RabbitMQ (task queue)';
echo $broker . "\n";Быстрая проверка
Рассуждение о гарантиях доставки.
Повторение
Теперь у Вас есть представление о принципах асинхронного обмена сообщениями:
- Очереди обеспечивают пространственное и временное разделение и служат буфером для выравнивания нагрузки.
- Большинство систем обеспечивают доставку не менее одного раза, поэтому потребители должны быть идемпотентными.
- Выполняйте подтверждение после фиксации побочного эффекта, а при сбоях позволяйте доставлять сообщение повторно.
- Для сохранения порядка требуется разделение по ключу, а для ядовитых сообщений — DLQ.
- Не помещайте в очередь работу, для которой вызывающей стороне нужен синхронный ответ.
Далее Вы примените эти знания на практике с RabbitMQ в PHP.
Часто задаваемые вопросы
Урок «Зачем нужны асинхронные сообщения» бесплатный?
Да — полный текст урока «Зачем нужны асинхронные сообщения» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс PHP Academy, подпишись на CoddyKit PRO. Курс PHP Academy содержит 4 уроков всего.
Чему я научусь в уроке «Зачем нужны асинхронные сообщения»?
Узнайте, как очереди разделяют производителей и потребителей Ты практикуешь PHP Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать PHP Academy?
Предыдущий опыт не требуется. PHP Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «Зачем нужны асинхронные сообщения»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке PHP Academy?
Да. Каждый урок PHP Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Зачем нужны асинхронные сообщения
- Работа с RabbitMQ в PHP
- Apache Kafka с PHP
- Создание событийно-ориентированных рабочих процессов