Как несколько одновременных запросов обрабатываются в Node.js, когда ответ асинхронный c? - PullRequest
1 голос
/ 05 мая 2020

Я могу представить себе ситуацию, когда на один Node.js сервер приходит 100 запросов. Для каждого из них требуются некоторые взаимодействия с БД, которые реализованы в некотором родном асинхронном c коде - с использованием очереди задач или, по крайней мере, очереди микрозадач (например, интерфейс драйвера БД обещан).

Как Node.js возвращает ответ, когда обработчик запросов перестал синхронизироваться c? Что происходит с подключением из api / веб-клиента, откуда исходят эти 100 запросов из описания?

1 Ответ

2 голосов
/ 05 мая 2020

Эта функция доступна на уровне ОС и называется (как ни странно) асинхронным вводом / выводом или неблокирующим вводом / выводом (Windows также вызывает / называет его перекрытым вводом / выводом).

На самом низком уровне, в C (C # / Swift), операционная система предоставляет API для отслеживания запросов и ответов. Существуют различные API-интерфейсы, доступные в зависимости от вашей ОС, и Node.js использует libuv для автоматического выбора лучшего доступного API во время компиляции, но для понимания того, как работает асинхронный API, давайте посмотрим на API который доступен для всех платформ: системный вызов select().

Функция select() выглядит примерно так:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, time *timeout);

Данные fd_set Структура - это набор / список файловых дескрипторов, в которых вы хотите наблюдать на предмет активности ввода-вывода. И помните, что в сокетах POSIX также есть файловые дескрипторы. Вы используете этот API следующим образом:

// Pseudocode:

// Say you just sent a request to a mysql database and also sent a http
// request to google maps. You are waiting for data to come from both.
// Instead of calling `read()` which would block the thread you add
// the sockets to the read set:

add mysql_socket to readfds
add maps_socket to readfds

// Now you have nothing else to do so you are free to wait for network
// I/O. Great, call select:

select(2, &readfds, NULL, NULL, NULL);

// Select is a blocking call. Yes, non-blocking I/O involves calling a
// blocking function. Yes it sounds ironic but the main difference is
// that we are not blocking waiting for each individual I/O activity,
// we are waiting for ALL of them

// At some point select returns. This is where we check which request
// matches the response:

check readfds if mysql_socket is set {
    then call mysql_handler_callback()
}

check readfds if maps_socket is set {
    then call maps_handler_callback()
}

go to beginning of loop

Итак, в основном ответ на ваш вопрос состоит в том, что мы проверяем структуру данных, какой сокет / файл только что инициировал операцию ввода-вывода, и выполняем соответствующий код.

Вы, без сомнения, легко заметите, как обобщить этот шаблон кода: вместо того, чтобы вручную настраивать и проверять файловые дескрипторы, вы можете сохранить все ожидающие асинхронные c запросы и обратные вызовы в списке или массиве и l oop через это до и после select(). Фактически, это то, что делает Node.js (и javascript в целом). И именно этот список обратных вызовов / файловых дескрипторов иногда называют очередью событий - это не очередь как таковая, а просто набор вещей, которые вы ожидаете выполнения.

Функция select() также имеет параметр тайм-аута в конце, который можно использовать для реализации setTimeout() и setInterval() и в браузерах обработки событий GUI, чтобы мы могли запускать код в ожидании ввода-вывода. Помните, что select блокирует - мы можем запустить другой код, только если select вернет. При тщательном управлении таймерами мы можем вычислить соответствующее значение для передачи в качестве тайм-аута select.

Структура данных fd_set на самом деле не является связанным списком. В более старых реализациях это битовое поле. Более современная реализация может улучшить битовое поле, если оно соответствует API. Но это частично объясняет, почему существует так много конкурирующих асинхронных c API, таких как poll, epoll, kqueue et c. Они были созданы, чтобы преодолеть ограничения select. Различные API-интерфейсы по-разному отслеживают файловые дескрипторы, некоторые используют связанные списки, некоторые используют таблицы ha sh, некоторые обеспечивают масштабируемость (возможность прослушивать десятки тысяч сокетов), а некоторые обеспечивают скорость, и большинство из них стараются сделать и то, и другое лучше чем другие. Что бы они ни использовали, в конечном итоге то, что используется для хранения запроса, является просто структурой данных, которая отслеживает файловые дескрипторы .

...