Node.js e richieste ad alta intensità di CPU


215

Ho iniziato a armeggiare con il server HTTP Node.js e mi piace molto scrivere Javascript sul lato server, ma qualcosa mi impedisce di iniziare a utilizzare Node.js per la mia applicazione web.

Comprendo l'intero concetto di I / O asincrono ma sono in qualche modo preoccupato per i casi limite in cui il codice procedurale richiede molta CPU, come la manipolazione delle immagini o l'ordinamento di grandi set di dati.

A quanto ho capito, il server sarà molto veloce per le semplici richieste di pagine Web come la visualizzazione di un elenco di utenti o la visualizzazione di un post sul blog. Tuttavia, se voglio scrivere un codice molto intenso per la CPU (ad esempio nel back-end dell'amministratore) che genera grafici o ridimensiona migliaia di immagini, la richiesta sarà molto lenta (alcuni secondi). Poiché questo codice non è asincrono, tutte le richieste che arrivano al server durante quei pochi secondi verranno bloccate fino a quando la mia richiesta lenta non viene eseguita.

Un suggerimento era di utilizzare i Web Worker per attività ad alta intensità di CPU. Tuttavia, temo che gli operatori web renderanno difficile scrivere codice pulito poiché funziona includendo un file JS separato. Cosa succede se il codice intensivo della CPU si trova nel metodo di un oggetto? Fa schifo scrivere un file JS per ogni metodo che richiede molta CPU.

Un altro suggerimento era quello di generare un processo figlio, ma ciò rende il codice ancora meno gestibile.

Qualche suggerimento per superare questo (percepito) ostacolo? Come si scrive codice pulito orientato agli oggetti con Node.js assicurandosi che le attività pesanti della CPU vengano eseguite in modo asincrono?


2
Olivier, hai posto la stessa domanda che avevo in mente (nuovo al nodo) e in particolare per quanto riguarda l'elaborazione delle immagini. In Java posso usare un ExecutorService a thread fisso e passarlo a tutti i lavori di ridimensionamento e aspettare che finisca da tutta la connessione, nel nodo, non ho capito come trasferire il lavoro a un modulo esterno che limita ( ad esempio) il numero massimo di operazioni simultanee su 2 alla volta. Hai trovato un modo elegante per farlo?
Riyad Kalla,

Risposte:


55

Ciò di cui hai bisogno è una coda di attività! Spostare le attività di lunga durata fuori dal server Web è una buona cosa. Mantenere ogni attività nel file js "separato" promuove la modularità e il riutilizzo del codice. Ti costringe a pensare a come strutturare il tuo programma in modo da semplificare il debug e la manutenzione a lungo termine. Un altro vantaggio di una coda di attività è che i lavoratori possono essere scritti in una lingua diversa. Basta inserire un'attività, svolgere il lavoro e riscrivere la risposta.

qualcosa del genere https://github.com/resque/resque

Ecco un articolo di Github sul perché l'hanno costruito http://github.com/blog/542-introducing-resque


35
Perché stai collegando alle librerie di Ruby in una domanda specificamente radicata nel mondo dei nodi?
Jonathan Dumaine,

1
@JonathanDumaine È una buona implementazione di una coda di attività. Rad il codice ruby ​​e riscriverlo in javascript. PROFITTO!
Simon Stender Boisen,

2
Sono un grande fan di Gearman per questo, i lavoratori Gearman non eseguono il polling di un server Gearman per nuovi lavori - i nuovi lavori vengono immediatamente inviati ai lavoratori. Molto reattivo
Casey Flynn

1
In effetti, qualcuno l'ha portato nel mondo dei nodi: github.com/technoweenie/coffee-resque
FrontierPsycho

@pacerier, perché lo dici? Cosa proponi?
luis.espinal,

289

Questo è un fraintendimento della definizione di web server - dovrebbe essere usato solo per "parlare" con i client. Le attività per carichi pesanti dovrebbero essere delegate a programmi autonomi (che ovviamente può anche essere scritto in JS).
Probabilmente diresti che è sporco, ma ti assicuro che un processo del web server bloccato nel ridimensionamento delle immagini è solo peggio (anche per dire Apache, quando non blocca altre query). Tuttavia, è possibile utilizzare una libreria comune per evitare la ridondanza del codice.

EDIT: ho escogitato un'analogia; l'applicazione web dovrebbe essere un ristorante. Hai camerieri (web server) e cuochi (lavoratori). I camerieri sono in contatto con i clienti e svolgono compiti semplici come fornire menu o spiegare se un piatto è vegetariano. D'altra parte delegano compiti più difficili in cucina. Poiché i camerieri fanno solo cose semplici, rispondono rapidamente e i cuochi possono concentrarsi sul proprio lavoro.

Node.js qui sarebbe un cameriere unico ma di grande talento in grado di elaborare molte richieste alla volta, e Apache sarebbe una banda di stupidi camerieri che elaborano solo una richiesta ciascuno. Se questo cameriere Node.js iniziasse a cucinare, sarebbe una catastrofe immediata. Tuttavia, cucinare potrebbe anche esaurire una grande quantità di camerieri Apache, senza menzionare il caos in cucina e la progressiva diminuzione della sensibilità.


6
Bene, in un ambiente in cui i server Web sono multi-thread o multi-processo e possono gestire più di una richiesta simultanea, è molto comune dedicare un paio di secondi a una singola richiesta. Le persone si aspettano questo. Direi che l'incomprensione è che node.js è un web server "normale". Usando node.js devi adattare un po 'il tuo modello di programmazione, e questo include spingere il lavoro "di lunga durata" a un lavoratore asincrono.
Thilo,

13
Non generare un processo figlio per ogni richiesta (che vanifica lo scopo di node.js). Genera lavoratori solo all'interno delle tue richieste pesanti. O instrada il tuo pesante lavoro di background su qualcosa di diverso da node.js.
Thilo,

47
Buona analogia, mbq!
Lance Fisher,

6
Ah, mi piace davvero. "Node.js: far funzionare male le cattive pratiche"
ethan,

7
@mbq Mi piace l'analogia ma potrebbe usare del lavoro. Il modello tradizionale multithread sarebbe una persona che è sia cameriere che cuoco. Una volta preso l'ordine, quella persona deve tornare indietro e cucinare il pasto prima di essere in grado di gestire un altro ordine. Il modello node.js ha i nodi come camerieri e i webworker come cuochi. I camerieri gestiscono il recupero / risoluzione delle richieste mentre i lavoratori gestiscono le attività che richiedono più tempo. Se è necessario ridimensionare le dimensioni, è sufficiente trasformare il server principale in un cluster di nodi e invertire le attività intensive della CPU su altri server creati per l'elaborazione a thread multipli.
Evan Plaice,

16

Non vuoi che il tuo codice intensivo della CPU esegua asincrono, vuoi che venga eseguito in parallelo . È necessario estrarre il processo di elaborazione dal thread che serve le richieste HTTP. È l'unico modo per risolvere questo problema. Con NodeJS la risposta è il modulo cluster, per la generazione di processi figlio per il sollevamento di carichi pesanti. (Il nodo AFAIK non ha alcun concetto di thread / memoria condivisa; è processi o niente). Hai due opzioni per come strutturare la tua applicazione. È possibile ottenere la soluzione 80/20 generando 8 server HTTP e gestendo attività ad alta intensità di calcolo in modo sincrono sui processi figlio. Farlo è abbastanza semplice. Potresti impiegare un'ora per leggerlo a quel link. In effetti, se hai semplicemente strappato il codice di esempio nella parte superiore di quel link, otterrai il 95% del percorso.

L'altro modo per strutturarlo è impostare una coda lavori e inviare grandi attività di calcolo sulla coda. Si noti che l'IPC è associato a molti costi generali per una coda lavori, quindi ciò è utile solo quando le attività sono notevolmente più grandi dell'overhead.

Sono sorpreso che nessuna di queste altre risposte menzioni nemmeno il cluster.

Sfondo: il codice asincrono è un codice che si sospende fino a quando non accade qualcosa altrove , a quel punto il codice si attiva e continua l'esecuzione. Un caso molto comune in cui qualcosa di lento deve accadere altrove è l'I / O.

Il codice asincrono non è utile se è il tuo processore a essere responsabile del lavoro. Questo è esattamente il caso delle attività "ad alta intensità di calcolo".

Ora, potrebbe sembrare che il codice asincrono sia di nicchia, ma in realtà è molto comune. Capita semplicemente di non essere utile per compiti intensivi di calcolo.

L'attesa sull'I / O è un modello che si verifica sempre nei server Web, ad esempio. Ogni client che si connette al tuo server ottiene un socket. Il più delle volte le prese sono vuote. Non vuoi fare nulla fino a quando un socket non riceve alcuni dati, a quel punto vuoi gestire la richiesta. Sotto il cofano un server HTTP come Node sta usando una libreria di eventi (libev) per tenere traccia delle migliaia di socket aperti. Il sistema operativo notifica a libev, quindi libev notifica a NodeJS quando uno dei socket riceve i dati, quindi NodeJS inserisce un evento nella coda degli eventi, e il tuo codice http si attiva a questo punto e gestisce gli eventi uno dopo l'altro. Gli eventi non vengono messi in coda fino a quando il socket non ha alcuni dati, quindi gli eventi non attendono mai i dati: è già lì per loro.

I server Web basati su eventi a thread singolo hanno senso come paradigma quando il collo di bottiglia è in attesa su un sacco di connessioni socket per lo più vuote e non si desidera un intero thread o processo per ogni connessione inattiva e non si desidera eseguire il polling dei 250k socket per trovare il prossimo che contiene dati.


dovrebbe essere la risposta corretta .... per quanto riguarda la soluzione in cui si generano 8 cluster, avresti bisogno di 8 core giusto? O bilanciamento del carico con più server.
Muhammad Umer,

anche quello che è un buon modo per conoscere la seconda soluzione, impostare una coda. Il concetto di coda è piuttosto semplice, ma è parte della messaggistica tra processi e coda che è estranea.
Muhammad Umer,

Giusto. Devi in ​​qualche modo trasferire il lavoro su un altro core. Per questo, hai bisogno di un altro nucleo.
Masonk,

Ri: code. La risposta pratica è utilizzare una coda di lavoro. Ce ne sono alcuni disponibili per il nodo. Non ho mai usato nessuno di loro, quindi non posso fare una raccomandazione. La risposta alla curiosità è che i processi di lavoro e i processi di coda finiranno per comunicare tramite socket.
Masonk,

7

Un paio di approcci che puoi usare.

Come osserva @Tim, è possibile creare un'attività asincrona che si trova all'esterno o parallelamente alla logica di servizio principale. Dipende dai tuoi esatti requisiti, ma anche cron può agire come un meccanismo di accodamento.

I WebWorker possono funzionare per i tuoi processi asincroni ma al momento non sono supportati da node.js. Esistono un paio di estensioni che forniscono supporto, ad esempio: http://github.com/cramforce/node-worker

Puoi ancora riutilizzare i moduli e il codice attraverso il meccanismo standard "richiede". Devi solo assicurarti che l'invio iniziale al lavoratore passi tutte le informazioni necessarie per elaborare i risultati.


0

L'uso child_processè una soluzione. Ma ogni processo figlio generato può consumare molta memoria rispetto a Gogoroutines

Puoi anche usare una soluzione basata sulla coda come kue

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.