Node.js и ресурсоемкие запросы

215

Я начал работать с HTTP-сервером Node.js и очень хотел писать Javascript на стороне сервера, но что-то мешает мне начать использовать Node.js для моего веб-приложения.

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

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

Одним из предложений было использование Web Workers для задач с интенсивным использованием процессора. Тем не менее, я боюсь, что веб-работникам будет сложно писать чистый код, поскольку он работает, включая отдельный файл JS. Что если интенсивный процессорный код находится в методе объекта? Это отстойно писать файл JS для каждого метода, интенсивно использующего процессор.

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

Любые предложения по преодолению этого (предполагаемого) препятствия? Как вы пишете чистый объектно-ориентированный код с Node.js, в то же время гарантируя, что тяжелые задачи процессора выполняются асинхронно?

Оливье Лалонд
источник
2
Оливье, вы задали тот же вопрос, который я имел в виду (новичок в ноде), особенно в отношении обработки изображений. В Java я могу использовать ExecutorService с фиксированным потоком и передать ему все задания по изменению размера и дождаться его завершения из всех соединений, в узле я не выяснил, как переместить работу на внешний модуль, который ограничивает (давайте скажем) максимальное количество одновременных операций до 2 за раз. Вы нашли элегантный способ сделать это?
Рияд Калла

Ответы:

55

Что вам нужно, это очередь задач! Перенос ваших долгосрочных задач с веб-сервера - это ХОРОШО. Хранение каждой задачи в «отдельном» файле js способствует модульности и повторному использованию кода. Это заставляет вас задуматься о том, как структурировать вашу программу таким образом, чтобы облегчить ее отладку и сопровождение в долгосрочной перспективе. Еще одно преимущество очереди задач - рабочие могут быть написаны на другом языке. Просто поставьте задачу, сделайте работу и напишите ответ.

что-то вроде этого https://github.com/resque/resque

Вот статья из github о том, почему они ее построили http://github.com/blog/542-introduction-resque

Тим
источник
35
Почему вы ссылаетесь на библиотеки Ruby в вопросе, специально основанном на мире узлов?
Джонатан Думайн
1
@JonathanDumaine Это хорошая реализация очереди задач. Рад код рубина и переписать его в JavaScript. PROFIT!
Саймон Стендер Боизен
2
Я большой поклонник снаряжения для этого, работники снаряжения не опрашивают сервер снаряжения для новых рабочих мест - новые рабочие места немедленно выдвигаются рабочим. Очень отзывчивый
Кейси Флинн
1
Фактически, кто-то перенес его в мир узлов: github.com/technoweenie/coffee-resque
FrontierPsycho
@ spacerier, почему ты так говоришь? Что вы предлагаете?
luis.espinal
289

Это неправильное понимание определения веб-сервера - его следует использовать только для «общения» с клиентами. Задачи с высокой нагрузкой следует делегировать автономным программам (это, конечно, можно также написать в JS).
Вы, вероятно, сказали бы, что это грязно, но я уверяю вас, что процесс веб-сервера, застрявший в изменении размеров изображений, только хуже (даже для, скажем, Apache, когда он не блокирует другие запросы). Тем не менее, вы можете использовать общую библиотеку, чтобы избежать избыточности кода.

РЕДАКТИРОВАТЬ: я придумал аналогию; веб-приложение должно быть как ресторан. У вас есть официанты (веб-сервер) и повара (рабочие). Официанты общаются с клиентами и выполняют простые задачи, такие как предоставление меню или объяснение того, является ли какое-то блюдо вегетарианским. С другой стороны, они делегируют более сложные задачи на кухню. Поскольку официанты делают только простые вещи, они быстро реагируют, а повара могут сосредоточиться на своей работе.

Здесь Node.js будет единственным, но очень талантливым официантом, который может обрабатывать много запросов одновременно, а Apache будет бандой тупых официантов, которые будут обрабатывать только один запрос каждый. Если этот официант Node.js начнет готовить, это станет настоящей катастрофой. Тем не менее, приготовление пищи также может истощить даже большое количество официантов Apache, не говоря уже о хаосе на кухне и постепенном снижении чувствительности.

