Perché diavolo useresti Node.js

Questo articolo proviene da Tomislav Capan, consulente tecnico e appassionato di Node.js. Tomislav lo aveva pubblicato originariamente nell'agosto 2013 nel blog di Toptal - puoi trovare il post originale qui; il blog è stato leggermente aggiornato. Il seguente argomento si basa sull'opinione e le esperienze di questo autore.

introduzione

La crescente popolarità di JavaScript ha portato molti cambiamenti e il volto dello sviluppo web oggi è radicalmente diverso. Le cose che possiamo fare oggi sul web con JavaScript in esecuzione sul server, così come nel browser, erano difficili da immaginare solo diversi anni fa o erano incapsulate in ambienti sandbox come Flash o Applet Java.

Prima di scavare in Node.js, potresti voler leggere i vantaggi dell'utilizzo di JavaScript nello stack, che unifica il formato di lingua e dati (JSON), permettendoti di riutilizzare in modo ottimale le risorse per sviluppatori. Poiché questo è più un vantaggio di JavaScript rispetto a Node.js in particolare, non ne discuteremo molto qui. Ma è un vantaggio fondamentale per incorporare Node.js nel tuo stack.

Node.js è un ambiente di runtime JavaScript basato sul motore JavaScript V8 di Chrome. Vale la pena notare che Ryan Dahl, il creatore di Node.js, mirava a creare siti Web in tempo reale con funzionalità push, "ispirati da applicazioni come Gmail". In Node.js, ha fornito agli sviluppatori uno strumento per lavorare nel paradigma I / O non bloccante e guidato dagli eventi.

In una frase: Node.js brilla nelle applicazioni web in tempo reale che utilizzano la tecnologia push sui socket Web. Cosa c'è di così rivoluzionario in questo? Bene, dopo oltre 20 anni di web senza stato basato sul paradigma di richiesta-risposta senza stato, finalmente abbiamo applicazioni web con connessioni bidirezionali in tempo reale, in cui sia il client che il server possono avviare la comunicazione, consentendo loro di scambiare dati liberamente .

Ciò è in netto contrasto con il tipico paradigma della risposta web, in cui il cliente inizia sempre la comunicazione. Inoltre, è tutto basato sullo stack web aperto (HTML, CSS e JS) in esecuzione sulla porta standard 80.

Si potrebbe sostenere che lo abbiamo avuto per anni sotto forma di applet Flash e Java, ma in realtà si trattava solo di ambienti sandbox che utilizzavano il Web come protocollo di trasporto da consegnare al client. Inoltre, sono stati eseguiti in modo isolato e spesso operavano su porte non standard, che potrebbero aver richiesto autorizzazioni aggiuntive e simili.

Con tutti i suoi vantaggi, Node.js ora svolge un ruolo fondamentale nello stack tecnologico di molte aziende di alto profilo che dipendono dai suoi vantaggi unici. La Fondazione Node.js ha consolidato tutte le migliori considerazioni sul perché le aziende dovrebbero prendere in considerazione Node.js in una breve presentazione che può essere trovata sulla pagina Case Studies della Fondazione Node.js.

In questo post, discuterò non solo di come vengono raggiunti questi vantaggi, ma anche del perché potresti voler usare Node.js - e perché no - usando alcuni dei classici modelli di applicazioni web come esempi.

Come funziona?

L'idea principale di Node.js: utilizzare l'I / O non bloccante, guidato dagli eventi, per rimanere leggero ed efficiente di fronte alle applicazioni in tempo reale ad alta intensità di dati che girano su dispositivi distribuiti.

È un boccone.

Ciò che significa veramente è che Node.js non è una nuova piattaforma di proiettile d'argento che dominerà il mondo dello sviluppo web. Invece, è una piattaforma che soddisfa un'esigenza particolare. E capire questo è assolutamente essenziale. Non vorrai assolutamente usare Node.js per operazioni che richiedono molta CPU; infatti, usarlo per il calcolo pesante annullerà quasi tutti i suoi vantaggi. Dove Node.js brilla davvero è la creazione di applicazioni di rete veloci e scalabili, in quanto è in grado di gestire un numero enorme di connessioni simultanee con un throughput elevato, il che equivale a un'alta scalabilità.

