Описать в спецификации поведение интеграции при сбоях: таймаут, какие ошибки повторять и с какими паузами, ключ идемпотентности, отбрасывание дублей, очередь недоставленных сообщений и outbox.
Когда ответа нет
Когда ответа нет
Таймаут — не отказ. Если ответ не пришёл, операция могла выполниться: деньги списаны, а ответ потерялся по дороге. Неизвестный исход — главный источник дублей.
Повторяют только исходы, которые могут пройти со второго раза, — они в таблице ниже. Повторы ограничивают:
• число попыток, например 3;
• паузы растут вдвое (1 с, 2 с, 4 с…) и сдвигаются на случайную величину (jitter), чтобы тысячи клиентов не ударили одновременно;
• общий бюджет времени на все попытки.
Ключ идемпотентности. Безопасно повторять только идемпотентное, а POST /orders не идемпотентен. Поэтому клиент генерирует уникальный Idempotency-Key (UUID) на одну попытку оформления и шлёт его в каждом повторе. Сервер хранит ключ с результатом (например, 24 часа) и на повтор возвращает тот же ответ. Тот же ключ с другим телом — ошибка, обычно 422. Это устоявшаяся практика (Stripe и др.) и черновик IETF, а не RFC.
Получатель событий отбрасывает дубли (Idempotent Receiver): хранит обработанные eventId в таблице processed_events и пропускает повторы. Сообщение, которое не обработалось за N попыток или битое, уходит в очередь недоставленных сообщений (DLQ), чтобы не стопорить остальные; за DLQ следят и разбирают её.
Двойная запись: заказ сохранён в базе, а событие в брокер не ушло (или наоборот). Решение — transactional outbox: событие пишется в таблицу outbox в той же транзакции, что и заказ, а отдельный процесс публикует его в брокер — хотя бы раз.
Исход → действие
| Исход | Повторять? | Как |
|---|---|---|
| таймаут, обрыв связи | да | только если операция идемпотентна или есть ключ |
| 502 / 503 / 504 | да | с растущей паузой и jitter |
| 429 | да | после паузы из Retry-After |
| 400 / 422 | нет | исправить запрос; битое сообщение — в DLQ |
| 401 / 403 / 404 | нет | без исправления ответ будет тем же |
| 409 | нет | перечитать состояние ресурса |
Конспект урока
Когда ответа нет
Таймаут — не отказ. Если ответ не пришёл, операция могла выполниться: деньги списаны, а ответ потерялся по дороге. Неизвестный исход — главный источник дублей.
Повторяют только исходы, которые могут пройти со второго раза, — они в таблице ниже. Повторы ограничивают:
• число попыток, например 3;
• паузы растут вдвое (1 с, 2 с, 4 с…) и сдвигаются на случайную величину (jitter), чтобы тысячи клиентов не ударили одновременно;
• общий бюджет времени на все попытки.
Ключ идемпотентности. Безопасно повторять только идемпотентное, а POST /orders не идемпотентен. Поэтому клиент генерирует уникальный Idempotency-Key (UUID) на одну попытку оформления и шлёт его в каждом повторе. Сервер хранит ключ с результатом (например, 24 часа) и на повтор возвращает тот же ответ. Тот же ключ с другим телом — ошибка, обычно 422. Это устоявшаяся практика (Stripe и др.) и черновик IETF, а не RFC.
Получатель событий отбрасывает дубли (Idempotent Receiver): хранит обработанные eventId в таблице processed_events и пропускает повторы. Сообщение, которое не обработалось за N попыток или битое, уходит в очередь недоставленных сообщений (DLQ), чтобы не стопорить остальные; за DLQ следят и разбирают её.
Двойная запись: заказ сохранён в базе, а событие в брокер не ушло (или наоборот). Решение — transactional outbox: событие пишется в таблицу outbox в той же транзакции, что и заказ, а отдельный процесс публикует его в брокер — хотя бы раз.