МБк
источник
6
Что ж, в среде, где веб-серверы являются многопоточными или многопроцессными и могут обрабатывать более одного одновременного запроса, очень часто тратится пара секунд на один запрос. Люди ожидают этого. Я бы сказал, что недоразумение заключается в том, что node.js является «обычным» веб-сервером. Используя node.js, вы должны немного скорректировать свою модель программирования, что включает в себя «длительную» работу для некоторого асинхронного работника.
Тило
13
Не создавайте дочерний процесс для каждого запроса (который противоречит цели node.js). Ищите рабочих изнутри только ваши тяжелые запросы. Или направьте свою тяжелую фоновую работу на что-то отличное от node.js.
Тило
47
Хорошая аналогия, МБК!
Лэнс Фишер
6
Ха, мне действительно это нравится. «Node.js: плохие методы работают плохо»
Этан
7
@mbq Мне нравится аналогия, но она может использовать некоторую работу. Традиционной многопоточной моделью будет человек, который одновременно и официант, и повар. Как только заказ принят, этот человек должен вернуться и приготовить еду, прежде чем сможет выполнить другой заказ. Модель node.js имеет узлы в качестве официантов и веб-работников в качестве поваров. Официанты обрабатывают выборку / разрешение запросов, в то время как рабочие выполняют более трудоемкие задачи. Если вам нужно увеличить масштаб, просто сделайте основной сервер кластером узлов и перенаправьте задачи, интенсивно загружающие ЦП, на другие серверы, созданные для многопоточной обработки.
Эван Плейс
16

Вы не хотите, чтобы ваш процессор с интенсивным использованием процессора выполнял асинхронно, вы хотите, чтобы он выполнялся параллельно . Вам нужно вывести обработку из потока, который обслуживает HTTP-запросы. Это единственный способ решить эту проблему. С NodeJS ответом является кластерный модуль , чтобы дочерние процессы для выполнения тяжелой работы. (У узла AFAIK нет понятия потоков / разделяемой памяти; это процессы или ничего). У вас есть два варианта структуры вашего приложения. Вы можете получить решение 80/20, создав 8 HTTP-серверов и синхронно обрабатывая ресурсоемкие задачи на дочерних процессах. Делать это довольно просто. Вы можете потратить час, чтобы прочитать об этом по этой ссылке. На самом деле, если вы просто сорвете пример кода в верхней части этой ссылки, вы получите 95% пути.

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

Я удивлен, что ни один из этих других ответов даже не упоминает кластер.

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

Асинхронный код бесполезен, если за работу отвечает ваш процессор . Именно так обстоит дело с «интенсивными вычислениями».

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

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

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

masonk
источник
должен быть правильный ответ .... что касается решения, при котором вы создаете 8 кластеров, вам понадобятся 8 ядер, верно? Или балансировщик нагрузки с несколькими серверами.
Мухаммед Умер
также, что является хорошим способом узнать о втором решении, настройке очереди. Концепция очереди довольно проста, но это часть обмена сообщениями между процессами и очередью, которая чужда.
Мухаммед Умер
Это правильно. Вам нужно как-то перенести работу на другое ядро. Для этого вам нужно другое ядро.
масон
Re: очереди. Практический ответ - использовать очередь заданий. Есть несколько доступных для узла. Я никогда не использовал ни одного из них, поэтому я не могу дать рекомендации. Любопытный ответ заключается в том, что рабочие процессы и процессы очереди в конечном итоге будут взаимодействовать через сокеты.
масон
7

Пара подходов, которые вы можете использовать.

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

WebWorkers могут работать для ваших асинхронных процессов, но в настоящее время они не поддерживаются node.js. Есть несколько расширений, которые обеспечивают поддержку, например: http://github.com/cramforce/node-worker

Вы по-прежнему получаете возможность использовать модули и код через стандартный механизм «требуется». Вам просто нужно убедиться, что начальная отправка работнику передает всю информацию, необходимую для обработки результатов.

Тоби хеде
источник
0

Использование child_processэто одно решение. Но каждый порожденный дочерний процесс может потреблять много памяти по сравнению с Gogoroutines

Вы также можете использовать решение на основе очереди, например, Kue

нео
источник