Gateway Aggregation паттерн
Этот шаблон полезен, когда клиент должен сделать несколько обращений к разным внутренним системам для выполнения операции.
Контекст и проблема
Для выполнения одной задачи клиенту может потребоваться сделать несколько вызовов различных backend сервисов. Приложение, которое использует множество сервисов для выполнения задачи, должно расходовать ресурсы на каждый запрос. Когда в приложение добавляется новая фича или сервис, требуются дополнительные запросы, что еще больше увеличивает требования к ресурсам и cross-service вызовам.
На диаграмме ниже клиент отправляет запросы каждому сервису (1,2,3). Каждый сервис обрабатывает запрос и отправляет ответ обратно в приложение (4,5,6). В сотовой сети с обычно высоким пингом использование отдельных запросов таким образом неэффективно и может привести к нарушению соединения или обрывам запросов. Хотя каждый запрос может выполняться параллельно, приложение должно отправлять, ждать и обрабатывать данные для каждого запроса, все через отдельные соединения, что увеличивает вероятность сбоя.
Решение
Используйте Gateway, чтобы уменьшить количество запросов между клиентом и сервером.
Этот шаблон может уменьшить количество запросов, которые приложение отправляет к back-end сервисам, и повысить производительность приложения в high-latency сетях.
На следующей диаграмме приложение отправляет запрос шлюзу (1). Запрос содержит пакет дополнительных запросов. Шлюз декомпозирует их и обрабатывает каждый запрос, отправляя его соответствующему сервису(2). Каждый сервис возвращает ответ в gateway(3). Gateway объединяет ответы от каждого сервиса и отпавляет ответ приложению (4). Приложение делает один запрос и получает только один ответ от шлюза.
Проблемы и вопросы при реализации
- Gateway не должен создавать coupling между back-end сервисами.
- Gateway должен располагаться рядом с back-end сервисами, чтобы максимально уменьшить задержку.
- Сервис шлюза может создать Single point of failure. Убедитесь, что gateway спроектирован правильно и соответствует требованиям доступности вашего приложения.
- Gateway может стать узким местом. Убедитесь, что gateway имеет достаточную производительность для обработки нагрузки и может быть масштабирован в соответствии с ожидаемым ростом.
- Выполните нагрузочное тестирование шлюза, чтобы исключить каскадные сбои сервисов.
- Реализуйте resilient design, используя такие методы, как: bulkheads, circuit breaking, retry, и timeouts .
- Если один или несколько сервисных вызовов занимают слишком много времени, может быть допустимо установить тайм-аут и вернуть частичный набор данных. Подумайте, как ваше приложение будет обрабатывать этот сценарий.
- Используйте асинхронный I/O, чтобы гарантировать, что задержка на сервере не вызовет проблем с производительностью в приложении.
- Реализуйте распределенную трассировку, используя correlation ID для отслеживания каждого отдельного реквеста.
- Отслеживайте показатели запросов и размеры ответов.
- Рассмотрите возможность возврата кэшированных данных в качестве стратегии отработки отказа для обработки сбоев.
- Вместо того чтобы встраивать агрегацию в gateway, рассмотрите возможность размещения сервиса агрегации за шлюзом. Агрегация запросов, вероятно, будет иметь другие требования к ресурсам, чем другие сервисы в шлюзе, и может повлиять на функции маршрутизации и разгрузки шлюза.
Используйте этот шаблон, когда:
- Клиент должен взаимодействовать с несколькими внутренними сервисами для выполнения операции.
- Клиент может использовать сети со значительной задержкой, такие как сотовые сети.
Не используйте этот шаблон, когда:
- Вы хотите сократить количество вызовов между клиентом и одним сервисом в рамках нескольких операций. В этом случае лучше добавить в сервис batch операцию.
- Клиент или приложение расположены рядом с backend сервисом, и задержка не является существенным фактором.
Пример агрегации шлюзов NGINX с помощью Lua
В следующем примере показано, как создать простой сервис агрегации шлюзов NGINX с помощью Lua.
worker_processes 4;
events {
worker_connections 1024;
}
http {
server {
listen 80;
location = /batch {
content_by_lua '
ngx.req.read_body()
-- read json body content
local cjson = require "cjson"
local batch = cjson.decode(ngx.req.get_body_data())["batch"]
-- create capture_multi table
local requests = {}
for i, item in ipairs(batch) do
table.insert(requests, {item.relative_url, { method = ngx.HTTP_GET}})
end
-- execute batch requests in parallel
local results = {}
local resps = { ngx.location.capture_multi(requests) }
for i, res in ipairs(resps) do
table.insert(results, {status = res.status, body = cjson.decode(res.body), header = res.header})
end
ngx.say(cjson.encode({results = results}))
';
}
location = /service1 {
default_type application/json;
echo '{"attr1":"val1"}';
}
location = /service2 {
default_type application/json;
echo '{"attr2":"val2"}';
}
}
}