Очередь запросов перед службой REST
Какое наилучшее технологическое решение (фреймворк / подход) иметь очередь запросов перед службой REST. так что я могу увеличить количество экземпляров службы REST для повышения доступности и размещения очереди запросов впереди, чтобы сформировать границу службы / транзакции для клиента службы.
- Мне нужен хороший и легкий выбор технологий / фреймворка для очереди запросов (Java)
- Подход к реализации конкурирующего потребителя с ним.
3 ответа
Здесь есть пара вопросов, в зависимости от ваших целей.
Во-первых, это только способствует доступности ресурсов на серверной части. Подумайте, есть ли у вас 5 серверов, обрабатывающих запросы на очереди. Если один из этих серверов выйдет из строя, запрос в очереди должен вернуться в очередь и быть доставленным на один из оставшихся 4 серверов.
Тем не менее, пока эти внутренние серверы обрабатываются, внешние серверы удерживают фактические, инициирующие запросы. Если один из этих серверов переднего плана выходит из строя, эти соединения полностью теряются, и исходный клиент может отправить запрос повторно.
Возможно, предпосылка заключается в том, что более простые интерфейсные системы подвержены меньшему риску отказа, и это, безусловно, верно для программного сбоя. Но сетевые карты, блоки питания, жесткие диски и т. Д. Довольно независимы от таких ложных надежд человека и наказывают всех одинаково. Итак, учтите это, когда говорите об общей доступности.
Что касается дизайна, то серверная часть - это простой процесс, ожидающий очередь сообщений JMS и обрабатывающий каждое сообщение по мере его поступления. Существует множество примеров этого, и любой сервер JMS подойдет на высоком уровне. Все, что вам нужно, это убедиться, что обработка сообщения является транзакционной, чтобы в случае сбоя обработки сообщения оно оставалось в очереди и могло быть доставлено другому обработчику сообщений.
Основное требование вашей очереди JMS - кластеризация. Сам сервер JMS является единственной точкой отказа в системе. Потерян сервер JMS, и ваша система в значительной степени мертва в воде, поэтому вам нужно будет иметь возможность кластеризовать сервер и заставить потребителей и производителей должным образом обрабатывать отработки отказа. Опять же, это зависит от JMS-сервера, большинство делают это, но это довольно обычная практика в мире JMS.
Внешний интерфейс - это то, где все становится немного сложнее, поскольку внешние серверы являются мостом от синхронного мира запроса REST к асинхронному миру внутренних процессоров. REST-запрос следует типичному RPC-шаблону: загрузка полезной нагрузки из сокета, удержание соединения открытым, обработка результатов и доставка результатов обратно в исходный сокет.
Чтобы продемонстрировать это, вы должны взглянуть на Asynchronous Servlet, который обрабатывает Servlet 3.0, представленный и доступный в Tomcat 7, последней версии Jetty (не знаю, какая версия), Glassfish 3.x и других.
В этом случае при поступлении запроса вы преобразуете номинально синхронный вызов сервлета в асинхронный, используя HttpServletRequest.startAsync(HttpServletRequest request, HttpServletResponse response)
,
Это возвращает AsynchronousContext и после запуска позволяет серверу освободить поток обработки. Затем вы делаете несколько вещей.
- Извлеките параметры из запроса.
- Создайте уникальный идентификатор для запроса.
- Создайте новый бэкэнд-запрос из ваших параметров.
- Свяжите идентификатор с AsyncContext и сохраните контекст (например, поместив его в карту приложения).
- Отправьте внутренний запрос в очередь JMS.
На этом этапе начальная обработка завершена, и вы просто возвращаетесь из doGet (или службы, или чего-то еще). Поскольку вы не вызвали AsyncContext.complete(), сервер не закроет соединение с сервером. Поскольку у вас есть хранилище AsyncContext на карте по идентификатору, это удобно для безопасного хранения на данный момент.
Теперь, когда вы отправили запрос в очередь JMS, он содержал: идентификатор запроса (который вы сгенерировали), любые параметры для запроса и идентификацию фактического сервера, выполняющего запрос. Этот последний бит важен, так как результаты обработки должны вернуться к исходной точке. Источник идентифицируется по идентификатору запроса и идентификатору сервера.
Когда ваш сервер переднего плана запустился, он также запустил поток, задачей которого является прослушивание очереди ответов JMS. Когда он устанавливает свое соединение JMS, он может настроить фильтр, такой как "Давать мне только сообщения для ServerID ABC123". Или вы можете создать уникальную очередь для каждого сервера переднего плана, а внутренний сервер использует идентификатор сервера для определения очереди, на которую будет возвращаться ответ.
Когда внутренние процессоры принимают сообщение, они принимают идентификатор запроса и параметры, выполняют работу, а затем принимают результат и помещают их в очередь ответов JMS. Когда он возвращает результат, он добавляет исходный идентификатор сервера и исходный идентификатор запроса в качестве свойств сообщения.
Таким образом, если вы получили запрос первоначально для Front End Server ABC123, внутренний процессор отправит результаты обратно на этот сервер. Затем этот поток слушателя будет уведомлен, когда получит сообщение. Задача потоков прослушивателей состоит в том, чтобы принять это сообщение и поместить его во внутреннюю очередь на сервере переднего плана.
Эта внутренняя очередь поддерживается пулом потоков, задачей которого является отправка полезных нагрузок запроса обратно в исходное соединение. Это делается путем извлечения исходного идентификатора запроса из сообщения, поиска AsyncContext из этой внутренней карты, обсуждавшейся ранее, и последующей отправки результатов в HttpServletResponse, связанный с AsyncContext. В конце он вызывает AsyncContext.complete() (или аналогичный метод), чтобы сообщить серверу, что вы сделали, и позволить ему освободить соединение.
Для ведения домашнего хозяйства у вас должен быть другой поток на сервере переднего плана, задачей которого является обнаружение, когда запросы слишком долго ожидают на карте. Часть исходного сообщения должна была быть временем начала запроса. Этот поток может просыпаться каждую секунду, сканировать карту на предмет запросов, и для любого, который был там слишком долго (скажем, 30 секунд), он может поместить запрос в другую внутреннюю очередь, потребляемую набором обработчиков, предназначенных для информирования клиент, запрос истек.
Вам нужны эти внутренние очереди, чтобы основная логика обработки не застревала, ожидая, пока клиент получит данные. Это может быть медленное соединение или что-то в этом роде, поэтому вы не хотите блокировать все остальные ожидающие запросы, чтобы обрабатывать их один за другим.
Наконец, вам нужно учесть, что вы вполне можете получить сообщение из очереди ответов на запрос, которого больше нет на вашей внутренней карте. Например, время ожидания запроса истекло, поэтому его больше не должно быть. С другой стороны, этот сервер переднего плана мог быть остановлен и перезапущен, поэтому его внутренняя карта ожидающего запроса будет просто пустой. На этом этапе, если вы обнаружите, что у вас есть ответ на запрос, который больше не существует, вы должны просто отказаться от него (ну, зарегистрируйте его, а затем откажитесь от него).
Вы не можете повторно использовать эти запросы, на самом деле нет такого понятия, как балансировщик нагрузки, возвращающийся к клиенту. Если клиент позволяет вам выполнять обратные вызовы с помощью опубликованных конечных точек, тогда, конечно, вы можете просто сделать так, чтобы другой обработчик сообщений JMS выполнял эти запросы. Но это не REST-тип, REST на этом уровне обсуждения - это больше клиент / сервер /RPC.
Что касается того, какая инфраструктура поддерживает асинхронные сервлеты на более высоком уровне, чем необработанный сервлет (например, Джерси для JAX-RS или что-то в этом роде), я не могу сказать. Я не знаю, какие фреймворки поддерживают его на этом уровне. Похоже, что это особенность Джерси 2.0, который еще не выпущен. Там могут быть и другие, вам придется осмотреться. Кроме того, не зацикливайтесь на Servlet 3.0. Servlet 3.0 - это просто стандартизация методов, используемых в отдельных контейнерах в течение некоторого времени (особенно Jetty), поэтому вы можете захотеть взглянуть на специфичные для контейнера опции вне Servlet 3.0.
Но понятия одинаковы. Большим преимуществом являются прослушиватель очереди ответов с отфильтрованным соединением JMS, внутренняя карта запросов к AsyncContext, а также внутренние очереди и пулы потоков для выполнения фактической работы в приложении.
Если вы ослабите свое требование, что оно должно быть в Java, вы можете рассмотреть HAProxy. Он очень легкий, очень стандартный и хорошо выполняет много хороших задач (объединение запросов / поддержание активности / организация очередей).
Подумайте дважды, прежде чем внедрять очереди запросов. Если ваш трафик не будет слишком бурным, это не повлияет на производительность вашей системы под нагрузкой.
Предположим, что ваша система может обрабатывать 100 запросов в секунду. Ваш HTTP-сервер имеет ограниченный пул рабочих потоков. Единственный способ, которым может помочь пул запросов, - это если вы получаете более 100 запросов в секунду. После заполнения пула рабочих потоков запросы начинают накапливаться в вашем пуле балансировки нагрузки. Поскольку они прибывают быстрее, чем вы можете справиться с ними, очередь становится больше... и больше... и больше. В конце концов или этот пул тоже заполняется, или у вас заканчивается ОЗУ, и балансировщик нагрузки (и, следовательно, вся система) сильно падает.
Если ваш веб-сервер слишком занят, начните отклонять запросы и получите дополнительную емкость в Интернете.
Пул запросов, безусловно, может помочь, если вы сможете вовремя получить дополнительную мощность для обработки запросов. Это также может причинить вам серьезную боль. Подумайте о последствиях, прежде чем включать вторичный пул запросов перед пулом рабочих потоков вашего HTTP-сервера.
Конструкция, которую мы используем, представляет собой интерфейс REST, принимающий все запросы и отправляющий их в очередь сообщений (т.е. Rabbitmq).
Затем работники слушают сообщения и исполняют их, следуя определенным правилам. Если все пойдет не так, у вас все равно будет запрос в MQ, и если у вас большое количество запросов, вы можете просто добавить работников...
Проверьте этот лейтмотив, он как бы показывает силу этой концепции!