Долгоживущие RESTful взаимодействия

У нас сейчас идет обсуждение в моей команде, и мне будут интересны другие взгляды. Предположим, у нас есть веб-сервис RESTful, роль которого заключается в том, чтобы аннотировать документы, применяя различные алгоритмы и сервисы анализа. Основные взаимодействия понятны: у нас есть ресурс, который является коллекцией документов; клиент помещает новый документ в коллекцию, возвращает URI нового документа, а затем может получить docURI вернуть документ или получить {docURI}/metadata чтобы увидеть общие метаданные, {docURI}/ne для именованных объектов и т. д. Проблема заключается в том, что для выполнения некоторых анализов может потребоваться много времени. Предположим, что клиент ПОЛУЧАЕТ URI метаданных до завершения анализа, потому что он хочет иметь возможность отображать частичные или инкрементные результаты в пользовательском интерфейсе. Повторение GET в будущем может дать больше результатов.

Решения, которые мы обсудили, включают в себя:

  • держать HTTP-соединение открытым, пока не будут выполнены все анализы (которые не кажутся масштабируемыми)
  • с помощьюcontent-length а также accept-rangeЗаголовки для получения инкрементного контента (но мы не знаем заранее, как долго будет финальный контент)
  • предоставление Atom-фида для каждого ресурса, чтобы клиент подписывался на обновления событий, а не просто ПОЛУЧИЛ ресурс (кажется слишком сложным и, возможно, ресурсоемким, если есть много активных документов)
  • просто получить GET вернуть то, что доступно в то время (но это все еще оставляет проблему клиента, зная, когда мы наконец закончим) [отредактировано, чтобы удалить ссылку на идемпотентность после комментариев].

Любые мнения или предложения для альтернативных способов обработки долгоживущих или асинхронных взаимодействий в архитектуре RESTful?

Ян

7 ответов

Я бы реализовал это следующим образом:

1) клиент запрашивает метаданные
2) сервер возвращает либо фактические данные (если они уже доступны), либо маркер NotReady
3) клиент спрашивает сервер, когда будут доступны данные (этот шаг можно объединить с предыдущим)
4) сервер возвращает временной интервал (может быть некоторая эвристика для общего количества выполняемых заданий и т. Д.)
5) клиент ожидает указанный период времени и переходит к шагу 1

Таким образом, вы можете предоставить данные клиентам как можно скорее. Вы можете настроить нагрузку на сервер, настроив интервал задержки, возвращаемый на шаге 4)

предоставление Atom-фида для каждого ресурса, чтобы клиент подписывался на обновления событий, а не просто ПОЛУЧИЛ ресурс (кажется слишком сложным и, возможно, ресурсоемким, если есть много активных документов)

Вы рассматривали SUP?

Если опрос является вариантом, зачем возиться с подачей? Почему бы просто не сделать так, чтобы клиенты опрашивали сам ресурс?

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

  • просто игнорируя идемпотентность и заставляя GET возвращать все, что доступно в то время (но это все еще оставляет проблему клиента, зная, когда мы наконец закончим).

Означает ли GET, который возвращает разные результаты с течением времени, на самом деле означает, что он не идемпотентен? В спецификации сказано:

Методы также могут иметь свойство "идемпотентности", заключающееся в том, что (кроме ошибок, связанных с ошибками или истечением срока действия), побочные эффекты от N > 0 идентичных запросов такие же, как и для одного запроса.

То есть, множественные вызовы GET могут возвращать разные результаты, если сами вызовы не имеют побочных эффектов.

В этом случае, возможно, ваш метод REST мог бы использовать условные механизмы GET и кэширования, чтобы указать, когда это будет сделано:

  • Пока идет анализ, GET {docURI}/metadata ответ может иметь:
    • Expires заголовок установлен на несколько секунд в будущем.
    • нет ETag заголовок.
  • По завершении анализа ответы на этот ресурс имеют:
    • нет Expires заголовок.
    • ETag, Последующие запросы с ETag должен вернуться 304 Not Modified,

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

Это "похоже" на дизайн RESTful - вы можете представить, что веб-браузер делает правильные вещи, поскольку он делает последовательные запросы к этому ресурсу.

Использовать HTTP 202 Принято.

Кроме того, ознакомьтесь с веб-службами RESTful - здесь я узнал об этом.

Вы можете проверить nServiceBus Уди Даана.

Одним из альтернативных решений, которые могут или не могут быть подходящими в вашем случае, является добавление новой конечной точки под названием "AnnotationRequests". Отправьте документ (или ссылку на него) в конечную точку AnnotationRequests, и он должен вернуть местоположение (например, http://example.org/AnnotationRequest/2042), которое позволит вашему клиенту опрашивать состояние процесса. Когда процесс завершен, представление "AnnotationRequest" может содержать ссылку на завершенный документ.

Одним приятным побочным эффектом этого является то, что вы можете выполнить GET для AnnotationRequests, чтобы увидеть документы, которые в данный момент обрабатываются. Вам решать, как долго вы хотите хранить запросы AnnotationRequest. Может быть полезно сохранить полную историю того, когда они были запрошены, кем и сколько времени они занимали, или могли периодически их выбрасывать.

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

Вам нужно, чтобы они предоставили вам "то, что я видел до сих пор" в запросе GET. Это дает вам идемпотентность. Если они просят кусок 1, они всегда получают один и тот же ответ. Как только они увидят кусок 1, они могут попросить кусок 2.

Ответ не становится больше. Больше частей становятся доступными. GET "на уровне коллекции" обеспечивает размер ответа. У вас есть "уровень детализации" GET для каждой части, которая доступна.

По сути, это такой же алгоритм, как подтверждение TCP/IP. Когда они получают кусок, вы отправляете следующий кусок. Если есть следующий кусок, в противном случае вы отправляете 200 новых сообщений.

"Проблема в том, что клиент знает, когда мы наконец закончим", не поддается решению. Они не могут знать, и вы не можете предсказать, сколько времени это займет.

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

Вы даже можете использовать алгоритм планировщика в стиле Unix, где их оценка уменьшается, когда они опрашивают, и повышаются, если они не опрашивают в течение Х секунд.

Альтернативой является какая-то очередь, в которую вы отправляете результаты обратно. Чтобы сделать это, они должны будут предоставить URI, который вы можете ПОСТАВИТЬ, чтобы сказать им, что вы сделали.

Или они используют Atom для облегченной архитектуры опроса. В то время как Atom кажется сложным - и все еще включает опрос - вы предоставляете минимальный ответ Atom ("еще не изменился") до тех пор, пока не закончите, когда вы предоставите ("новые результаты"), чтобы они могли получить реальный тяжелый вес. Это для "все или ничего", вместо метода приращения ответа выше.

Вы также можете думать о GET "на уровне коллекции" как о своем статусе Atom в процессе в целом.

Другие вопросы по тегам