Ответ
Гарантия доставки (delivery guarantee) в асинхронных системах, построенных вокруг брокеров сообщений (Message Brokers), обеспечивается комбинацией нескольких ключевых механизмов. Без них сообщения могли бы теряться при сбоях.
Основные механизмы:
-
Сохранение (Persistence) сообщений и метаданных.
- Сообщение, принятое брокером (например, RabbitMQ, Apache Kafka), записывается на диск или реплицируется в кластер до того, как брокер подтвердит его получение отправителю (Publisher Acknowledgement). Это защищает от потери данных при падении брокера.
- Очереди (queues) и топики (topics) также объявляются как устойчивые (durable).
-
Подтверждение потребителя (Consumer Acknowledgement).
- После успешной обработки сообщения потребитель (Consumer) должен явно отправить брокеру подтверждение (ACK). Только получив его, брокер удаляет сообщение из очереди.
- Если подтверждение не пришло (таймаут) или потребитель явно отказался (NACK), брокер либо возвращает сообщение в очередь для повторной обработки, либо перемещает его в "мертвую букву" (Dead Letter Queue).
-
Повторные попытки (Retries) и отказоустойчивость.
- Потребитель или сама инфраструктура могут реализовывать политики повторных попыток при временных сбоях (например, недоступность БД).
-
Идемпотентность обработчика.
- Гарантия "хотя бы раз" (at-least-once delivery), которую дают многие системы, означает, что одно и то же сообщение может быть доставлено потребителю несколько раз. Поэтому логика обработки должна быть идемпотентной: повторная обработка того же сообщения не должна вызывать побочных эффектов (например, двойное списание средств). Этого можно достичь через проверку уникальных ID сообщений или использование механизмов оптимистичной блокировки.
Практический пример с RabbitMQ и PHP (библиотека php-amqplib):
// 1. Отправитель (Publisher) с подтверждением и сохранением на диск
$channel->confirm_select(); // Включаем режим подтверждений от брокера
$channel->queue_declare('task_queue', false, true, false, false); // Durable queue
$msg = new AMQPMessage(
$body,
['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT] // Persistent message
);
$channel->basic_publish($msg, '', 'task_queue');
$channel->wait_for_pending_acks(); // Ждем подтверждения от брокера
// 2. Потребитель (Consumer) с ручным подтверждением
$channel->basic_consume(
'task_queue',
'',
false,
false, // <-- Важно: auto_ack = false (отключаем авто-подтверждение)
false,
false,
function (AMQPMessage $msg) {
try {
echo " [x] Received ", $msg->body, "n";
// Имитация работы...
processMessage($msg->body);
// 3. Явное подтверждение успешной обработки
$msg->ack();
echo " [x] Donen";
} catch (Exception $e) {
echo " [x] Failed: ", $e->getMessage(), "n";
// 4. Отказ с требованием повторной доставки
$msg->nack(true); // Параметр `requeue = true`
}
}
);
Таким образом, гарантия доставки — это не один механизм, а целая стратегия, реализуемая на уровне брокера, отправителя и потребителя.