Come funziona sotto il cofano è piuttosto interessante. Rispetto alle tecniche di web serving tradizionali in cui ogni connessione (richiesta) genera un nuovo thread, occupando la RAM di sistema ed eventualmente massimizzando la quantità di RAM disponibile, Node.js opera su un singolo thread, usando I / non bloccanti O chiama, permettendogli di supportare decine di migliaia di connessioni simultanee (trattenute nel loop degli eventi).

* Immagine tratta dal post del blog originale.

Un rapido calcolo: supponendo che ogni thread abbia potenzialmente 2 MB di memoria di accompagnamento, l'esecuzione su un sistema con 8 GB di RAM ci mette a un massimo teorico di 4000 connessioni simultanee (calcoli presi dall'articolo di Michael Abernethy “Proprio quello che è Nodo .js? ”, pubblicato su IBM developerWorks nel 2011; sfortunatamente, l'articolo non è più disponibile), oltre al costo del cambio di contesto tra thread. Questo è lo scenario che in genere affronti nelle tecniche di web serving tradizionali. Evitando tutto ciò, Node.js raggiunge livelli di scalabilità di oltre 1 milione di connessioni simultanee e oltre 600.000 connessioni simultanee a websocket.

Esiste, ovviamente, la questione della condivisione di un singolo thread tra tutte le richieste dei client ed è una potenziale insidia nella scrittura di applicazioni Node.js. In primo luogo, il calcolo pesante potrebbe soffocare il singolo thread di Node e causare problemi a tutti i client (ne parleremo più avanti) poiché le richieste in arrivo verrebbero bloccate fino al completamento di tale calcolo. In secondo luogo, gli sviluppatori devono fare molta attenzione a non consentire un'eccezione che si diffonde nel ciclo degli eventi Node.js principale (più in alto), che causerà la chiusura dell'istanza Node.js (arresto efficace del programma).

La tecnica utilizzata per evitare eccezioni che gorgogliano in superficie sta restituendo gli errori al chiamante come parametri di callback (invece di lanciarli, come in altri ambienti). Anche se alcune eccezioni non gestite riescono a rompersi, sono stati sviluppati strumenti per monitorare il processo Node.js ed eseguire il ripristino necessario di un'istanza bloccata (anche se probabilmente non sarai in grado di ripristinare lo stato corrente della sessione utente), il più comune è il modulo Forever o l'utilizzo di un approccio diverso con strumenti di sistema esterni upstart e monit, o anche solo upstart.

npm: il gestore pacchetti nodo

Quando si discute di Node.js, una cosa che sicuramente non dovrebbe essere omessa è il supporto integrato per la gestione dei pacchetti utilizzando lo strumento npm fornito di default con ogni installazione di Node.js. L'idea dei moduli npm è abbastanza simile a quella di Ruby Gems: un insieme di componenti riutilizzabili disponibili al pubblico, disponibili attraverso una facile installazione tramite un repository online, con gestione della versione e delle dipendenze.

Un elenco completo dei moduli impacchettati è disponibile sul sito Web npm o è possibile accedervi utilizzando lo strumento CLI npm che viene installato automaticamente con Node.js. L'ecosistema di moduli è aperto a tutti e chiunque può pubblicare il proprio modulo che verrà elencato nel repository npm. Una breve introduzione a npm è disponibile in una Guida per principianti e dettagli sui moduli di pubblicazione nel Tutorial di pubblicazione di npm.

