Publish/Subscribe и гарантии доставки
Один издатель — много подписчиков, и при этом сообщение почти всегда доставляется «как минимум один раз».
Publish/Subscribe (pub/sub) — паттерн, где издатель публикует событие в тему, а все подписчики на эту тему получают копию, ничего не зная друг о друге.
Чем pub/sub отличается от очереди
В обычной очереди сообщение забирает один consumer и оно исчезает. В pub/sub событие получают все подписчики. Пример: пользователь оформил заказ → событие order.created → его независимо получают сервис склада, сервис уведомлений и сервис аналитики. Издатель не знает об их существовании — добавить нового подписчика можно, не трогая издателя.
┌──▶ [ склад ]
[ заказ ] ─order.created─┼──▶ [ уведомления ]
└──▶ [ аналитика ]
Издатель не знает о подписчиках; добавляем нового — код издателя не меняем.
Гарантии доставки
Сеть ненадёжна: подтверждение может потеряться, и тогда отправитель не знает, дошло ли сообщение. Отсюда три уровня гарантий — и у каждого своя цена.
| Гарантия | Смысл | Риск |
| At-most-once | не повторяем отправку | сообщение может потеряться |
| At-least-once | повторяем, пока не подтвердят | возможны дубли |
| Exactly-once | ровно один раз | дорого и сложно достичь честно |
На практике массово используют at-least-once: лучше доставить дважды, чем потерять. Но тогда обработчик обязан корректно переживать дубли — иначе письмо уйдёт дважды, а деньги спишутся повторно.
Идемпотентность — лекарство от дублей
Идемпотентная операция — операция, повторное выполнение которой даёт тот же результат, что и однократное.
Если обработка идемпотентна, дубли при at-least-once безвредны. Типичный приём — ключ идемпотентности: у каждого сообщения есть уникальный id, обработчик запоминает обработанные id и игнорирует повтор.
При получении сообщения с id = X:
обрабатывали X раньше?
да → пропустить (уже сделано)
нет → выполнить, записать X как обработанный
Так «списать деньги» становится безопасным к повтору: второй приход того же id ничего не спишет. «Exactly-once на практике» — это почти всегда «at-least-once + идемпотентность», а не магия брокера.
Примеры идемпотентных и неидемпотентных операций
| Идемпотентно | Неидемпотентно (опасно при повторе) |
| «установить статус = paid» | «увеличить баланс на 100» |
| «удалить пользователя X» | «отправить ещё одно письмо» |
| HTTP PUT, DELETE | HTTP POST без ключа идемпотентности |
Итог
- Pub/sub рассылает событие всем подписчикам; издатель о них не знает — легко добавлять новых.
- At-least-once — стандарт: лучше дубль, чем потеря, но обработчик должен переживать повторы.
- Идемпотентность (ключ id) делает дубли безвредными; «exactly-once» на практике — это at-least-once + идемпотентность.