Подавать ответ на HTTP-запрос после получения другого запроса

Мой вариант использования - обслуживать ответ на HTTP-запрос после получения другого запроса с отдельного сервера.

  1. Я хочу сделать это наилучшим образом, помня о масштабировании.
  2. Мы используем Golang 1.19 с Gin Framework.
  3. Сервер будет иметь несколько модулей, поэтому каналы не будут работать.
  4. Тайм-ауты для всех запросов, делающих первоначальный запрос, истекают через 60 секунд.

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

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

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

LeetCode запись решения 2536. Увеличение подматриц на единицу
LeetCode запись решения 2536. Увеличение подматриц на единицу
Увеличение подматриц на единицу - LeetCode
Версия Java на основе версии загрузки
Версия Java на основе версии загрузки
Если вы зайдете на официальный сайт Spring Boot , там представлен start.spring.io , который упрощает создание проектов Spring Boot, как показано ниже.
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Как включить TLS в gRPC-клиенте и сервере : 2
Как включить TLS в gRPC-клиенте и сервере : 2
Здравствуйте! 🙏🏻 Надеюсь, у вас все хорошо и добро пожаловать в мой блог.
Сортировка hashmap по значениям
Сортировка hashmap по значениям
На Leetcode я решал задачу с хэшмапой и подумал, что мне нужно отсортировать хэшмапу по значениям.
6
0
226
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Тл;др

Описание проблемы

Итак, предположим, что ваше серверное приложение под названием server_app, например, имеет 3 модуля:

     +---------------------+
     |  server_app_service |
     +---------------------+
     |  server_app_pod_a   |
     |  server_app_pod_b   |
     |  server_app_pod_c   |
     +---------------------+

Ваша служба получает запрос с именем "request A" и решает передать его server_app_pod_a. Теперь ваш server_app_pod_a перенаправляет запрос на какой-то шлюз и ждет какого-то уведомления, чтобы продолжить обработку ответа клиента. И, как вы уже знаете, нет никакой гарантии, что когда шлюз выполняет request B, служба снова передает его server_app_pod_a. И даже если бы это было так, управление состоянием вашего приложения стало бы трудной задачей.

Обмен сообщениями

Как вы могли заметить, я выделил жирным шрифтом слово «уведомление» в предыдущем абзаце, потому что, если вы действительно думаете об этом, request "B" больше похоже на уведомление с каким-то сообщением, чем на запрос какого-то ресурса. Так что мой выбор номер 1 был бы очередью сообщений, такой как kafka (опять же, как вы знаете, их много). Идея в том, что если бы вы могли определить алгоритм для вычисления уникальных ключей для ваших запросов, вы могли бы ожидать, что полученные уведомления будут в том же самом поде. Таким образом, управление состоянием будет намного проще, а также вероятность получения уведомления в том же модуле будет намного выше (конечно, это зависит от многих факторов, таких как состояние очереди сообщений). Взглянув на ваши вопросы:

Я хочу сделать это наилучшим образом, помня о масштабировании.

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

Тайм-ауты для всех запросов, делающих первоначальный запрос, истекают через 60 секунд.

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

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

Использование очередей сообщений — это общая идея, которая применима практически к любому языку программирования, но в зависимости от парадигм программирования языка, а также библиотек и инструментов, специфичных для языка, могут быть и другие подходы к этой проблеме. Например, в Scala, если вы используете какой-то специальный инструмент под названием akka (который обеспечивает парадигму программирования моделей акторов), вы можете использовать что-то так называемое akka-cluster-sharding для решения этой проблемы. И идея довольно проста, мы знаем, что должен быть какой-то супервизор, который знает точное местоположение и состояние своих собственных абонентов. Поэтому, когда он получает какое-то сообщение, он просто знает, куда и какому актору (мы говорим о программировании акторной модели) переслать запрос. Другими словами, его можно использовать для совместного использования состояния между акторами, созданными в кластере, как на одной машине, так и на другой. Но как личное предпочтение я бы не стал использовать общение на конкретном языке, а придерживался бы общих идей из-за проблем, которые это может вызвать в будущем.

Заворачивать

Достаточно длинные объяснения :). Просто чтобы понять, о чем я говорю, давайте рассмотрим тот же самый сценарий, но с другой моделью коммуникации:

  1. Клиент отправляет запрос «А» в сервис server_app.
  2. Сервис выбирает один из модулей (например, server_app_pod_b) для обработки запроса.
  3. Затем модуль пытается определить некоторый ключ для запроса и передает его шлюзу вместе с запросом и ожидает публикации сообщения с ключом в очереди.
  4. Шлюз делает то, что должен, и отправляет сообщение с ключом в очередь сообщений.
  5. Точно такой же под serer_app_pod_b получает сообщение с ключом, извлекает данные сообщения и продолжает обрабатывать запрос клиента.

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

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

anwerj 20.11.2022 09:04

@anwerj Почти всегда есть два способа общения с сообщениями: один — tell (выстрелил и забыл), который не будет блокироваться, другой — ask, который ждет ответного сообщения и блокирует. Тот факт, что вам нужно дождаться запроса «B», делает вашу модель передачи сообщений блокирующей моделью, но может гарантировать проблему, о которой вы упомянули вначале.

