Как обычно, когда дело доходит до системного дизайна, ответ: это зависит .
Есть много факторов, которые определяют производительность того и другого. В общем, ожидание одного 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 или больше, и администратору, который установил сервер, вероятно, придется настроить свою ОС.