Alcuni dei moduli npm più utili oggi sono:

  • express - Express.js, un framework di sviluppo web ispirato a Sinatra per Node.js e lo standard di fatto per la maggior parte delle applicazioni Node.js disponibili oggi.
  • hapi: un framework molto modulare e semplice da usare per la configurazione di applicazioni web e di servizi
  • connect - Connect è un framework di server HTTP estensibile per Node.js, che fornisce una raccolta di "plug-in" ad alte prestazioni noti come middleware; funge da base di base per Express.
  • socket.io e sockjs - Componente lato server dei due componenti WebSocket più comuni oggi disponibili.
  • pug (ex Jade) - Uno dei più popolari motori di template, ispirato a HAML, predefinito in Express.js.
  • mongodb e mongojs: wrapper MongoDB per fornire l'API per database di oggetti MongoDB in Node.js.
  • redis - Libreria client Redis.
  • lodash (underscore, lazy.js) - La cintura di utilità JavaScript. Underscore ha avviato il gioco, ma è stato rovesciato da una delle sue due controparti, principalmente a causa delle migliori prestazioni e dell'implementazione modulare.
  • per sempre - Probabilmente l'utilità più comune per garantire che un determinato script di nodo venga eseguito continuamente. Mantiene il tuo processo Node.js in produzione di fronte a guasti imprevisti.
  • bluebird - Un'implementazione Promises / A + completa con prestazioni eccezionalmente buone
  • moment - Una libreria di date JavaScript leggera per l'analisi, la convalida, la manipolazione e la formattazione delle date.

L'elenco continua. Ci sono tonnellate di pacchetti davvero utili là fuori, disponibili a tutti (senza offesa per quelli che ho omesso qui).

Dove utilizzare Node.js

CHIACCHIERARE

La chat è l'applicazione multiutente in tempo reale più tipica. Dall'IRC (back in the day), attraverso molti protocolli proprietari e aperti in esecuzione su porte non standard, alla possibilità di implementare tutto oggi in Node.js con i websocket in esecuzione sulla porta standard 80.

L'applicazione di chat è davvero l'esempio perfetto di Node.js: è un'applicazione leggera, ad alto traffico, ad alta intensità di dati (ma bassa elaborazione / calcolo) che viene eseguita su dispositivi distribuiti. È anche un ottimo caso d'uso anche per l'apprendimento, poiché è semplice, ma copre la maggior parte dei paradigmi che userete mai in una tipica applicazione Node.js.

Proviamo a descrivere come funziona.

Nello scenario più semplice, sul nostro sito Web abbiamo un'unica chat room in cui le persone vengono e possono scambiare messaggi uno-a-molti (in realtà tutti). Ad esempio, supponiamo che sul sito Web siano presenti tre persone tutte collegate alla nostra bacheca.

Sul lato server, abbiamo una semplice applicazione Express.js che implementa due cose: 1) un gestore di richieste GET '/' che serve la pagina web contenente sia una bacheca di messaggi che un pulsante 'Invia' per inizializzare l'input di nuovi messaggi, e 2) un server websocket che ascolta i nuovi messaggi emessi dai client websocket.

Sul lato client, abbiamo una pagina HTML con un paio di gestori impostati, uno per l'evento clic sul pulsante "Invia", che raccoglie il messaggio di input e lo invia nel websocket, e un altro che ascolta i nuovi messaggi in arrivo sul client dei websocket (ovvero i messaggi inviati da altri utenti, che ora il server desidera visualizzare sul client).

