Promise.all () против ожидания - PullRequest
4 голосов
/ 12 июля 2020

Я пытаюсь понять node.js однопоточную архитектуру и событие oop, чтобы сделать наше приложение более эффективным. Итак, рассмотрим этот сценарий, когда мне нужно сделать несколько вызовов базы данных для вызова http api. Я могу сделать это, используя Promise.all() или используя отдельный await.

пример:

Используя async / await

await inserToTable1();
await insertToTable2();
await updateTable3();

Используя Promise.all() Я могу сделать то же самое с помощью

await Promise.all[inserToTable1(), insertToTable2(), updateTable3()]

Здесь для одного обращения к API в данный момент времени Promise.all() будет быстрее возвращать ответ, поскольку он запускает вызовы DB параллельно. Но если у меня будет 1000 обращений API в секунду, будет ли разница? Для этого сценария Promise.all() лучше для события oop?

Обновление Предположим следующее. Под 1000 обращениями API я имел в виду общий c трафик приложения. Предположим, существует 20-25 API. Некоторые из них могут выполнять операции с БД, некоторые могут делать несколько HTTP-вызовов и т. Д. c. Кроме того, мы никогда не достигнем максимального количества подключений к пулу БД.

Заранее спасибо !!

Ответы [ 2 ]

4 голосов
/ 12 июля 2020

Как обычно, когда дело доходит до системного дизайна, ответ: это зависит .

Есть много факторов, которые определяют производительность того и другого. В общем, ожидание одного Promise.all() ожидает всех запросов параллельно.

Событие L oop

Событие l oop использует ровно 0% процессорного времени для ожидания запроса. . См. Мой ответ на этот связанный вопрос для объяснения того, как именно работает событие l oop: Производительность NodeJS с большим количеством обратных вызовов

Итак, из события l oop с точки зрения * нет реальной разницы между запросом последовательно и запросом параллельно с Promise.all(). Итак, если это суть вашего вопроса, я думаю, ответ будет , между ними нет разницы .

Однако обработка обратных вызовов требует процессорного времени. Опять же, время завершения выполнения всех обратных вызовов одинаковое. Таким образом, с точки зрения производительности ЦП между ними нет разницы .

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

Даже если служба, которую вы запрашиваете, не является многопоточной и фактически обрабатывает запросы последовательно, или если сервер, с которого вы запрашиваете, является единственным Core CPU (в наши дни это редко, но вы все еще можете арендовать одноядерные виртуальные машины), то параллельные запросы сокращают сетевые накладные расходы, поскольку ваша ОС может отправлять несколько запросов в одном кадре Ethe rnet, таким образом амортизируя накладные расходы на заголовки пакетов по нескольким запросам. Однако это дает убывающую отдачу, превышающую полдюжины параллельных запросов.

Одна тысяча запросов

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

Пулы соединений.

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

В некоторых реализациях выполнение 1000 запросов через такую ​​библиотеку вызовет низкоуровневый сетевой код библиотека для пакетной обработки 5 запросов за раз. Это означает, что у вас может быть не более 5 параллельных запросов (при условии, что пул состоит из 5 соединений). В этом случае совершенно безопасно выполнить 1000 параллельных запросов.

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

Ограничение количества подключений.

Большинство баз данных, таких как Mysql и Postgresql, позволяют admin, чтобы настроить ограничение на количество подключений, например 5, чтобы база данных отклоняла больше, чем ограниченное количество подключений на IP-адрес. Если вы используете библиотеку, которая не управляет автоматически максимальным количеством подключений к вашей базе данных, тогда ваша база данных примет первые 5 запросов и отклонит оставшиеся до тех пор, пока не станет доступен другой слот (возможно, что соединение будет освобождено до того, как node.js завершит открытие 1000-го сокета. ). В этом случае вы не можете успешно выполнить 1000 параллельных запросов - вам нужно управлять количеством выполняемых параллельных запросов.

Некоторые службы API также ограничивают количество подключений, которые вы можете установить параллельно. Например, Google Maps ограничивает вас до 500 запросов в секунду. Поэтому ожидание 1000 параллельных запросов приведет к сбою 50% ваших запросов и, возможно, к блокировке вашего ключа API или IP-адреса.

Сетевые ограничения.

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

Однако все существующие операционные системы ограничивают максимальное количество открытых сокетов. В Linux (например, Ubuntu & Android) и Unix (например, MacOSX и iOS) сокеты реализованы как файловые дескрипторы. И существует максимальное количество файловых дескрипторов, выделяемых для каждого процесса.

Для Linux это число обычно по умолчанию равно 1024 файлам. Обратите внимание, что по умолчанию процесс открывает 3 файловых дескриптора: stdin, stdout и stderr. Остается 1021 файловый дескриптор, общий для файлов и сокетов. Таким образом, ваш 1000 параллельных запросов очень близок к этому числу и может завершиться неудачно, если два клиента попытаются сделать 1000 параллельных запросов одновременно.

Это число можно увеличить, но оно имеет жесткое ограничение. Текущее максимальное количество файловых дескрипторов, которое вы можете настроить на Linux, составляет 590432. Однако эта экстремальная конфигурация работает правильно только в однопользовательской системе без запущенных демонов (или других фоновых программ).

Что делать?

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

С async / await это просто. Вы можете сделать что-то вроде этого:

let parallel_requests = 10;

while (one_thousand_requests.length > 0) {
    let batch = [];

    for (let i=0;i<parallel_requests;i++) {
        let req = one_thousand_requests.pop();
        if (req) {
            batch.push(req());
        }
    }

    await Promise.all(batch);
}

Как правило, чем больше запросов вы можете сделать параллельно, тем лучше (короче) общее время обработки. Думаю, это то, что вы хотели услышать. Но вам нужно сбалансировать параллелизм с указанными выше факторами. 5 в целом нормально. 10 может быть. 100 будет зависеть от ответа сервера на запросы. 1000 или больше, и администратору, который установил сервер, вероятно, придется настроить свою ОС.

2 голосов
/ 12 июля 2020
Подход

await приостанавливает выполнение функции для каждого вызова await и выполняет их последовательно, в то время как Promise.all может выполнять вещи параллельно (в asyn c) и возвращать успех, когда все они успешны.

Так что лучше использовать Promise.all, если ваши три (inserToTable1(), insertToTable2(), table3()) метода независимы.

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

Циклы событий

Отделение вызывающего абонента от ответа позволяет среде выполнения JavaScript делать другие вещи, ожидая ваша асинхронная операция для завершения и их обратные вызовы для запуска.

JavaScript среды выполнения содержат очередь сообщений, в которой хранится список сообщений, которые должны быть обработаны, и связанные с ними функции обратного вызова. Эти сообщения помещаются в очередь в ответ на внешние события (например, щелчок мышью или получение ответа на HTTP-запрос), если была предоставлена ​​функция обратного вызова.

Событие L oop имеет одно простое задание - для мониторинга стека вызовов и очереди обратных вызовов. Если стек вызовов пуст, он берет первое событие из очереди и передает его sh стеку вызовов, который его эффективно запускает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...