Архитектура обработки входящих запросов в сервисе

#.net #networking #architecture #scalability #parallel-processing

#.net #сеть #архитектура #масштабируемость #параллельная обработка

Вопрос:

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

Вот список моих целей:

  • Масштабируемость — должна быть возможность распараллеливания архитектуры на несколько процессоров или даже на несколько серверов.
  • Способность справляться с огромным количеством параллельных подключений.
  • Не должно вызывать проблем с блокировкой, если обработка одного запроса занимает много времени.
  • Время обработки запроса до ответа должно быть минимальным.
  • Построен вокруг .NET framework (будет писать это на C #)

Предлагаемая мной архитектура и поток довольно сложны, поэтому вот схема моего первоначального проекта:

Блок-схема архитектуры

вот это на tinypic на случай, если он сильно изменится)

Идея заключается в том, что запросы поступают по сети (хотя я еще не решил, будет ли лучше TCP или UDP) и сразу передаются высокоскоростному балансировщику нагрузки. Затем балансировщик нагрузки выбирает очередь запросов (RQ) для размещения запроса, используя генератор взвешенных случайных чисел. Веса являются производными от размера каждой очереди. Причина использования взвешенного RNG, а не просто размещения запросов в наименее загруженную очередь, заключается в том, что он предотвращает блокировку всего сервера пустой, но заблокированной очередью (из-за зависшего запроса). Если все RQ превышают определенный размер, балансировщик нагрузки отбрасывает запрос и помещает ответ «сервер слишком занят» в очередь вывода (OPQ) — эта часть не показана на диаграмме.

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

  1. Немедленные — немедленные запросы, как следует из названия, обрабатываются немедленно.

  2. Отложенные — отложенные запросы считаются низкоприоритетными. Они обрабатываются немедленно при низкой нагрузке или помещаются в очередь отложенных запросов (DRQ), если нагрузка высокая. Балансировщик нагрузки извлекает эти отложенные запросы из DRQ, помечает их как немедленные, а затем помещает их обратно в соответствующие RQ.

  3. Запросы с временной синхронизацией помещаются в очередь запросов с временной синхронизацией (TRQ) вместе с их целевой временной меткой. Эти запросы часто генерируются в результате другого запроса, а не явно отправляются клиентом. Когда временная метка запроса превышена, следующий доступный поток обработки запросов использует ее и обрабатывает.

Когда запрос обрабатывается, данные могут быть извлечены из кэша пары ключ / значение в памяти, кэша пары ключ / значение или на диске, или с выделенного сервера базы данных SQL. Кэшированные значения будут BSON, а индекс будет строкой. Я подумываю об использовании Dictionary<T1,T2> для реализации этого в памяти и btree (или аналогичного) для дискового кэша.

Ответ создается по завершении обработки и помещается в очередь вывода (OPQ). Затем цикл использует ответы из OPQ и передает их обратно клиенту по сети. Если OPQ достигает 80% от своего максимального размера, одна четверть потоков обработки запросов останавливается. Если OPQ достигает 90% от своего максимального размера, половина потоков обработки запросов останавливается. Если OPQ достигает своего максимального размера, все потоки обработки запросов останавливаются. Это будет достигнуто с помощью семафора, который также должен предотвращать блокировку отдельных потоков обработки запросов и оставлять устаревшие запросы.

Я ищу предложения по нескольким областям:

  • Есть ли какие-либо серьезные недостатки в этой архитектуре, которые я пропустил?
  • Есть ли что-нибудь, что я должен рассмотреть по соображениям производительности?
  • Будут ли TCP или UDP более подходящими для запросов? Было бы очень полезно иметь «подтверждение доставки», которое предлагает TCP, но легкий характер UDP также привлекателен.
  • Есть ли какие-либо особые соображения, о которых мне нужно подумать при работе с более чем 100 тыс. одновременных подключений на сервере Windows? Я знаю, что стек TCP в Linux работает хорошо, но я не уверен в Windows.
  • Есть ли какие-либо другие вопросы, которые я должен задать? Я забыл что-нибудь учесть?

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

Обновленная версия схемы здесь.

Комментарии:

1. Как прошел этот проект / как он продвигается? Какие-либо сообщения в блоге об этом? Мне очень интересно услышать, что вы узнали на этом пути и к каким выводам вы пришли.

Ответ №1:

