⌛ Идемпотентность в распределённых системах
Идемпотентность — свойство операции, гарантирует, что повторное выполнение одной и той же операции приведёт к такому же результату, как и первое выполнение
Т.е. если отправить один и тот же запрос 10 раз, результат должен быть таким же, как если отправить его один раз
🟢пример: если повторное списание денег с карты не приводит к новому списанию, а возвращает результат первого списания — операция идемпотентна
Если же деньги спишутся дважды — нарушение идемпотентности
В распределённых системах сетевая ненадёжность, таймауты и ретраи делают это свойство критически важным
Примеры проблем обеспечения идемпотентности
🔘повторные сетевые запросы: клиент не получил ответ вовремя (из-за таймаута, сетевого сбоя) и отправил запрос повторно. Сервер успешно обработал первый запрос
🔘дубли сообщений в брокерах: гарантируют доставку как минимум одни раз (at-least-once). При сбое потребителя сообщение может быть доставлено повторно
🔘неопределённость состояния при сбоях: cервис обработал запрос, но упал до того, как отправил подтверждение. Оркестратор (н-р, Kubernetes) перезапустит контейнер, и обработка перезапустится
Примеры последствий отсутствия идемпотентности
➖фин потери (двойные списания)
➖нарушение консистентности данных (два одинаковых пользователя в БД)
➖времязатратные отладки и исправления данных
Подходы к реализации идемпотентности
Уникальный id запроса (Request ID)
Клиент генерирует уникальный ID (UUID) для каждой бизнес-операции и передаёт в каждом запросе (например, в HTTP-заголовке Idempotency-Key)
Сервер, получив запрос, проверяет, не обрабатывался ли уже запрос с таким ID
🟣Если нет — выполняет операцию и сохраняет результат в быстрое хранилище (н-р, Redis) с ключом = ID
🟣Если да — возвращает сохранённый ранее ответ, не выполняя операцию повторно
✅ Плюсы: относительно просто, универсальность (подходит для REST, gRPC, Webhooks)
✖️ Минусы: требует наличия быстрого хранилища состояния для всех инстансов сервиса. Необходимо определять TTL для ключей
Подходит для идемпотентных POST-запросов в API (н-р, создание платежа, заказа)
Журналирование и Outbox-паттерн
Паттерн для надежной отправки сообщений в брокер в контексте транзакции с БД
🔘сервис не отправляет сообщение в брокер напрямую
🔘он в рамках одной транзакции записывает сообщение в специальную таблицу в БД (outbox)
🔘отдельный процесс (CDC) считывает новые записи из outbox и публикует их в брокер
🔘после успешной публикации запись из outbox удаляется
✔️ гарантирует отправку сообщения в брокер только тогда, когда бизнес-транзакция commit'ится. Решает проблему дублей на стороне отправителя
✖️ архитектурная сложность, необходимость настройки и поддержки CDC-процесса
Микросервисные асинхронные интеграции, где критична гарантия доставки события после записи в БД
Exactly-Once на уровне брокеров
Брокеры предлагают встроенные механизмы для обеспечения семантики "точно один раз"
🟣Для продюсеров: использование transactional id и подтверждений от всех партиций (acks=all) гарантирует, что сообщение не будет потеряно и не будет записано дублем
🟣Для консьюмеров: чтение сообщения и commit offset'а происходят атомарно. Консьюмер не получит одно и то же сообщение дважды после успешной обработки и коммита
✔️ высокая надёжность "из коробки"
✖️ сложность конфигурации, расходы на производительность, привязка к конкретной технологии
В проектах на Kafka, где требования к надёжности обработки потоков данных крайне высоки
📎 Материалы
1. Идемпотентность: что это, примеры и применение в API
2. Как сделать хорошую интеграцию? Часть 2. Идемпотентные операции – основа устойчивой интеграции
3. История одного идемпотентного метода
4. Идемпотентность в такси-приложении: кейс из практики
5. Стажёр Вася и его истории об идемпотентности API
6. Важность идемпотентности в распределенных системах
🔹 Обеспечение идемпотентности API
📚 Книги
1. Высоконагруженные приложения - Мартин Клеппман (Глава 11)
2. Создание микросервисов - Сэм Ньюмен (Глава 12)
#проектирование
➿➿➿➿➿➿➿➿
🧑🎓 Больше полезного в базе знаний по системному анализу
Post #629
18.8K
- 👍 34
- ❤ 16
- 🔥 6
- 👏 1