Узел.JS и интенсивные запросы процессора


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

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

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

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

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

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

5 193

5 ответов:

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

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

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

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

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

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

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

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

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

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

асинхронный код не полезен, если это процессор который отвечает за выполнение работы. Именно так обстоит дело с" вычислительными интенсивными " задачами.

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

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

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

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

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

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

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

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

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