AminMal 20.11.2022 09:30
  • клиент запускает HTTP-вызов на сервер A.
  • сервер A создает уникальный ключ и сохраняет его в кэш-сервере в течение 60 с (период ожидания).
  • сервер A перенаправляет запрос B через HTTP-вызов. Это звонок «выстрели и забудь» для B.
  • сервер A немедленно возвращает ответ клиенту с уникальным ключом.
  • клиент начинает опрос (скажем, каждые 500 мс), используя HTTP API GET статуса, чтобы проверить, выполняется ли обработка на сервере A.
  • означает, что сервер B завершает задачу и перезванивает на сервер A через HTTP API.
  • сервер A сохраняет ответ в кеше на уникальный ключ в течение короткого периода времени (скажем, 60 с).
  • клиентский вызов A для API проверки статуса получит данные из кеша и вернет их клиенту.

Нет другого компонента, кроме сервера A и сервера B. Есть кеш-сервер, но он является внутренним для A. B не нужно знать об этом. Какой сервис поддерживает то, что четко определено, поэтому его легко поддерживать. B может иметь внутреннюю очередь для обработки переадресованного запроса. Но А не обязательно знать, как реализован Б. Каждый сервис может поддерживаться разными командами с использованием простого HTTP-контракта.

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

Кто-то может спросить, где логика повтора для связи между серверами между A и B? Но для этого нам не нужна очередь. По сути, клиент хочет здесь синхронный тип вызова. Мы просто разбиваем его на несколько вызовов. Ответ в любом случае должен быть быстрым. У нас может быть механизм повторных попыток из 3-5 попыток в HTTP-клиенте в случае сбоя сети. Мы используем капсулы. Я ожидаю, что это будет за балансировщиком нагрузки k8s. Этот балансировщик нагрузки в любом случае повторит попытку к исправному поду, если первый под выйдет из строя. Так что вы там в значительной степени покрыты.

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

Обновление на основе комментария:

Реализация клиентского опроса довольно проста. По сути, это цикл, который запускается каждые 0,5 с и выполняет вызов HTTP GET для проверки статуса. Я считаю, что любой клиент должен быть в состоянии реализовать это. Но могут быть некоторые ограничения, когда команда клиента не хочет вносить какие-либо изменения. Я не хочу в это вникать.

Допустим, у некоторых клиентов нет возможности опроса, а у некоторых клиентов нет поддержки веб-сокетов. Тогда единственное, что осталось, — это долгоживущее HTTP-соединение между клиентом и сервером A. Предположим, мы выбираем этот подход. Не вдаваясь в очевидные проблемы наличия долгоживущего потока запросов на сервере, есть еще одна проблема, специфичная для этого сценария. Поток запросов на сервере A должен быть уведомлен, когда сервер B выполняет обратный вызов.

Подход 1: мы используем какую-то очередь, как упоминалось в другом ответе (я бы оставил очередь внутри A, но не будем вдаваться в это). И мы используем какой-то ключ сообщения, чтобы потребление происходило в том же поде. У нас есть API и потребитель, работающие в одном модуле. Это достижимо. Но поток запроса и поток потребителя отличаются. Поток-потребитель каким-то образом должен уведомить поток запроса о том, что результат доступен. Поэтому требуется некоторая связь между потоками, что только усложняет ситуацию. И это не входит в обязанности очереди. Так что я бы отказался от такого подхода.

Кто-то может спросить, может попросить поток напрямую прослушивать какое-то сообщение с определенным ключом. Я так не думаю. Ради аргумента, есть некоторые технологии очередей, где вы можете это сделать. Но у вас, по сути, есть эфемерный потребитель и отдельный раздел для каждого ключа, который не будет масштабироваться. Прослушивание всех сообщений и игнорирование всех, кроме одного, в эфемерном потребителе также не является жизнеспособным решением.

Подход 2. Из подхода 1 мы поняли, что поток запроса должен быть уведомлен, когда сервер B отправляет обратный вызов. Это сильно усложнило бы процесс, и вам потребуются дополнительные компоненты, такие как ZooKeeper, для распределенной блокировки или наблюдения за некоторыми изменениями. Вместо этого мы можем просто расширить нашу текущую систему для выполнения опроса на стороне сервера. У него не должно быть заметной разницы во времени отклика, поскольку мы уже проводим опрос на стороне клиента. Мы просто переносим его на серверную часть. Кроме того, в любом случае нам пришлось бы поддерживать длительный поток запросов на сервере A, независимо от того, создаем ли мы распределенное уведомление или опрос сервера. Поток будет выглядеть так:

  • Клиент вызывает сервер А.
  • Поток запросов сервера A выполняет HTTP-вызов на сервер B.
  • Сервер B запускает фоновую обработку и немедленно возвращает HTTP-ответ A.
  • Поток запроса в A запускает циклическую проверку в кэш-сервере, доступен ли ключ каждые 500 мс.
  • B обрабатывает результат и выполняет обратный вызов HTTP для A.
  • поток обратного вызова в A кэширует результат по тому же ключу.
  • Оригинал Поток запроса считывает значение из кеша и возвращает ответ клиенту.

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

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

Да, это наша текущая реализация. Но у некоторых клиентов нет средств для опроса, поэтому мы хотим ответить одним звонком.

anwerj 26.11.2022 09:00

Обновлен мой ответ на основе вашего комментария. Вы можете проверить часть «Обновление на основе комментария:».

aatwork 26.11.2022 19:37

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