Ах, мир API! Вы добавляете в свое приложение волшебный порошок, созданный из пюре из функций curl _ * (), и он волшебным образом интегрируется с внешними системами, предоставляя вам доступ ко всем интересным данным в мире. Иногда приходится проклинать 400 неверных запросов, показывая HTML вместо запроса Content-Type во время разработки, но это происходит из-за того, что в коде отсутствует какой-либо параметр. Однако после развертывания кода, выполняющего HTTP-запрос, все в порядке, верно? На самом деле, нет.
Распределенные системы
Как только вы вызываете внешний API с ваших производственных серверов, ваше приложение становится частью распределенных систем. К сожалению, эта система имеет часть вне вашего административного и географического контроля. На самом деле приложения всегда выполняются на множестве компьютеров, таких как веб-сервер и база данных; Тем не менее, эффекты интеграции смягчаются:
- машины находятся в одной локальной сети и в центре обработки данных. Таким образом, задержка между TCP-соединениями между ними на порядок ниже, чем при подключении к другим системам.
- Программное обеспечение, работающее на некоторых из этих машин, является надежным и стабильным, и его не требуется обновлять чаще, чем раз в год. Реляционные (и другие виды) баз данных следуют этим принципам, как и большинство инфраструктурных сервисов (кэши, мониторинг).
- Все машины находятся под вашим контролем, поэтому вы можете вмешаться в них, когда возникнет ошибка, чтобы восстановить сервис.
При интеграции с внешним сервисом сценарий отличается:
- фактические узлы, с которыми вы разговариваете, могут быть на другом континенте.
- У них могут возникнуть проблемы с доступностью в любое время (ни у кого нет 100% доступности, а число 9 мало для многих веб-сервисов, не принадлежащих Google и Facebook).
- Эти проблемы не находятся под вашим контролем, так как вы не можете откатить внешние узлы, и их состояние не зависит от ваших развертываний.
Вероятность отказа
Но являются ли эти зависимости проблемой? Разве Facebook API не должен быть всегда доступен?
Kaizumeus радикальн в том смысле, что он никогда не должен вызывать внешнюю систему в том же процессе, который обслуживает HTTP-запрос от клиента. Причина заключается в том, чтобы избежать замыслов и задержек, связанных с доступностью внешних служб. Предположим, вы интегрируете с N API, каждый из которых имеет доступность 1-P: это означает, что вероятность того, что первый из них не работает или не ответит в любой момент времени, равна P. Предполагая, что API независимы, вероятность отсутствия один из них, который был отключен за один раз, равен (1-P) ^ N: если 1-P явно меньше 1, эта вероятность очень быстро уменьшается с увеличением N, даже для небольших значений P (вероятность отказа).
Поэтому мы должны согласиться с тем, что при интеграции с большим количеством внешних служб некоторые из них всегда будут недоступны. Но мы можем решить, как с этим бороться.
Стратегии
Прежде всего, вы должны различать синхронное взаимодействие и асинхронное. Синхронные взаимодействия требуют, чтобы ответ был предоставлен клиенту, в то время как асинхронные принимаются системой.
Выбор стратегии зависит от сценария: извлечение данных из хранилища обычно происходит синхронно, а уведомления, отправляемые письма и обновления в другие согласованные хранилища могут быть асинхронными.
Запросы, удовлетворяющие асинхронной модели, всегда должны выгружаться в очередь (ActiveMQ) или систему заданий (Gearman), которая выполняет их в другом процессе: поскольку вам не нужен ответ, вы можете выполнять их, не вешая клиента HTTP. Синхронизация пользователя с этими заданиями может быть осуществлена с помощью модели извлечения (опрос системы) или с помощью операции выталкивания (уведомление о завершении).
Важные преимущества, которые вы получаете от обработки таких асинхронных запросов, состоят в том, что их режимы сбоев не влияют на ваше приложение: клиент никогда не видит пустой экран из-за невозможности отправки почты.
Более того, задержка начальных взаимодействий уменьшается, поскольку вы можете вернуться сразу после создания задания. Это особенно важно, поскольку HTTP-запросы вкладываются друг в друга:
[A] -> [B] -> [C] [A] <- [B] <- [C] 200 OK 200 OK
становится
[A]->[B] [A]<-[B] 202 Accepted [B]->[C] [B]<-[C] 202 Accepted
Эта модель хранения и пересылки типична для системы обмена сообщениями, но это не означает, что ее нельзя реализовать с помощью HTTP-запросов, возвращающих 202 Принято. Шаблон не является его реализацией. Существует механизм повторных попыток, который должен быть установлен как на стороне инициатора (в случае, если принимающая сторона недоступна), так и на принимающей стороне (чтобы всегда принимать входящие запросы и гарантировать доступность, но иметь возможность обрабатывать запросы позже и закрывать соединение немедленно).
Наконец, гибридная модель для асинхронных запросов, которую я видел в производственных системах, заключается в том, чтобы выполнить первый запрос в процессе, но настроить механизм повторных попыток в случае сбоя. Этот выбор позволяет вам писать синхронные тесты end2end без необходимости писать много условий ожидания; в то же время он сохраняет возможность повторения запросов до их завершения и гарантирует, что внешние системы, которые работают со временем, будут в конечном итоге достигнуты.
Выводы
Ни у кого нет идеальной доступности, и чем больше внешних систем задействовано в сценарии использования вашего приложения, тем больше сбоев подвержено его распространению. Отключение приложения от внешних систем часто возможно с помощью очередей, рабочих и совершенно новых процессов, а также подхода «хранение и пересылка» в отношении вложенных взаимодействий.