Мой вариант использования - обслуживать ответ на HTTP-запрос после получения другого запроса с отдельного сервера.
Мое текущее решение состоит в том, чтобы использовать общий кеш, где каждый модуль будет продолжать проверять кеш. Я считаю, что я могу оптимизировать это с помощью каналов, где вместо проверки кеша один за другим система периодически проверяет любой завершенный ответ.
Я также хотел бы знать, как это могло быть достигнуто в других языках программирования.
PS: Это запрос, основанный на дизайне, у меня есть репутация, чтобы поделиться наградой, поэтому я спрашиваю здесь. Пожалуйста, не стесняйтесь редактировать, если вопрос не ясен.
Итак, предположим, что ваше серверное приложение под названием 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 для решения этой проблемы. И идея довольно проста, мы знаем, что должен быть какой-то супервизор, который знает точное местоположение и состояние своих собственных абонентов. Поэтому, когда он получает какое-то сообщение, он просто знает, куда и какому актору (мы говорим о программировании акторной модели) переслать запрос. Другими словами, его можно использовать для совместного использования состояния между акторами, созданными в кластере, как на одной машине, так и на другой. Но как личное предпочтение я бы не стал использовать общение на конкретном языке, а придерживался бы общих идей из-за проблем, которые это может вызвать в будущем.
Достаточно длинные объяснения :). Просто чтобы понять, о чем я говорю, давайте рассмотрим тот же самый сценарий, но с другой моделью коммуникации:
server_app
.server_app_pod_b
) для обработки запроса.serer_app_pod_b
получает сообщение с ключом, извлекает данные сообщения и продолжает обрабатывать запрос клиента.Вероятно, есть и другие подходы к решению этой проблемы, но я бы выбрал именно этот. Надеюсь, что это поможет!
@anwerj Почти всегда есть два способа общения с сообщениями: один — tell (выстрелил и забыл), который не будет блокироваться, другой — ask, который ждет ответного сообщения и блокирует. Тот факт, что вам нужно дождаться запроса «B», делает вашу модель передачи сообщений блокирующей моделью, но может гарантировать проблему, о которой вы упомянули вначале.
Нет другого компонента, кроме сервера 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, независимо от того, создаем ли мы распределенное уведомление или опрос сервера. Поток будет выглядеть так:
Исходный поток запроса будет бездействовать в течение 500 мс, прежде чем сделать следующий вызов кеша. Таким образом, другие потоки смогут использовать это время. Кроме того, получение кеша по ключу происходит очень быстро, и у вас не будет проблем с масштабируемостью.
Но вы быстрее исчерпаете свои потоки запросов, если будете поддерживать продолжительное HTTP-соединение с клиентом. Таким образом, вам понадобится больше модулей A для обработки той же скорости запросов. Я бы по-прежнему рекомендовал поговорить с клиентской командой и внести там необходимые изменения, чтобы использовалось недолговечное соединение (такое же, как ваш текущий поток).
Да, это наша текущая реализация. Но у некоторых клиентов нет средств для опроса, поэтому мы хотим ответить одним звонком.
Обновлен мой ответ на основе вашего комментария. Вы можете проверить часть «Обновление на основе комментария:».
Звучит хорошее решение, использующее очередь сообщений с моделью подписчика и уникальным хешем для запроса. Обязательно попробую. Мне также нужно оценить влияние клиентских блоков на 60 секунд. Увидит, что также можно избежать.