Также вы можете рассмотреть следующее:

  • Отработка отказа. Вы могли бы разработать подход к сохранению запросов во время возможных сбоев службы, чтобы все ожидающие запросы обрабатывались даже после перезапуска службы
  • Очередь ошибок. (также известный как шаблон канала мертвых писем)
  • Каналы и фильтры. Предоставляя такую функцию, вы бы достигли высокого уровня гибкости и расширяемости сервиса
  • Подтверждение запроса. В некоторый заранее определенный промежуток времени клиент, отправивший запрос в сервис, ожидает сообщения подтверждения с идентификатором корреляции, установленным на начальный идентификатор запроса. Таким образом, сервис может уведомлять клиентов о том, что конкретный запрос получен и помещен во входящую очередь, если клиент не получает подтверждение для только что отправленного запроса — он может повторно отправитьэто или пометить как сбой.

PS: Также я бы посоветовал отличную книгу «Шаблоны корпоративной интеграции«

Комментарии:

1. Это хороший момент при отказоустойчивости. Поскольку каждый экземпляр будет выполняться на отдельном сервере, я собирался сбалансировать нагрузку между ними (не имеет значения, на какой из них отправляется запрос) и просто перебалансировать его, если сервер выйдет из строя. Однако я не рассматривал идею что-то делать с ожидающими запросами. Возможно, мне следует сохранить их дубликат на диске на случай сбоя демона, но считать запросы потерянными, если вся машина выйдет из строя? Есть идеи получше?

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

Ответ №2:

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

Что касается TCP и UDP — какую производительность вы ищете? И не было бы лучше использовать некоторую существующую инфраструктуру связи, такую как ZeroMQ, чтобы позаботиться об этих технических деталях для вас?

Итай.

Комментарии:

1. Идея наличия нескольких очередей запросов проистекает из моей идеи о специализации определенных очередей для предпочтения одного подмножества типов запросов. Это должно позволить мне настроить мой код, чтобы ускорить обработку определенных типов сообщений. Что касается TCP / UDP / чего-то еще, я не совсем уверен, что мне нужно. Я хотел бы напрямую взаимодействовать с сетевым протоколом, но это означает, что я ограничен тем, что поддерживает .NET (а это в значительной степени только TCP и UDP).

2. Я бы туда не пошел. Если ваша очередь поддерживает приоритеты (некоторые системы очередей поддерживают, я не помню, поддерживает ли MSMQ), вы свободны. В общем, я думаю, вы пытаетесь заново изобрести много вещей, которые уже были изобретены — и с открытым исходным кодом. Я бы действительно изучил несколько существующих систем массового обслуживания, прежде чем начать внедрять что-то по сети. Также взгляните на WCF, хотя я не уверен, насколько он ориентирован на производительность.

3. Возможно, мне следует сохранить несколько очередей, но использовать их просто для хранения разных приоритетов сообщений. Таким образом, я могу просто переместить свой балансировщик нагрузки на другую сторону очередей и упростить множество внутренних вещей в процессе.

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

5. Я использую Queue<T> из . NET framework, который не поддерживает приоритеты. Я буду использовать взвешенный RNG для балансировки загрузки выборок, как я планировал сделать раньше со вставками. Обновление: я мог бы в конечном итоге использовать ConcurrentQueue<T> вместо этого, если мне нужна явная безопасность потоков.

Ответ №3:

Если вы хотите, чтобы это хорошо масштабировалось, вам нужно убедиться, что все компоненты масштабируемы — элементы обработки, части ввода / вывода и очереди. Если вы собираетесь делать это в стеке Microsoft, я бы серьезно рекомендовал изучить Windows Azure, которая предлагает большинство, если не все, ключевые функции, которые вам понадобятся. Одна вещь, которую вы не упомянули — будет ли уровень постоянного хранения (например, база данных)? Если это так, будьте готовы масштабировать и это, иначе это станет вашей единственной точкой отказа.

Комментарии:

1. База данных была показана на диаграмме и упомянута в моем вопросе. Я также не очень хочу использовать Azure, так как я бы предпочел, чтобы мое приложение выполняло логику. Причина этого в том, что я хочу, чтобы он мог устанавливаться на нескольких разных хостах (включая хосты клиентов) и чтобы они действовали как собственный экземпляр или как часть общего экземпляра.

2. Извините — изображение заблокировано из моего текущего местоположения, и я пропустил ссылку на DB в сообщении. Что касается «моего приложения, выполняющего логику», я не понимаю, как использование функций масштабируемости Azure лишает вас возможности настраивать логику. Приложения, установленные на хостах клиентов, могут использовать «общие» экземпляры или использовать отдельные учетные записи для «приватизации» их установки.

3. Я имел в виду, что я хотел бы, чтобы некоторые клиенты могли запускать свою собственную «ферму» моего сервера без необходимости покупать или устанавливать Azure. Я также хотел бы держаться подальше от дорогостоящих зависимостей от программного обеспечения.