Статьи

REST обратные вызовы

В API REST, который правильно использует гипермедиа, URL-адреса для связи с HTTP-запросами клиента не являются фиксированными. Они встроены в ответы от предыдущих запросов, что делает возможным их изменение.

Этот стиль использует стандартные типы мультимедиа для передачи ссылок, такие как XML в форме каналов Atom или JSON HAL. Даже если мы пропустим стандартные типы мультимедиа, мы можем встроить ссылки в заголовки HTTP ответов, чтобы обеспечить гибкость и возможность обнаружения:

  • Заголовки местоположений в 201 г. Созданы ответы на запросы POST.
  • Заголовки местоположений в 30X перенаправляются после того, как мы заметили, что POST не приемлем или условия If-Match не выполняются.
  • Свяжите заголовки, чтобы перемещаться по коллекции ресурсов назад и вперед, и сохранять закладки на самой последней на данный момент странице.

Сегодня я хочу расширить это мышление, чтобы охватить взаимодействие между серверами между двумя приложениями, которые общаются через REST API; на самом деле, через два REST API выставлены друг другу.

Контекст: асинхронные вызовы

Процессы являются ценным, ограниченным ресурсом; во многих стеках, таких как LAMP, процесс, занятый ответом на HTTP-запрос, больше ничего не может сделать, даже если он заблокирован в ожидании ввода-вывода от нисходящей зависимости, такой как база данных. Более того, существует ограничение на количество процессов, которые можно выделить. Например, количество процессов Apache ограничено конфигурацией; каждый новый процесс приводит к другому соединению с базой данных, создаваемой или принимаемой пулом; и переключение контекста требует значительных затрат, которые приходится выполнять ядрам процессора, чтобы управлять множеством процессов, намного превышающих их (например, программисту приходится работать над 3 или 4 пользовательскими историями одновременно).

Тем не менее, число веб-процессов определяло, сколько одновременно работающих клиентов вы можете поддерживать: после того, как все они будут выделены, клиенты увидят, что их соединение зависло, пока они ожидают системного вызова accept () от Apache. После истечения времени ожидания их соединение отклоняется или прерывается на стороне клиента.

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

Например, при наличии 64 процессов, на получение ответа которых уходит в среднем 1 секунда, вы можете обработать ровно 64 одновременно работающих клиента, прежде чем приступить к деградации (65-му клиенту придется подождать, пока освободится один процесс, и это время очереди быть добавлено к времени, затрачиваемому процессом, чтобы построить его ответ.)

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

> POST /jobs HTTP/1.1
> param=value
< HTTP/1.1 200 OK

в

> POST /jobs HTTP/1.1
> param=value
< HTTP/1.1 202 Accepted

Процесс просто помещает задание в очередь, возможно, немедленно выполняя некоторую недорогую проверку. Клиентское приложение может получить результат, перезвонив по другому URL-адресу после обработки задания. Подобные асинхронные вызовы позволяют обрабатывать большие скачки нагрузки, которые заполняют вашу очередь, даже если она не в состоянии гарантировать такую ​​пропускную способность (заплатив цену более высокого общего времени за выполнение задания).

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

Перезвонить

Серверная сторона берет на себя инициативу после завершения задания и трансформируется в клиента, отправляющего HTTP-запрос исходному клиенту (который теперь действует как сервер, конечно). Протокол должен быть общим (например, запросы PUT или POST в определенном формате).

Однако вам не нужно исправлять этот URL-адрес обратного вызова, так как его можно передать через исходный HTTP-запрос:

> POST /jobs HTTP/1.1
> param=value&resource_url=https://www.onebip.com/api/billing/123
< 202 Accepted

После завершения работы запрос обратного вызова будет:

PUT /api/billing/123
...some body...
Host: www.onebip.com

(Методы HTTP здесь используются для объяснительных целей, пожалуйста, не судите их семантику.)

Это похоже на то, что делают JavaScript и другие языки стиля с передачей продолжения . Например, jQuery выполняет запросы Ajax:

success = function(response) { ... };
failure = function(response) { ... };
$.ajax(url, ..., success, failure);

Вместо этого процессы Erlang связываются с однонаправленными сообщениями, обрабатываемыми асинхронно. Таким образом, чтобы получить ответ, каждое сообщение должно включать отправителя в свой контент (в данном случае это не URL, а PID):

neighbor ! {self(), ...}.

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

Выводы

Динамичность отсутствия жестких маршрутов возврата сообщений позволяет нам также играть с системой в целях тестирования. Например, если мы разрабатываем систему A, которая взаимодействует с другой системой B, легко протестировать A из промежуточной области в производственной системе B, не затрагивая производство A: просто настройте URL-адреса B и передайте свои собственные (общедоступные) URL-адреса.

Также становится легко поддерживать несколько производственных систем A1, A2, … An: вы в основном трансформируете совместную работу между A и B в ситуацию B-as-a-service, где легко подключать новых клиентов A, даже когда есть обратный путь от B до A.