Uso Node.js al lavoro e lo trovo molto potente. Costretto a scegliere una parola per descrivere Node.js, direi "interessante" (che non è un aggettivo puramente positivo). La comunità è vibrante e in crescita. JavaScript, nonostante le sue stranezze, può essere un ottimo linguaggio per codificare. E ripenserai quotidianamente la tua comprensione delle "migliori pratiche" e dei modelli di codice ben strutturato. C'è un'enorme energia di idee che scorre in Node.js in questo momento, e lavorare in esso ti espone a tutto questo pensiero: un ottimo sollevamento pesi mentale.
Node.js in produzione è sicuramente possibile, ma lontano dalla distribuzione "chiavi in mano" apparentemente promessa dalla documentazione. Con Node.js v0.6.x, "cluster" è stato integrato nella piattaforma, fornendo uno degli elementi costitutivi essenziali, ma il mio script "production.js" è ancora ~ 150 righe di logica per gestire cose come la creazione del log directory, riciclaggio dei lavoratori morti, ecc. Per un servizio di produzione "serio", devi anche essere pronto a limitare le connessioni in entrata e fare tutto ciò che Apache fa per PHP . Ad essere onesti, Ruby on Rails ha questo esatto problema. È risolto tramite due meccanismi complementari: 1) Mettere Ruby su Rails / Node.Apache / che gestirà i processi di lavoro, li riciclerà periodicamente, ecc. Devo ancora trovare un framework di servizio Node.js che sembra completamente cotto; potrebbe esistere, ma non l'ho ancora trovato e uso ancora ~ 150 righe nel mio "production.js" arrotolato a mano.Lighttd ). Il server web può servire in modo efficiente contenuto statico, accedere alla registrazione, riscrivere gli URL, terminare SSL , applicare regole di accesso e gestire più servizi secondari. Per le richieste che raggiungono il servizio nodo effettivo, il server Web inoltra la richiesta tramite. 2) Utilizzo di un framework come Unicorn
La lettura di framework come Express fa sembrare che la pratica standard sia semplicemente quella di servire tutto attraverso un servizio Node.js tuttofare ... "app.use (express.static (__ dirname + '/ public'))" . Per servizi a basso carico e sviluppo, probabilmente va bene. Ma non appena provi a caricare molto tempo il tuo servizio e lo esegui 24 ore su 24, 7 giorni su 7, scoprirai rapidamente le motivazioni che spingono i grandi siti a disporre di un codice C ben consolidato, come Nginx che fronteggia il loro sito e gestisce tutto delle richieste di contenuto statico (... fino a quando non si imposta un CDN , come Amazon CloudFront )). Per un approccio un po 'umoristico e spudoratamente negativo su questo, vedi questo ragazzo .
Node.js sta trovando anche sempre più usi non di servizio. Anche se stai usando qualcos'altro per servire il contenuto web, potresti comunque usare Node.js come strumento di compilazione, usando i moduli npm per organizzare il tuo codice, Browserify per ricucirlo in una singola risorsa e uglify-js per minimizzarlo per la distribuzione . Per quanto riguarda il Web, JavaScript è una corrispondenza di impedenza perfetta e spesso lo rende la via di attacco più semplice. Ad esempio, se si desidera passare attraverso un sacco di payload di risposta JSON , è necessario utilizzare il mio modulo CLI di sottolineatura , la cintura di utilità dei dati strutturati.
Pro e contro:
- Pro: Per un ragazzo server, scrivere JavaScript sul back-end è stato un "farmaco gateway" per l'apprendimento dei moderni schemi di interfaccia utente. Non ho più paura di scrivere il codice client.
- Pro: tende a incoraggiare il corretto controllo degli errori (err viene restituito praticamente da tutti i callback, assillando il programmatore per gestirlo; inoltre, async.js e altre librerie gestiscono il paradigma "fall se una di queste attività secondarie fallisce" molto meglio del tipico codice sincrono )
- Pro: alcune attività interessanti e normalmente difficili diventano banali, come ottenere lo stato delle attività in volo, comunicare tra i lavoratori o condividere lo stato della cache
- Pro: community enorme e tonnellate di ottime librerie basate su un solido gestore di pacchetti (npm)
- Contro: JavaScript non ha una libreria standard. Ti abitui così tanto all'importazione di funzionalità che sembra strano quando usi JSON.parse o qualche altro metodo build in che non richiede l'aggiunta di un modulo npm. Ciò significa che ci sono cinque versioni di tutto. Anche i moduli inclusi nel "core" di Node.js hanno altre cinque varianti se non sei soddisfatto dell'implementazione predefinita. Ciò porta a una rapida evoluzione, ma anche a un certo livello di confusione.
Contro un semplice modello a processo singolo ( LAMP ):
- Pro: scalabile fino a migliaia di connessioni attive. Molto veloce e molto efficiente. Per una flotta web, ciò potrebbe significare una riduzione di 10 volte il numero di caselle richieste rispetto a PHP o Ruby
- Pro: scrivere modelli paralleli è facile. Immagina di dover recuperare tre (o N) BLOB da Memcached . Fallo in PHP ... hai appena scritto il codice recupera il primo BLOB, poi il secondo, poi il terzo? Wow, è lento. C'è un PECL speciale modulo per risolvere quel problema specifico per Memcached, ma cosa succede se si desidera recuperare alcuni dati Memcached in parallelo con la query del database? In Node.js, poiché il paradigma è asincrono, avere una richiesta Web che fa più cose in parallelo è molto naturale.
- Contro: il codice asincrono è fondamentalmente più complesso del codice sincrono e la curva di apprendimento iniziale può essere difficile per gli sviluppatori senza una solida comprensione di cosa significhi effettivamente l'esecuzione simultanea. Tuttavia, è enormemente meno difficile che scrivere qualsiasi tipo di codice multithreading con il blocco.
- Contro: se una richiesta ad alta intensità di calcolo viene eseguita, ad esempio, per 100 ms, bloccherà l'elaborazione di altre richieste che vengono gestite nello stesso processo Node.js ... AKA, cooperativa-multitasking . Ciò può essere mitigato con il modello Web Workers (scissione di un sottoprocesso per gestire l'attività costosa). In alternativa, è possibile utilizzare un gran numero di lavoratori Node.js e consentire a ciascuno di gestire una singola richiesta contemporaneamente (ancora abbastanza efficiente perché non è possibile riciclare il processo).
- Contro: l'esecuzione di un sistema di produzione è MOLTO più complicata di un modello CGI come Apache + PHP, Perl , Ruby , ecc. Eccezioni non gestite faranno cadere l'intero processo, richiedendo la logica per riavviare i lavoratori falliti (vedi cluster ). I moduli con codice nativo difettoso possono arrestare il processo. Ogni volta che un lavoratore muore, tutte le richieste che stava gestendo vengono eliminate, quindi un'API buggy può facilmente degradare il servizio per altre API ospitate.
Contro la scrittura di un servizio "reale" in Java / C # / C (C? Davvero?)
- Pro: eseguire operazioni asincrone in Node.js è più semplice rispetto alla sicurezza dei thread in qualsiasi altro luogo e offre probabilmente maggiori vantaggi. Node.js è il paradigma asincrono di gran lunga meno doloroso in cui abbia mai lavorato. Con buone librerie, è solo leggermente più difficile della scrittura di codice sincrono.
- Pro: nessun bug di multithreading / blocco. È vero, investi in anticipo nello scrivere codice più dettagliato che esprima un flusso di lavoro asincrono adeguato senza operazioni di blocco. E devi scrivere alcuni test e far funzionare il tutto (è un linguaggio di scripting e i nomi delle variabili di diteggiatura grassa vengono catturati solo al momento del test unitario). MA, una volta che lo fai funzionare, la superficie per gli heisenbugs - strani problemi che si manifestano solo una volta su un milione di corse - quella superficie è solo molto più bassa. Le tasse che scrivono il codice Node.js sono pesantemente caricate in anticipo nella fase di codifica. Quindi tendi a finire con un codice stabile.
- Pro: JavaScript è molto più leggero per esprimere la funzionalità. È difficile dimostrarlo con le parole, ma JSON , tipizzazione dinamica, notazione lambda, eredità prototipale, moduli leggeri, qualunque cosa ... tende solo a prendere meno codice per esprimere le stesse idee.
- Contro: Forse ti piacciono davvero tanto i servizi di codifica in Java?
Per un'altra prospettiva su JavaScript e Node.js, dai un'occhiata a Da Java a Node.js , un post sul blog sulle impressioni e le esperienze di uno sviluppatore Java sull'apprendimento di Node.js.
Moduli
Quando si considera il nodo, tenere presente che la scelta delle librerie JavaScript DEFINIRÀ la propria esperienza. La maggior parte delle persone usa almeno due, un aiutante di pattern asincrono (Step, Futures, Async) e un modulo di zucchero JavaScript ( Underscore.js ).
Helper / JavaScript Sugar:
- Underscore.js : usa questo. Fallo e basta. Rende il tuo codice piacevole e leggibile con cose come _.isString () e _.isArray (). Non sono davvero sicuro di come potresti scrivere un codice sicuro altrimenti. Inoltre, per migliorare la riga di comando-fu, dai un'occhiata al mio Underscore-CLI .
Moduli asincroni:
- Step : un modo molto elegante per esprimere combinazioni di azioni seriali e parallele. La mia raccomandazione personale. Vedi il mio post su come appare il codice Step.
- Futures : un modo molto più flessibile (è davvero una buona cosa?) Di esprimere l'ordinamento attraverso i requisiti. Può esprimere cose come "inizia a, b, c in parallelo. Quando A e B finiscono, inizia AB. Quando A e C finiscono, inizia AC". Tale flessibilità richiede maggiore cura per evitare bug nel flusso di lavoro (come non chiamare mai il callback o chiamarlo più volte). Vedi il post di Raynos sull'uso dei futures (questo è il post che mi ha fatto "ottenere" futures).
- Async : libreria più tradizionale con un metodo per ogni modello. Ho iniziato con questo prima della mia conversione religiosa al passo e alla successiva realizzazione che tutti gli schemi in Async potevano essere espressi al Passo con un unico paradigma più leggibile.
- TameJS - Scritto da OKCupid, è un precompilatore che aggiunge un nuovo linguaggio primitivo "wait" per scrivere in modo elegante flussi di lavoro seriali e paralleli. Il modello sembra sorprendente, ma richiede una pre-compilazione. Sto ancora decidendo su questo.
- StreamlineJS - concorrente di TameJS. Mi sto inclinando verso Tame, ma puoi decidere da solo.
O per leggere tutto sulle biblioteche asincrone, vedere questo panel-intervista con gli autori.
Web Framework:
- Esprimi un ottimo framework Ruby on Rails-esk per l'organizzazione di siti web. Utilizza JADE come motore di template XML / HTML, il che rende la costruzione di HTML molto meno dolorosa, quasi persino elegante.
- jQuery Sebbene tecnicamente non sia un modulo nodo, jQuery sta rapidamente diventando uno standard di fatto per l'interfaccia utente lato client. jQuery fornisce selettori simili a CSS per "interrogare" insiemi di elementi DOM su cui è possibile operare (impostare gestori, proprietà, stili, ecc.). Allo stesso modo, il framework CSS Bootstrap di Twitter , Backbone.js per un modello MVC e Browserify.js per ricamare tutti i tuoi file JavaScript in un singolo file. Tutti questi moduli stanno diventando standard di fatto, quindi dovresti almeno controllarli se non ne hai sentito parlare.
test:
- JSHint - Deve usare; All'inizio non l'ho usato, il che ora sembra incomprensibile. JSLint aggiunge una serie di verifiche di base ottenute con un linguaggio compilato come Java. Parentesi non corrispondente, variabili non dichiarate, tipi di molte forme e dimensioni. Puoi anche attivare varie forme di ciò che chiamo "modalità anale" in cui verifichi lo stile degli spazi bianchi e quant'altro, il che è OK se questa è la tua tazza di tè - ma il valore reale deriva dal ricevere un feedback immediato sul numero esatto di riga in cui hai dimenticato un ")" di chiusura ... senza dover eseguire il codice e toccare la linea offensiva. "JSHint" è una variante più configurabile di Douglas Crockford s' JSLint .
- Concorrente moka ai voti che sto iniziando a preferire. Entrambi i framework gestiscono le basi abbastanza bene, ma i modelli complessi tendono ad essere più facili da esprimere in Mocha.
- Vows Vows è davvero piuttosto elegante. E stampa un delizioso rapporto (--spec) che mostra quali casi di test sono passati / falliti. Dedica 30 minuti all'apprendimento e puoi creare test di base per i tuoi moduli con il minimo sforzo.
- Zombie - Test senza testa per HTML e JavaScript utilizzando JSDom come "browser" virtuale. Roba molto potente. Combinalo con Replay per ottenere test deterministici velocissimi del codice nel browser.
- Un commento su come "pensare" test:
- Il test non è facoltativo. Con un linguaggio dinamico come JavaScript, ci sono molto pochi controlli statici. Ad esempio, passare due parametri a un metodo che prevede 4 non si interromperà fino all'esecuzione del codice. Barra piuttosto bassa per la creazione di bug in JavaScript. I test di base sono essenziali per colmare il divario di verifica con le lingue compilate.
- Dimentica la convalida, esegui il codice. Per ogni metodo, il mio primo caso di validazione è "niente si rompe", ed è il caso che si attiva più spesso. Dimostrando che il tuo codice viene eseguito senza generare l'80% dei bug e farà così tanto per migliorare la sicurezza del tuo codice che ti ritroverai indietro e aggiungendo i casi di validazione sfumati che hai saltato.
- Inizia in piccolo e rompi la barriera inerziale. Siamo tutti pigri e premuti per il tempo, ed è facile vedere i test come "lavoro extra". Quindi inizia in piccolo. Scrivi test case 0 - carica il tuo modulo e segnala il successo. Se ti costringi a fare così tanto, allora la barriera inerziale ai test si rompe. Sono <30 minuti per farlo la prima volta, inclusa la lettura della documentazione. Ora scrivi il test case 1 - chiama uno dei tuoi metodi e verifica "non si rompe nulla", cioè che non ricevi un errore. Il caso di test 1 dovrebbe richiedere meno di un minuto. Con l'inerzia scomparsa, diventa facile espandere in modo incrementale la copertura del test.
- Ora evolvi i tuoi test con il tuo codice. Non lasciarti intimidire da come sarebbe il test end-to-end "corretto" con server simulati e tutto il resto. Il codice inizia in modo semplice e si evolve per gestire nuovi casi; anche i test dovrebbero. Man mano che aggiungi nuovi casi e nuova complessità al tuo codice, aggiungi casi di test per esercitare il nuovo codice. Quando trovi dei bug, aggiungi verifiche e / o nuovi casi per coprire il codice difettoso. Quando esegui il debug e perdi la fiducia in un pezzo di codice, torna indietro e aggiungi dei test per dimostrare che sta facendo quello che pensi che sia. Acquisisci stringhe di dati di esempio (da altri servizi che chiami, siti Web che scarichi, qualunque cosa) e inseriscili nel tuo codice di analisi. Alcuni casi qui, una migliore convalida lì e ti ritroverai con un codice altamente affidabile.
Inoltre, controlla l' elenco ufficiale dei moduli Node.js consigliati. Tuttavia, il Wiki dei moduli di nodo di GitHub è molto più completo e una buona risorsa.
Per comprendere il nodo, è utile considerare alcune delle scelte chiave di progettazione:
Node.js è EVENT BASED e ASYNCHRONOUS / NON-BLOCKING. Eventi, come una connessione HTTP in entrata, attiveranno una funzione JavaScript che svolge un po 'di lavoro e dà il via ad altre attività asincrone come la connessione a un database o l'estrazione di contenuti da un altro server. Una volta avviate queste attività, la funzione evento termina e Node.js ritorna in modalità sospensione. Non appena succede qualcos'altro, come la connessione al database stabilita o il server esterno che risponde con il contenuto, le funzioni di callback vengono attivate e viene eseguito più codice JavaScript, potenzialmente dando il via a attività ancora più asincrone (come una query del database). In questo modo, Node.js alternerà felicemente le attività per più flussi di lavoro paralleli, eseguendo qualunque attività sia sbloccata in qualsiasi momento. Questo è il motivo per cui Node.js fa un ottimo lavoro gestendo migliaia di connessioni simultanee.
Perché non usare solo un processo / thread per connessione come tutti gli altri?In Node.js, una nuova connessione è solo un'allocazione di heap molto piccola. L'avvio di un nuovo processo richiede molta più memoria, un megabyte su alcune piattaforme. Ma il costo reale è il sovraccarico associato al cambio di contesto. Quando hai 10 ^ 6 thread del kernel, il kernel deve fare molto lavoro per capire chi dovrebbe eseguire il prossimo. Un sacco di lavoro è stato dedicato alla costruzione di uno scheduler O (1) per Linux, ma alla fine è solo molto più efficiente avere un singolo processo guidato dagli eventi rispetto a 10 ^ 6 processi in competizione per il tempo della CPU. Inoltre, in condizioni di sovraccarico, il modello multi-processo si comporta in modo molto scarso, affamando i servizi di amministrazione e gestione critici, in particolare SSHD (il che significa che non è nemmeno possibile accedere alla scatola per capire quanto sia fottuto).
Node.js è SINGOLO FILETTATO e SENZA BLOCCO . Node.js, come scelta progettuale molto deliberata, ha un solo thread per processo. Per questo motivo, è fondamentalmente impossibile per più thread accedere ai dati contemporaneamente. Pertanto, non sono necessari blocchi. Le discussioni sono difficili. Davvero molto difficile. Se non ci credi, non hai fatto abbastanza programmazione thread. Ottenere il blocco corretto è difficile e risulta in bug che sono davvero difficili da rintracciare. L'eliminazione di blocchi e multi-thread fa scomparire una delle classi più cattive di bug. Questo potrebbe essere il più grande vantaggio singolo del nodo.
Ma come posso sfruttare la mia scatola a 16 core?
Due strade:
- Per attività di calcolo pesanti come la codifica delle immagini, Node.js può attivare processi figlio o inviare messaggi a processi di lavoro aggiuntivi. In questo progetto, avresti un thread che gestisce il flusso di eventi e N processi che svolgono compiti di calcolo pesanti e masticano le altre 15 CPU.
- Per ridimensionare il throughput su un servizio Web, è necessario eseguire più server Node.js su una casella, una per core, utilizzando il cluster (Con Node.js v0.6.x, il modulo "cluster" ufficiale collegato qui sostituisce la versione di learnboost che ha un'API diversa). Questi server Node.js locali possono quindi competere su un socket per accettare nuove connessioni, bilanciando il carico su di essi. Una volta accettata una connessione, questa diventa strettamente legata a uno di questi processi condivisi. In teoria, suona male, ma in pratica funziona abbastanza bene e ti permette di evitare il mal di testa di scrivere un codice thread-safe. Inoltre, ciò significa che Node.js ottiene un'eccellente affinità con la cache della CPU, utilizzando in modo più efficace la larghezza di banda della memoria.
Node.js ti consente di fare alcune cose davvero potenti senza sudare. Supponiamo di avere un programma Node.js che esegue una varietà di attività, ascolta su unaporta TCP per i comandi, codifica alcune immagini, qualunque cosa. Con cinque righe di codice, è possibile aggiungere un portale di gestione Web basato su HTTP che mostra lo stato corrente delle attività attive. È facile da fare:
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(myJavascriptObject.getSomeStatusInfo());
}).listen(1337, "127.0.0.1");
Ora puoi premere un URL e controllare lo stato del processo in esecuzione. Aggiungi alcuni pulsanti e hai un "portale di gestione". Se hai uno script Perl / Python / Ruby in esecuzione, semplicemente "lanciare un portale di gestione" non è esattamente semplice.
Ma JavaScript non è lento / cattivo / cattivo / spawn-of-the-diavolo? JavaScript ha alcune strane stranezze, ma con "le parti buone" c'è un linguaggio molto potente lì, e in ogni caso, JavaScript è la lingua sul client (browser). JavaScript è qui per rimanere; altre lingue lo prendono come un IL e talenti di livello mondiale sono in competizione per produrre i motori JavaScript più avanzati. A causa del ruolo di JavaScript nel browser, un'enorme quantità di sforzi ingegneristici è stata lanciata nel rendere JavaScript estremamente rapido. V8è l'ultimo e il più grande motore javascript, almeno per questo mese. Spazza via gli altri linguaggi di scripting sia in efficienza che in stabilità (guardandoti, Ruby). E migliorerà solo con enormi team che lavorano sul problema in Microsoft, Google e Mozilla, in competizione per costruire il miglior motore JavaScript (Non è più un "interprete" JavaScript poiché tutti i motori moderni fanno tonnellate di JITcompilazione sotto il cofano con interpretazione solo come fallback per il codice di esecuzione una volta). Sì, tutti vorremmo poter correggere alcune delle più strane scelte di lingua JavaScript, ma non è poi così male. E il linguaggio è così dannatamente flessibile che non stai davvero codificando JavaScript, stai codificando Step o jQuery - più di qualsiasi altra lingua, in JavaScript, le librerie definiscono l'esperienza. Per creare applicazioni Web, devi comunque conoscere JavaScript, quindi codificare con esso sul server ha una sorta di sinergia di competenze. Non mi ha fatto paura di scrivere il codice client.
Inoltre, se odi davvero JavaScript, puoi usare lo zucchero sintattico come CoffeeScript . O qualsiasi altra cosa che crei codice JavaScript, come Google Web Toolkit (GWT).
A proposito di JavaScript, cos'è una "chiusura"? - Praticamente un modo fantasioso per dire che si mantengono variabili con ambito lessicale nelle catene di chiamate. ;) Come questo:
var myData = "foo";
database.connect( 'user:pass', function myCallback( result ) {
database.query("SELECT * from Foo where id = " + myData);
} );
// Note that doSomethingElse() executes _BEFORE_ "database.query" which is inside a callback
doSomethingElse();
Vedi come puoi semplicemente usare "myData" senza fare nulla di imbarazzante come metterlo in un oggetto? E a differenza di Java, la variabile "myData" non deve essere di sola lettura. Questa potente funzionalità linguistica rende la programmazione asincrona molto meno dettagliata e meno dolorosa.
Scrivere codice asincrono sarà sempre più complesso della scrittura di un semplice script a thread singolo, ma con Node.js non è molto più difficile e ottieni molti vantaggi oltre all'efficienza e alla scalabilità di migliaia di connessioni simultanee. ..