Идемпотентность
Зачем
Обеспечить Fault Tolerance и реализовать паттерн Failure.
Проблема:
- Гонки сообщений, запросов изменения, чтения
- Типичны для распределенных систем: сетевые ошибки происходят регулярно, железо регулярно выходит из строя, таймауты, перезапросы, дубли
- Альтернатива использование техник оптимистичного управления параллелизмом
Решение:
- Идемпотентным называют такой метод API, повторный вызов которого не меняет состояние ресурса\сущности. НО: результат идемпотентного вызова может меняться.
- Использования уникального ключа идемпотентности для каждой транзакции
Паттерны
В ИС потребителе
- ключ идемпотентности генерится
- необходимо отделить создание idempotency_key от области передачи отправки, практика: Добавить шаг подтверждения перед шагом отправки
- поле заголовка HTTP Idempotency-Key
- Ключ идемпотентности обязательно создавать в формате GUID. Рекомендуется версия 4.
На сервере ИС источник
- Необходимо проверять, что параметры входящего запроса совпадают с параметрами существующего запроса с таким же ключом идемпотентности.
- Варианты ответов (применение от необходимости обработки на клиенте)
- AWS отдает ошибку IdempotentParameterMismatch в таком случае
- 400 Bad Request, указывающее, что полезная нагрузка не соответствует полезной нагрузке исходного запроса
- Чтобы детерминировано определить ключ идемпотентности как для изначального сообщения, так и для повторно обработанного, можно использовать хеш его содержимого или хеш уникальных для сообщения полей.
- уникальность ключа должна проверяться не глобально, а только применительно к конкретному пользователю и конкретной операции
- Варианты ответов (применение от необходимости обработки на клиенте)
- необходимо хранить ключ идемпотентности
- инсертится в базу в поле, на котором есть ограничение базы данных по уникальности. Если это ограничение не дало сделать инсерт, то отдавать ошибку (HTTP status 409).
- или NOSQL (Redis)
- необходимо определить время жизни (TTL) ключа идемпотентности исходя из бизнес логики бизнес операции
- если ключ найден сервер\прокси\middleware может отдать предыдущий ответ (response, header) из кэша с учетом TTL, без выполнения запроса
- Когда приходит дублирующий запрос, в то время как исходный все еще обрабатывается (параллельные запросы). Ответы на идемпотентные операции различаются в зависимости от состояния исходного запроса:
- Завершено успешно – исходный код состояния и текст ответа извлекаются из кэша, например Momento, и возвращаются вызывающему абоненту.
- Завершено с ошибкой — повторная попытка операции, как если бы она была исходной.
- Выполняется — возвратить успешное выполнение и не выполнять никаких операций.
REST HTTP методы
- идептоментны всегда: GET, PUT, DELETE
- необходимо реализовать идептоментность
- POST - клиент должен контролировать повторные запросы, если не получил ответа
- PATCH - если клиент не получил ответа на запрос, просто направляет повторно
- Прокси-серверы могут не повторять POST и PATCH запросы при ошибках, тогда как GET и PUT могут повторить.
Message Bus
- Чтобы гарантировать идемпотентность MQ, необходимо гарантировать, что потребители не будут повторно использовать одно и то же сообщение.
Синхронизировать знание о списке ресурсов на клиенте и сервере через версионирование этого списка
При создании заказа приложение передает в отдельном поле или заголовке If-Match версию, о которой он знает.
- Сервер атомарно с изменением увеличивает версию при любых изменениях заказов (создание, отмена, редактирование).
- То есть приложение в запросе к серверу говорит ему, какое состояние заказов оно знает. И если это состояние заказов (версия) расходится с тем, что хранится на сервере, то сервер отдает ошибку «заказы были изменены параллельно, перезагрузите информацию о заказах».
Технологии
- .NET MediatR (паттерн медиатор) и PipelineBehavior (паттерн chain of responsibility + decorator/proxy) (https://temofeev.ru/info/articles/borba-s-dublikatami-delaem-post-idempotentnym/)
- IdempotentAPI with Cache Store and support Cluster Environment
- .net sample with Cache Store Redis