Publisher confirms и неизвестный исход публикации
Consumer acknowledgement говорит брокеру, что получатель завершил свою работу. Теперь рассмотрим обратное направление: как издатель узнаёт, что брокер принял сообщение. Результат урока — publisher confirms вместе с проверкой отсутствующего маршрута. Отдельно сохраним неизвестный исход, когда публикация могла состояться, но подтверждение не дошло.
Продолжаем publish.py, broker.py и будущий relay итогового проекта. Брокер, сбои связи и повторная публикация не запускались. Официальная документация confirms объясняет независимость publisher confirms и consumer ack, а API BlockingChannel проверено по первичной документации Pika.
Запись в сокет не является принятием брокером
Без специального протокола вызов публикации может означать, что клиент сформировал кадры и отправил их по соединению. В этот момент приложение ещё не обязано знать, сохранена ли работа нужными объектами брокера. Потеря сети после отправки создаёт неопределённость. TCP решает передачу байтов на своём уровне, но не подтверждает наш предметный факт обработки.
Включаем подтверждения на канале через confirm_delivery. BlockingChannel в выбранном режиме связывает публикацию с ожиданием подтверждения или соответствующим отказом. Благодаря этому publish.py печатает broker confirmed только после успешного возврата publish_event. Это наблюдение относится к ответственности RabbitMQ, а не к отдельным эффектам catalog и audit.
Следующий фрагмент находится в полном издателе после создания channel и объявления топологии:
channel.confirm_delivery()
publish_event(channel, event)
print('broker confirmed:', event['eventId'])
Канал имеет собственную историю публикаций и подтверждений. После отказа connection новый channel не сохраняет прежние объекты Python и не превращает неопределённый исход в отрицательный. Приложение должно опираться на устойчивый конверт, который оно намеревалось отправить, и решить, какие факты требуют повторения.
В нашем маленьком издателе один файл публикуется последовательно. Такой режим удобен для изучения, но не объявляется оптимальным по скорости. Пакетные и асинхронные подтверждения способны повысить пропускную способность, однако требуют хранения состояния нескольких незавершённых публикаций. В этой серии не скрываем эту сложность за выдуманным обещанием производительности.
Confirm и return отвечают на разные вопросы
Exchange может принять публикацию, не найдя подходящей очереди. Если по предметному договору это допустимая рассылка необязательным слушателям, сообщение может исчезнуть без получателя. Для «Доставки» хотя бы одно назначение важно, поэтому ставим mandatory=True. При отсутствии маршрута broker возвращает сообщение издателю.
В режиме confirms Pika сигнализирует непригодную маршрутизацию через соответствующее исключение публикации. Полный CLI не скрывает этот отказ. Он не печатает успех и закрывает connection в finally, если связь ещё открыта. Проверка обязательного маршрута помогает обнаружить ошибочное имя события или отсутствующий binding, но не доказывает корректность всех подписок.
Предположим, catalog связан с exchange, а audit случайно не объявлен. Сообщение нашло хотя бы одну очередь, поэтому mandatory не обязан сигнализировать проблему. Если предметное требование включает оба получателя, нужно отдельно контролировать топологию и состояния обработки. Publisher confirm не перечисляет нам завершённые бизнес-эффекты каждого сервиса.
Три разных наблюдения можно сопоставить так:
| Наблюдение | Что становится известно |
|---|---|
| Confirm принят | Broker принял ответственность в соответствующем режиме |
| Return по mandatory | Не найден требуемый маршрут к очереди |
| Commit consumer | Локальный предметный эффект этого получателя сохранён |
Эта таблица не объединяет события в одну транзакцию. Сведения могут появляться в разных процессах и в разные моменты. Чтобы показывать единый пользовательский статус, потребуется прикладной протокол квитанций или проверка производной проекции. В нашем API позже показывается именно доставка брокеру, а не успех всех consumer.
Подтверждение потеряно после принятия
Издатель отправил sample-event, broker разместил его в очереди, а затем соединение оборвалось перед получением confirm. Если издатель повторит прежний конверт, в очереди могут оказаться две доставки одного eventId. Это правильная осторожность при неизвестном исходе, если получатель умеет обрабатывать повтор. Отказ от повторения, напротив, рискует потерять событие в варианте, где первое не было принято.
Поэтому неопределённость нельзя переводить в утверждение «ничего не произошло». Она требует сохранённого намерения доставки. В одиннадцатом уроке outbox будет хранить тело события до подтверждения, а relay отметит его отправленным только после успешного вызова. Если процесс упадёт между confirm и отметкой SQLite, тот же eventId отправится снова.
Ожидаемая схема неизвестного исхода:
Публикация отправлена
Connection прервалась до известного confirm
Факт в broker: может существовать или отсутствовать
Повтор: прежний eventId и прежнее тело
Consumer: обязан различать дубликат и новый факт
Здесь не задано точное число сообщений или время появления повтора. Это зависит от выполненного сценария, которого автор не проводил. Важно предметное решение: сохранённое намерение не теряется только потому, что клиент не смог прочитать ответ. Идентичность события не меняется при транспортной неопределённости.
Сохранность брокера имеет собственные условия
Confirms работают в контексте конкретной очереди и её условий хранения. Наши durable classic queues одного узла не дают репликации на несколько машин. Подтверждённое сообщение не становится независимым от потери всех его носителей. Для рабочих требований нужно изучать quorum queues, доступность большинства реплик и поведение при отказах инфраструктуры.
Не следует смешивать confirm со snapshot исходной системы. Если publisher создал событие только в оперативной памяти, затем процесс упал до отправки, broker не знает об этой работе. Подтверждение защищает уже начатый обмен, но не связывает изменение материала с формированием факта. Именно эту пропасть между локальной транзакцией и сетевым действием решает outbox.
В полном relay состояние pending сохраняется при исключении публикации. Журнал выводит тип ошибки, а не URI с паролем. Автоматическое повторение имеет паузу и ограниченную скорость reconnect. Даже хороший протокол способен перегрузить временно недоступный broker, если тысячи клиентов одновременно выполняют бесконечный быстрый цикл.
Важна и граница тайм-аута: отсутствие подтверждения в выбранное время не обязательно является отрицательным подтверждением broker. Оно может означать задержку или разрыв связи после успешного принятия. Поэтому интерфейс relay сохраняет pending до известного успеха и допускает повтор того же события. Состояние «ожидается» точнее отражает знание приложения, чем уверенное сообщение о том, что факт отвергнут.
После урока вы можете объяснить два направления подтверждений и третий независимый предметный commit. Для упражнения уберите все bindings и предскажите роль mandatory, затем восстановите только catalog и объясните предел такой проверки. Далее построим ограниченные отложенные повторы, сохранив событие и отдельную попытку конкретного получателя.