Идемпотентность и gRPC
Два сюжета, где ломаются наивные API: ретраи, задваивающие эффект (оплата прошла дважды), и выбор протокола для межсервисного обмена. Собес проверяет, знаешь ли ты Idempotency-Key и понимаешь ли, что gRPC это трейдоф, а не «просто быстрее REST».
Типовые формулировки: «как защитить оплату от двойного списания при ретрае?», «когда gRPC, а когда REST?», «читается ли gRPC браузером».
Idempotency-Key против задвоения
Идемпотентная операция: повтор с теми же параметрами даёт тот же итог, что один вызов. Это критично для ретраев - они не задваивают эффект. POST по природе не идемпотентен: два POST /orders создадут два заказа, а сетевой ретрай оплаты реально приходит дважды.
Решение - Idempotency-Key: клиент шлёт уникальный ключ операции, сервер запоминает ключ вместе с результатом и на повтор с тем же ключом возвращает ПРЕЖНИЙ ответ, не выполняя эффект снова. Так одна оплата остаётся одной, сколько бы раз запрос ни повторился.
// Полагаться, что «POST не повторится», нельзя: ретраи сети, прокси и клиентов - обыденность, ключ обязателен для денежных операций.
POST /payments
Idempotency-Key: 7f3a-... # повтор с тем же ключом ->
# прежний ответ, второго списания нет- идемпотентность
- повтор операции не меняет итог
- Idempotency-Key
- ключ, по которому сервер не повторяет эффект POST
gRPC - трейдоф, не серебряная пуля
gRPC это RPC на protobuf поверх HTTP/2: бинарный формат, строгая .proto-схема, кодогенерация клиентов, стриминг из коробки. За счёт бинарности и HTTP/2 он быстрее JSON/REST, но не читается глазами и плохо работает напрямую из браузера.
Отсюда выбор по месту: gRPC уместнее во ВНУТРЕННЕМ межсервисном обмене, где важны скорость, строгая схема и стриминг. Наружу и в браузер берут REST/JSON - читаемо, кэшируемо, работает везде без прокси. Считать gRPC безусловно лучше REST - ошибка: это размен скорости на читаемость и совместимость.
// Для браузерного gRPC нужен gRPC-Web и прокси-прослойка - лишняя сложность, если наружу и так удобнее REST.
- gRPC
- RPC на protobuf поверх HTTP/2, бинарный и строго типизированный
- protobuf
- бинарный формат и схема контракта для gRPC
Как отвечать: «Как защитить оплату от двойного списания при ретрае?»
Через Idempotency-Key. POST не идемпотентен сам по себе, а сетевой ретрай оплаты реально приходит дважды, так что полагаться на «клиент не повторит» нельзя. Клиент генерирует уникальный ключ на операцию и шлёт его в заголовке. Сервер при первом запросе выполняет списание и сохраняет ключ вместе с результатом, а на любой повтор с тем же ключом возвращает уже сохранённый ответ, не списывая второй раз. Ключ храню с TTL, а саму запись результата делаю в одной транзакции со списанием, чтобы не было гонки между двумя параллельными ретраями.
Назван механизм, объяснено почему POST опасен, и добавлены продовые детали - транзакционность записи ключа и гонка параллельных ретраев; видно, что человек это реально строил.
На чём валят
- −Полагаться, что POST не повторится: ретраи сети реальны - для денег нужен Idempotency-Key.
- −Совать gRPC в публичный браузерный API: напрямую из браузера он неудобен, нужен gRPC-Web и прокси.
- −Считать gRPC безусловно лучше REST: это трейдоф скорости против читаемости и совместимости.
- −Писать эффект и сохранять ключ идемпотентности не атомарно - два параллельных ретрая проскочат оба.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 11, остальные разбираются в тренажёре.
- POST-оплата может прийти дважды из-за ретрая сети. Как не списать деньги повторно?A)Idempotency-Key: сервер запоминает ключ и не повторяет эффектB)Понадеяться, что сеть не повторит запрос дважды за короткий срокC)Обернуть списание в транзакцию — она сама отсечёт дубликат запросаD)Просто заменить POST на GET — его безопасно повторять
показать ответ и разбор
+A)Idempotency-Key: сервер запоминает ключ и не повторяет эффект// разбор: Клиент шлёт уникальный Idempotency-Key в заголовке; сервер сохраняет его с результатом первой обработки. Пришёл повтор с тем же ключом — сервер возвращает прежний ответ, не выполняя списание снова. Так POST-эффект становится безопасным для ретраев, чего сам метод не гарантирует.
- Что такое gRPC в двух словах?A)Текстовый REST-протокол с обменом JSON поверх обычного HTTP/1B)Библиотека очередей сообщений для асинхронной связи сервисовC)Формат базы данных для хранения удалённых вызовов процедурD)RPC на protobuf поверх HTTP/2 — бинарный контракт
показать ответ и разбор
+D)RPC на protobuf поверх HTTP/2 — бинарный контракт// разбор: gRPC — фреймворк удалённого вызова процедур: контракт описывают в .proto (Protocol Buffers), обмен идёт бинарно поверх HTTP/2 с поддержкой стриминга. Компактнее и быстрее JSON/REST, строгая типизация и кодогенерация клиентов. Цена — не читается глазами и хуже дружит с браузером напрямую.
- Когда gRPC уместнее REST/JSON?A)Публичный API для сторонних разработчиков и браузеров напрямуюB)gRPC стоит брать для нового сервиса — он современнее и быстрее RESTC)Только для статических сайтов, где важна отдача HTML-страницD)Внутренний обмен сервисов: нужна скорость, схема, стриминг
показать ответ и разбор
+D)Внутренний обмен сервисов: нужна скорость, схема, стриминг// разбор: gRPC силён во внутреннем межсервисном обмене: низкая задержка, компактный бинарный формат, строгая схема с кодогенерацией, двунаправленный стриминг. Для публичных API и браузеров чаще берут REST/JSON — читаемо, кэшируемо, работает везде без прокси. Выбор — контекст, а не «что моднее».
- Что значит, что операция идемпотентна?A)Повтор с теми же данными даёт тот же итог, не дублируя эффектB)Операция выполняется настолько быстро, что её повтор практически незаметенC)Операция возвращает одинаковый ответ независимо от входных данныхD)Операцию не получится повторить дважды за одну пользовательскую сессию
показать ответ и разбор
+A)Повтор с теми же данными даёт тот же итог, не дублируя эффект// разбор: Идемпотентность: многократное выполнение с одними и теми же данными приводит к тому же состоянию, что и однократное. GET, PUT, DELETE идемпотентны по семантике; POST — нет (два POST создадут две сущности). Свойство критично при ненадёжной сети: клиент может безопасно повторить запрос после таймаута.
- Оплата идёт через POST, клиент из-за таймаута повторяет запрос. Как не списать дважды?A)Полагаться на то, что второй запрос просто не дойдёт до сервера из-за сетиB)Запретить клиенту повторы запросов после первой отправки платежаC)Ничего не делать: HTTP сам не даст одному POST выполниться дваждыD)Idempotency-Key: сервер по ключу распознаёт повтор и возвращает прежний результат
показать ответ и разбор
+D)Idempotency-Key: сервер по ключу распознаёт повтор и возвращает прежний результат// разбор: POST не идемпотентен, а сеть допускает повторы после таймаутов — есть риск двойного списания. Клиент прикладывает уникальный Idempotency-Key к запросу; сервер запоминает ключ с результатом первой обработки и на повтор с тем же ключом возвращает тот же ответ, не выполняя операцию заново. Так небезопасный по природе POST делают безопасным для ретраев.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.