Как сохранить идемпотентность API при одновременном получении нескольких запросов с одним и тем же идентификатором?

Из множества статей и коммерческих API, которые я видел, большинство людей делают свои API идемпотентными, прося клиента предоставить requestId или idempotent-key (например, https://www.masteringmodernpayments.com/blog/idempotent-stripe-requests) и в основном сохраняют карту ответов requestId ‹-> в хранилище. Поэтому, если приходит запрос, который уже есть на этой карте, приложение просто вернет сохраненный ответ.

Это все хорошо для меня, но моя проблема заключается в том, как мне справиться со случаем, когда второй вызов поступает, пока первый вызов все еще выполняется?

Итак, вот мои вопросы

  1. Я предполагаю, что идеальным поведением было бы, если бы второй вызов продолжал ждать, пока первый вызов не завершится и не вернет ответ первого вызова? Вот как это делают люди?

  2. если да, то как долго второй вызов должен ждать завершения первого вызова?

  3. если второй вызов имеет ограничение по времени ожидания, а первый вызов еще не завершен, что он должен сказать клиенту? Должен ли он просто не возвращать никаких ответов, поэтому клиент истечет время ожидания и повторит попытку?


person hikky36    schedule 25.07.2015    source источник
comment
Я столкнулся с точно такой же проблемой, и меня раздражает отсутствие ответов на этот (казалось бы) основной вопрос в Интернете. Мне кажется, что в этой ветке нет ответа на ваш вопрос, особенно на вопрос 1. Не могли бы вы объяснить, как вы в итоге решили эту проблему? Какую методологию вы использовали?   -  person dream-weaver    schedule 09.09.2019


Ответы (2)


Для wunderlist мы используем ограничения базы данных, чтобы убедиться, что ни один идентификатор запроса (который является столбцом в каждой из наших таблиц) никогда не используется дважды. Поскольку наша технология базы данных (postgres) гарантирует невозможность вставки двух записей, нарушающих это ограничение, нам нужно только правильно отреагировать на возможную ошибку вставки. По сути, мы передаем эту деталь нашему хранилищу данных.

Я бы порекомендовал, независимо от того, как вы это сделаете, постараться не координировать свои действия в своем приложении. Если вы попытаетесь узнать, происходят ли две вещи одновременно, то высока вероятность того, что это будут ошибки. Вместо этого может существовать уже используемая вами система, которая может дать необходимые вам гарантии.

Теперь, чтобы конкретно ответить на ваши три вопроса:

  1. Для нас, поскольку мы используем ограничения базы данных, база данных обрабатывает вещи, стоящие в очереди и ожидающие. Вот почему я лично предпочитаю старые базы данных SQL — не из-за SQL или отношений, а потому, что они действительно хороши в блокировках и организации очередей. Мы используем базы данных SQL как тупые несвязанные таблицы.
  2. Это во многом зависит от вашей системы. Мы пытаемся настроить все наши тайм-ауты примерно на 1 с в каждой системе и подсистеме. Мы скорее потерпим неудачу, чем будем стоять в очереди. Вы можете измерить, а затем посмотреть на свой 99-й процентиль для определения времени и просто установить его в качестве тайм-аута, если вы не знаете заранее.
  3. Мы бы вернули клиенту статус 504 http (и соответствующее тело ответа). Причина наличия идемпотентного ключа заключается в том, что клиент может повторить запрос, поэтому мы никогда не беспокоимся о тайм-ауте и позволяем им делать именно это. Опять же, мы бы предпочли быстрый тайм-аут и исправление проблем, чем позволить вещам стоять в очереди. Если что-то стоит в очереди, то даже после того, как что-то будет исправлено, нужно подождать некоторое время, пока все станет лучше.
person Nathan    schedule 11.08.2015

Немного сложно понять, был ли второй вызов от того же клиента с тем же токеном запроса или от другого клиента.

Обычно в случае одновременных запросов от разных клиентов, работающих с одним и тем же ресурсом, вы также хотели бы реализовать стратегию управления версиями вместе с токеном запроса для идемпотентности.

Типичной стратегией версий в реляционной базе данных может быть столбец версии с триггером, который автоматически увеличивает число при каждом обновлении записи.

При этом все клиенты должны указать свой токен запроса, а также версию, которую они обновляют (обычно для этого используется заголовок IfMatch, а номер версии используется в качестве значения ETag).

На стороне сервера, когда приходит время обновить состояние ресурса, вы сначала проверяете, соответствует ли номер версии в базе данных версии, предоставленной в ETag. Если они это сделают, вы записываете изменения, и версия увеличивается. Если предположить, что второй запрос работает с тем же номером версии, что и первый, он затем завершится с ошибкой 412 (или 409, в зависимости от того, как вы интерпретируете спецификации HTTP), и клиент не должен повторять попытку.

Если вы действительно хотите немедленно остановить второй запрос, пока выполняется первый запрос, вы идете по пути пессимистической блокировки, которая не очень подходит для REST API.

В случае, когда вы на самом деле говорите о повторной попытке клиента с тем же токеном запроса, потому что он получил временную сетевую ошибку, это почти тот же случай.

Оба запроса будут выполняться одновременно, второй запрос запустится, потому что первый запрос еще не завершен и еще не записал токен запроса в базу данных, но тот из них, который завершится первым, завершится успешно и запишет токен запроса.

Для другого запроса он получит конфликт версий (поскольку первый запрос увеличил версию), после чего он должен перепроверить таблицу базы данных токенов запроса, найти там свой собственный токен и предположить, что это был параллельный запрос, который завершился до он сделал и вернул 200.

Кажется, что это много, но если вы хотите охватить все странные и замечательные режимы отказа при работе с REST, идемпотентностью и параллелизмом, это способ справиться с этим.

person nrjohnstone    schedule 22.08.2017
comment
› Оба запроса будут выполняться одновременно, второй запрос запустится, потому что первый запрос еще не завершен и еще не записал токен запроса в базу данных, но тот из них, который завершится первым, завершится успешно и запишет токен запроса. . Что сервер должен сделать со вторым запросом? Должен ли он отвергнуть это? - person Keen Sage; 21.08.2019