Почему нужно делать запросы идемпотентными, и как это реализовать
Когда проектируешь современное приложение — особенно если это распределенная система (микросервисы) — нужно быть предельно аккуратным.
Потому что всё, что проходит по сети — может потеряться. Сеть ненадежная штука, она отваливается: происходят сбои, задержки, пакеты могут не доходить или идти долго.
Допустим, у нас банковская система. Сервис №1 (клиент) отправляет другому сервису №2 (сервер) запрос: "Зачислить 100 рублей на счёт Васи".
1. Сервер обработал, деньги зачислил (в БД байтики подвигал).
2. Но клиент не получил ответ (он долго шел или сеть оборвалась)
3. Клиент решает: «Ну ладно, отправляем еще раз».
4. И отправляет тот же самый запрос. Сервер получает его и... снова зачисляет 100 рублей.
Поздравляю. Мы только что подарили Васе 100 рублей.
Это реальность в распределённых системах. Это случается, если ты не реализуешь идемпотентность.
💡 Что вообще такое идемпотентность?
Если по-простому: это такое поведение, при котором многократный вызов одной и той же операции с одними и теми же параметрами даёт один и тот же результат.
— Умножение любого числа на 1 — идемпотентно (или прибавление 0).
— Повторные запросы GET/PUT/DELETE идемпотенты
А вот "прибавить к балансу 100 рублей" — не идемпотентно. Если сделать ее два раза — результат будет другим.
Обычно, POST-запросы — не идемпотентные.
💡 Как нам сделать так, чтобы повторный запрос не привёл к повторной операции?
1. Заводим idempotency key — уникальный идентификатор запроса. Передаем в заголовке X-Idempotency-Key
2. Каждый запрос, который может быть ретрайнут, должен иметь этот ключ.
3. Это может быть UUID, который генерится на клиенте или где-то на гейтвее.
Когда сервер получает запрос:
1. Он смотрит, не обрабатывал ли он уже запрос с таким ключом.
2. Если да — он возвращает результат той самой обработки, без повторения действий.
3. Если нет — выполняет операцию и сохраняет результат вместе с ключом.
💾 Где и как хранить обработанные ключи?
Есть несколько популярных вариантов:
— Redis — идеально для временного хранения. Можно задать TTL (время жизни ключа), чтобы не захламлять память. Быстро, просто. Есть риск потерять данные.
— PostgreSQL или другая SQL-БД — если нужна надёжность и долговечность. Но медленнее. Если у вас несколько тыс RPS, то спокойно подойдет.
— В самой бизнес-сущности — например, если обрабатываешь платёж, можно добавить поле "идемпотентный ключ", и искать по нему. Либо в этом случае ключом идемпотентности может выступать уже существующий идентификатор операции, а не какой-то искусственный ключ.
TTL — почти обязательно. Иначе за год таких ключей накопится миллиарды. Обычно TTL ставят от 1 часа до пары суток — зависит от критичности. Если нужны очень жесткие гарантии и запросы могут долететь через недели - можно хранить несколько лет или всегда.
⚠️ Какие есть нюансы?
— Запрос ещё в процессе — возвращаем статус типа "Processing", и говорим клиенту подождать (можно с Retry-After) + HTTP code 202
— Ошибка при выполнении: нужно различать ошибки:
• Системная (например, упала БД) — можно ретраить.
• Валидация или бизнес-ошибка — ретрай не поможет
Короче: на каждый запрос с ключом — должен быть один и только один побочный эффект.
Система без идемпотентности ненадежна и может приводить к неожиданным и нежелательным последствиям. Это, кстати, относится не только к входящим http запросам. Может быть такое, что при отправке в брокер сообщение задублировалось — на потребителе нужно проверять дубликаты (реализовать можно аналогично через заголовок и ключ)
👍 — знал
🔥 — не знал
#хардовая_польза
Post #212
4.99K
- 🔥 90
- 👍 46
- ❤ 9
- ✍ 2