Как обеспечить надежность на конечных точках при ненадежных сервисах и надежном брокере сообщений
Что проверяют
Понимаете ли вы, что надёжный брокер сам ничего не гарантирует: сообщения теряются и дублируются на границе между брокером и сервисом. Ждут разговора про подтверждения, повторы и идемпотентность. Следом спрашивают, как атомарно записать в базу и отправить событие.
Как отвечать
Разберите обе границы.
Продюсер пишет в базу и публикует событие; если он упадёт между этими шагами, данные и события разойдутся. Решение: transactional outbox. Событие пишется в таблицу outbox в той же транзакции, что и бизнес-данные, а отдельный процесс (или Debezium через CDC) переносит его в брокер. На стороне брокера включают publisher confirms в RabbitMQ или acks=all с идемпотентным продюсером в Kafka.
Консьюмер:
- подтверждение после обработки: ручной ack в RabbitMQ (в
aio-pikaчерезasync with message.process()), в Kafkaenable.auto.commit=falseи коммит offset после записи результата, в Celerytask_acks_late=True; - идемпотентность: доставка at-least-once, дубли будут. Таблица обработанных
message_idс уникальным ключом или upsert по бизнес-ключу; - повторы: экспоненциальная задержка с джиттером (
tenacity), ограниченное число попыток, дальше DLQ; - таймауты и circuit breaker: у каждого внешнего вызова явный таймаут (
httpxпо умолчанию ждёт 5 секунд,requestsждёт бесконечно), при серии ошибок breaker перестаёт дёргать упавший сервис; - ограничение параллелизма:
prefetch_countв RabbitMQ, чтобы воркер не набирал сотни неподтверждённых сообщений.
Многошаговые операции между сервисами ведут сагой с компенсирующими действиями. Следят за длиной очереди, возрастом самого старого сообщения и размером DLQ.
Где спотыкаются
Подтверждают сообщение сразу при получении. Процесс падает посреди обработки, и сообщение потеряно, хотя брокер «надёжный».
Ретраят без лимита и без задержки. Упавший сервис получает шквал повторов, а очередь забивает ядовитое сообщение, которое не обработается никогда.
Считают, что дублей не будет. Повторная доставка случается при ребалансе или рестарте воркера, и без идемпотентности клиенту приходит два списания.