Как программирование, управляемое событиями, помогает веб-серверу, который выполняет только ввод-вывод?

Я рассматриваю несколько фреймворков / методов программирования для нашего нового бэкэнд-проекта. Это касается реализации BackendForFrontend, которая объединяет нисходящие сервисы. Для простоты вот шаги, через которые он проходит:

  1. Запрос поступает на веб-сервер
  2. Веб-сервер делает запрос вниз по течению
  3. Запрос в нисходящем направлении возвращает результат
  4. Веб-сервер возвращает запрос

Чем программирование, управляемое событиями, лучше, чем «обычная» обработка запросов по потокам? Некоторые веб-сайты пытаются объяснить, и часто это сводится к примерно следующему:

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

Чего я не понимаю: нам нужен поток / обработчик, чтобы ждать этих данных, верно? Приятно, что обработчик событий может продолжать работу, но нам все еще нужен (в этом примере) поток / обработчик для каждого запроса, который ожидает каждый последующий запрос, верно?

Рассмотрим этот пример: обратным запросам в нисходящем направлении требуется n секунд. За эти n секунд приходит r запросов. В потоке на запрос нам нужно r потоков: по одному на каждый запрос. По прошествии n секунд первый поток завершает обработку и становится доступным для нового запроса.

При реализации дизайна, управляемого событиями, нам понадобится r + 1 потоков: цикл событий и r обработчиков. Каждый обработчик принимает запрос, выполняет его и после выполнения вызывает обратный вызов.

Так как это улучшить ситуацию?


person R. Bosman    schedule 03.04.2018    source источник
comment
Я только что подумал: если у нас есть обработчик, который может обрабатывать несколько запросов в одном потоке, преимущество очевидно: нам нужен только один (или несколько для избыточности) обработчиков, что значительно сокращает количество необходимых потоков. Но на самом деле это невозможно, правда?   -  person R. Bosman    schedule 03.04.2018
comment
Почему нет? Обработчик - это просто структура данных, которая запоминает состояние каждого соединения (номер сокета, адрес, файлы cookie и т. Д.) Вверх и вниз и поддерживает связь между ними, плюс некоторый код, который знает, как обрабатывать следующий ввод, фактически кривошип на вашем государственном автомате. Каждое соединение имеет свою собственную отдельную структуру данных / текущее состояние. Нет жесткого ограничения на количество запросов, которые могут быть обработаны в одном потоке.   -  person Gil Hamilton    schedule 03.04.2018
comment
Вы можете остановиться на этом? Как называется такая реализация? Я пробовал исследовать это, но ничего не могу найти, но, возможно, мои ключевые слова (несколько HTTP-запросов в одном потоке) неверны   -  person R. Bosman    schedule 05.04.2018
comment
Насколько я понимаю, вам всегда нужен пул потоков для выполнения параллельного запроса, поскольку вы всегда должны выполнять запрос синхронным образом. Да, вы можете создать асинхронный API вокруг этого, но на самом деле для выполнения запроса требуется ожидающий поток (опять же, насколько я понимаю)   -  person R. Bosman    schedule 05.04.2018
comment
Нет. Прием сокета (когда данные уже доступны) просто копирует байты из буфера ядра в буфер пользовательского пространства. Отправка обратная: копирование из пользовательского пространства в ядро ​​(при условии, что в буферах ядра есть место). Итак, эти операции являются синхронными в том смысле, что в вашем потоке больше ничего не происходит, пока они выполняются, но они завершаются почти мгновенно. select, poll и аналогичные механизмы позволяют дождаться, пока любой из нескольких сокетов станет доступным для чтения или записи.   -  person Gil Hamilton    schedule 05.04.2018
comment
Попробуйте эти ключевые слова: асинхронный однопоточный сервер   -  person Gil Hamilton    schedule 05.04.2018


Ответы (2)


Чего я не понимаю: нам нужен поток / обработчик, чтобы ждать этих данных, верно?

Не совсем. Идея NIO заключается в том, что никакие потоки никогда не блокируются.

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

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

Итак, в этом случае ЦП подобен потоку в вашем приложении. Это никогда не блокируется. Он всегда делает какие-то вещи, связанные с процессором.

Идея программирования NIO такая же. В случае, если вы разоблачаете, представьте, что ваш HTTP-сервер имеет единственный поток. Когда вы получаете запрос от своего клиента, вам необходимо сделать восходящий запрос (который представляет собой ввод-вывод). Итак, что здесь будет делать структура NIO, так это выдать запрос и зарегистрировать обратный вызов, когда ответ будет доступен.

Сразу после этого ваш ценный единственный поток освобождается для обслуживания еще одного запроса, который регистрирует другой обратный вызов, и так далее, и так далее.

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

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

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

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

Точно так же это причина распространения таких фреймворков, как Netty, RxJava, Reactive Streams Initiative и Project Reactor. Все они стремятся продвигать этот тип оптимизации и модели программирования.

Существует также интересное движение новых фреймворков, которые используют эти мощные функции и пытаются конкурировать или дополнять друг друга. Я говорю об интересных проектах, таких как Vert.x и Ratpack. И я почти уверен, что для других языков существует гораздо больше.

person Edwin Dalorzo    schedule 05.04.2018

Вся идея неблокирующей парадигмы достигается благодаря этой идее под названием «Цикл событий».

Интересные ссылки:

  1. http://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch06lev1sec2.html
  2. Понимание цикла событий
  3. https://www.youtube.com/watch?v=8aGhZQkoFbQ
person Sankarganesh Eswaran    schedule 07.03.2019