Quando uno dei clienti pubblica un messaggio, ecco cosa succede:

  • Il browser rileva il pulsante "Invia" tramite un gestore JavaScript, raccoglie il valore dal campo di input (ovvero il testo del messaggio) ed emette un messaggio websocket utilizzando il client websocket collegato al nostro server (inizializzato sull'inizializzazione della pagina Web).
  • Il componente lato server della connessione websocket riceve il messaggio e lo inoltra a tutti gli altri client connessi utilizzando il metodo di trasmissione.
  • Tutti i client ricevono il nuovo messaggio come messaggio push tramite un componente lato websocket in esecuzione all'interno della pagina Web. Quindi raccolgono il contenuto del messaggio e aggiornano la pagina Web sul posto aggiungendo il nuovo messaggio alla scheda.
Immagine tratta dal blog originale.

Questo è l'esempio più semplice. Per una soluzione più solida, è possibile utilizzare una semplice cache basata sull'archivio Redis. O in una soluzione ancora più avanzata, una coda di messaggi per gestire l'instradamento dei messaggi ai client e un meccanismo di consegna più robusto che può coprire perdite temporanee della connessione o archiviare i messaggi per i clienti registrati mentre sono offline. Ma indipendentemente dai miglioramenti apportati, Node.js continuerà a funzionare con gli stessi principi di base: reagire agli eventi, gestire molte connessioni simultanee e mantenere la fluidità dell'esperienza dell'utente.

API SOPRA UN DB DI OGGETTI

Sebbene Node.js brilli davvero con le applicazioni in tempo reale, è abbastanza naturale per esporre i dati dai DB di oggetti (ad esempio MongoDB). I dati memorizzati JSON consentono a Node.js di funzionare senza l'impedenza non corrispondente e la conversione dei dati.

Ad esempio, se stai usando Rails, convertiresti da JSON a modelli binari, quindi esponili indietro come JSON su HTTP quando i dati vengono consumati da React.js, Angular.js, ecc., O anche semplicemente jQuery AJAX chiamate. Con Node.js, puoi semplicemente esporre i tuoi oggetti JSON con un'API REST da utilizzare per il client. Inoltre, non devi preoccuparti della conversione tra JSON e qualsiasi altra cosa quando leggi o scrivi dal tuo database (se stai usando MongoDB). In breve, è possibile evitare la necessità di più conversioni utilizzando un formato di serializzazione dei dati uniforme su client, server e database.

INGRESSI QUEUED

Se ricevi una grande quantità di dati simultanei, il tuo database può diventare un collo di bottiglia. Come illustrato sopra, Node.js può facilmente gestire da solo le connessioni simultanee. Ma poiché l'accesso al database è un'operazione di blocco (in questo caso), si verificano problemi. La soluzione è riconoscere il comportamento del cliente prima che i dati vengano realmente scritti nel database.

Con questo approccio, il sistema mantiene la sua reattività sotto un carico pesante, il che è particolarmente utile quando il cliente non ha bisogno di una conferma definitiva della corretta scrittura dei dati. Esempi tipici includono: la registrazione o la scrittura di dati di tracciamento dell'utente, elaborati in batch e non utilizzati in un secondo momento; così come le operazioni che non hanno bisogno di essere riflesse all'istante (come l'aggiornamento di un conteggio di "Mi piace" su Facebook) dove è accettabile l'eventuale consistenza (così spesso usata nel mondo NoSQL).

I dati vengono messi in coda attraverso una sorta di infrastruttura di memorizzazione nella cache o di accodamento dei messaggi (MQ) (ad es. RabbitMQ, ZeroMQ) e digeriti da un processo separato di scrittura batch del database o servizi di back-end di elaborazione intensiva di calcolo, scritti in una piattaforma con prestazioni migliori per tali attività. Un comportamento simile può essere implementato con altri linguaggi / framework, ma non sullo stesso hardware, con lo stesso throughput elevato e mantenuto.

Immagine tratta dall'articolo originale.

In breve: con Node, puoi spingere lateralmente le scritture del database e gestirle in seguito, procedendo come se fossero riuscite.

STREAMING DEI DATI

Nelle piattaforme Web più tradizionali, le richieste e le risposte HTTP vengono trattate come eventi isolati; in realtà, sono in realtà flussi. Questa osservazione può essere utilizzata in Node.js per creare alcune interessanti funzionalità. Ad esempio, è possibile elaborare i file mentre vengono ancora caricati, poiché i dati arrivano attraverso uno stream e possiamo elaborarli in modo online. Questo potrebbe essere fatto per la codifica audio o video in tempo reale e il proxy tra origini dati diverse (vedere la sezione successiva).

DELEGA

Node.js è facilmente impiegato come proxy sul lato server dove può gestire una grande quantità di connessioni simultanee in modo non bloccante. È particolarmente utile per il proxy di servizi diversi con tempi di risposta diversi o per la raccolta di dati da più punti di origine.

Un esempio: considera un'applicazione lato server che comunica con risorse di terze parti, estrae dati da diverse fonti o archivia risorse come immagini e video su servizi cloud di terze parti.

Sebbene esistano server proxy dedicati, l'utilizzo di Node potrebbe essere utile se l'infrastruttura di proxy è inesistente o se è necessaria una soluzione per lo sviluppo locale. Con questo intendo che potresti creare un'app sul lato client con un server di sviluppo Node.js per risorse e richieste API proxy / di stub, mentre in produzione gestiresti tali interazioni con un servizio proxy dedicato (nginx, HAProxy, ecc. .).

BROKERAGE - CRUSCOTTO DEL COMMERCIO DI STOCK

Torniamo al livello dell'applicazione. Un altro esempio in cui domina il software desktop, ma che potrebbe essere facilmente sostituito con una soluzione web in tempo reale, è il software di trading dei broker, utilizzato per tenere traccia dei prezzi delle azioni, eseguire calcoli / analisi tecniche e creare grafici / grafici.

Il passaggio a una soluzione basata sul Web in tempo reale consentirebbe ai broker di passare facilmente da una stazione di lavoro all'altra. Presto potremmo iniziare a vederli sulla spiaggia in Florida .. o Ibiza .. o Bali.

CRUSCOTTO DI MONITORAGGIO DELL'APPLICAZIONE

Un altro caso d'uso comune in cui Node-with-web-socket si adatta perfettamente: tracciare i visitatori del sito Web e visualizzare le loro interazioni in tempo reale. Potresti raccogliere statistiche in tempo reale dal tuo utente o addirittura spostarlo al livello successivo introducendo interazioni mirate con i tuoi visitatori aprendo un canale di comunicazione quando raggiungono un punto specifico nella tua canalizzazione - un esempio di questo può essere trovato con CANDDi.

Immagina come migliorare la tua attività se sapessi cosa stavano facendo i tuoi visitatori in tempo reale, se potessi visualizzare le loro interazioni. Con i socket bidirezionali in tempo reale di Node.js, ora puoi farlo.

CRUSCOTTO DI MONITORAGGIO DEL SISTEMA

Ora, visitiamo il lato infrastrutturale delle cose. Immagina, ad esempio, un provider SaaS che desidera offrire ai suoi utenti una pagina di monitoraggio del servizio (ad esempio, la pagina Stato GitHub). Con il ciclo degli eventi Node.js, possiamo creare un potente dashboard basato sul Web che controlla gli stati dei servizi in modo asincrono e invia i dati ai client utilizzando i websocket.

Sia gli stati interni (intra-aziendali) che quelli dei servizi pubblici possono essere segnalati dal vivo e in tempo reale utilizzando questa tecnologia. Spingi ulteriormente questa idea e prova a immaginare un Network Operations Center (NOC) che monitora le applicazioni in un operatore di telecomunicazioni, un fornitore di cloud / rete / hosting o qualche istituzione finanziaria, tutte eseguite sullo stack Web aperto supportato da Node.js e websocket invece di Java e / o Applet Java.

Nota: non tentare di creare sistemi in tempo reale su Node.js (ovvero sistemi che richiedono tempi di risposta coerenti). Erlang è probabilmente una scelta migliore per quella classe di applicazioni.

Dove è possibile utilizzare Node.js

APPLICAZIONI WEB LATO SERVER

Node.js con Express.js può anche essere utilizzato per creare applicazioni Web classiche sul lato server. Tuttavia, sebbene possibile, questo paradigma di richiesta-risposta in cui Node.js porterebbe in giro HTML renderizzato non è il caso d'uso più tipico. Ci sono argomenti da sostenere a favore e contro questo approccio. Ecco alcuni fatti da considerare:

Professionisti:

  • Se la tua applicazione non ha alcun calcolo intensivo per la CPU, puoi crearla in Javascript dall'alto verso il basso, anche fino al livello del database se usi DB di archiviazione JSON come MongoDB. Ciò facilita notevolmente lo sviluppo (incluso l'assunzione).
  • I crawler ricevono una risposta HTML completamente renderizzata, che è molto più SEO-friendly di, per esempio, un'applicazione a pagina singola o un'app di websocket eseguita su Node.js.

Contro:

  • Qualsiasi calcolo intensivo della CPU bloccherà la reattività di Node.js, quindi una piattaforma threaded è un approccio migliore. In alternativa, potresti provare a ridimensionare il calcolo (*).
  • L'uso di Node.js con un database relazionale è ancora piuttosto problematico (vedi sotto per maggiori dettagli). Fatti un favore e raccogli qualsiasi altro ambiente come Rails, Django o ASP.Net MVC se stai cercando di eseguire operazioni relazionali.

(*) Un'alternativa ai calcoli intensivi della CPU è quella di creare un ambiente supportato da MQ altamente scalabile con elaborazione back-end per mantenere Node come un "impiegato" frontale per gestire le richieste dei client in modo asincrono.

Dove Node.js non dovrebbe essere usato

APPLICAZIONE WEB LATO SERVER CON UN DATABASE RELATIVO DIETRO

Confrontando Node.js con Express.js con Ruby on Rails, ad esempio, c'è una decisione chiara a favore di quest'ultima quando si tratta dell'accesso ai dati relazionali.

Gli strumenti di DB relazionali per Node.js sono ancora piuttosto sottosviluppati, rispetto alla concorrenza. D'altra parte, Rails fornisce automagicamente la configurazione dell'accesso ai dati immediatamente, insieme agli strumenti di supporto per le migrazioni dello schema DB e ad altre gemme (gioco di parole). Rails e i suoi framework peer hanno implementazioni di livello di accesso ai dati Active Record o Data Mapper mature e comprovate, che ti mancheranno molto se provi a replicarle in puro JavaScript.

Tuttavia, se sei davvero propenso a rimanere JS fino in fondo, dai un'occhiata a Sequelize e Node ORM2.

(*) È possibile e non raro utilizzare Node.js esclusivamente come facciata rivolta al pubblico, mantenendo il back-end di Rails e il suo facile accesso a un DB relazionale.

INFORMATICA / ELABORAZIONE LATERALE DEL SERVER PESANTE

Quando si tratta di calcoli pesanti, Node.js non è la migliore piattaforma in circolazione. No, non vuoi assolutamente costruire un server di calcolo Fibonacci in Node.js. In generale, qualsiasi operazione intensiva della CPU annulla tutti i vantaggi della velocità effettiva offerti dal nodo con il suo modello I / O non-block-driven, poiché eventuali richieste in arrivo verranno bloccate mentre il thread è occupato con il crunching dei numeri.

Come affermato in precedenza, Node.js è a thread singolo e utilizza solo un singolo core della CPU. Quando si tratta di aggiungere la concorrenza su un server multi-core, c'è un po 'di lavoro svolto dal team principale Node sotto forma di un modulo cluster. Puoi anche eseguire diverse istanze del server Node.js abbastanza facilmente dietro un proxy inverso tramite nginx.

Con il clustering, è comunque necessario scaricare tutti i calcoli pesanti su processi in background scritti in un ambiente più appropriato e farli comunicare tramite un server della coda messaggi come RabbitMQ.

Anche se l'elaborazione in background potrebbe essere inizialmente eseguita sullo stesso server, un simile approccio ha il potenziale per una scalabilità molto elevata. Tali servizi di elaborazione in background potrebbero essere facilmente distribuiti su server di lavoro separati senza la necessità di configurare i carichi di server Web frontali.

Ovviamente, utilizzeresti lo stesso approccio anche su altre piattaforme, ma con Node.js ottieni quel throughput elevato di reqs / sec di cui abbiamo parlato, poiché ogni richiesta è una piccola attività gestita in modo rapido ed efficiente.

Conclusione

Abbiamo discusso di Node.js dalla teoria alla pratica, iniziando con i suoi obiettivi e le sue ambizioni e finendo con i suoi punti deboli e le insidie. Quando le persone incontrano problemi con Node, si riduce quasi sempre al fatto che le operazioni di blocco sono la radice di tutti i mali - il 99% degli abusi di Node è una conseguenza diretta.

Ricorda: Node.js non è mai stato creato per risolvere il problema di ridimensionamento del calcolo. È stato creato per risolvere il problema di ridimensionamento I / O, che funziona davvero bene.

Quindi, pensaci: se il tuo caso d'uso non contiene operazioni ad alta intensità di CPU né accede a risorse di blocco, puoi sfruttare i vantaggi di Node.js e goderti applicazioni di rete veloci e scalabili. Benvenuti nel web in tempo reale.