Webudvikler begejstret for Node.js: Lynhurtig Javascript på serveren

Med hjælp af open source-værktøjet Node.js er Javascript blevet et hit på webserveren. Fordelene er meget store, lyder budskabet fra en webudvikler hos One.com.

Når nu Google med V8-motoren har fået hastigheden på Javascript skruet voldsomt i vejret, hvorfor så ikke også udnytte teknologien på browserens modpol, serveren? Sådan spurgte opfinderen af Node.js sig selv for to år siden, og siden er Node.js blevet det nye hit til websider, der skal håndtere mange samtidige forespørgsler.

Og fordelene er til at føle på, forklarede webudvikler Morten Siebuhr fra One.com på konferencen Open Source Days, da han delte sine erfaringer med Node.js.

Har man mange forespørgsler ad gangen, vil klassiske trådede servere som Apache hurtigt løbe sur i opgaven, enten med en urimeligt lang responstid, eller et enormt hukommelsesforbrug.

»Normalt kalder du en række databaser, smækker det sammen og serverer det for brugeren. Men det giver en lang responstid ved mange samtidige forespørgsler. Det løser man med threads, men så løber man ind i hukommelsesmuren. Det er ikke muligt at få nok hukommelsesbåndbredde,« ridsede Morten Siebuhr op.

Ved at basere en webapplikation på events med Node.js, slipper man for at vente på hvert eneste database-kald. Serveren sender sin forespørgsel afsted og kan så skynde sig videre med næste punkt i stedet for at vente på svar.

»Du outsourcer arbejdet til databasen. Det svarer til, at man holder op med at hænge i telefonrøret, fra man afgiver sin pizzabestilling, til pizzaen er bagt færdig,« forklarede han.

Effekten af at skifte til Node.js var ikke til at tage fejl af. Selv med 10.000 samtidige forespørgsler, holdt responstiden sig på et acceptabelt niveau ? langt under hvad en Apache-løsning kunne klare, viste Morten Siebuhr på en graf.

Med Googles V8-motor nedenunder bliver Javascript-koden just-in-time-compileret til maskinkode, og Node.js bliver ikke forpustet, selv når antallet af brugere stiger og stiger.

»Det er lynende hurtigt,« konstaterede han.

Javascript fyldt med 'what the fuck'-oplevelser

Normalt koder Morten Siebuhr i Python, og som uddannet datalog har han prøvet lidt af hvert. Men Javascript er han først begyndt på for alvor med Node.js, og det har ikke kun været glade hvedebrødsdage.

»Javascript er nyt for mig ? og jeg er vant til, at et programmeringssprog giver mening. Javascript blev opfundet af Netscape, som havde brug for noget hurtigt, og derfor implementerede Javascript på 14 dage. Det kan mærkes,« sagde Morten Siebuhr og viste nogle 'what the fuck'-eksempler på, hvad han kunne blive irriteret over.

Hvis man for eksempel glemmer et semikolon efter funktioner, brokker Javascript sig ikke ? men der begynder at ske mystiske ting.

Et godt råd til andre, som skulle i gang med Javascript, var derfor at bruge JSLint, som fanger den slags problemer og råber op, hvis der mangler et semikolon, sagde han.

Node.js er også endnu under udvikling og findes foreløbigt kun i version 0.4. Det betyder, at man skal være forberedt på at løbe ind i fejl her og der.

Chat-server med få linjers kode

Men omvendt er fordelene store med Node.js og Javascript på serveren. I stedet for at programmere de dele af en webside, der skal håndtere mange brugere, for sig selv i Erlang, Googles Go eller andre alternativer, er Javascript en naturlig del af en webside.

»Du kan tage den samme Javascript-kode og bruge den i klienten eller på serveren, for eksempel hvis man skal validere form-input både i klienten og på serveren. Det giver nogle nye, interessante muligheder,« sagde Morten Siebuhr.

Som eksempel på, hvor elegant man med Node.js kan håndtere mange samtidige forbindelser, viste han en chat-server skrevet med ganske få linjer Javascript.

»Det er her, at Node.js virkeligt begynder af funkle. Chat-serveren holder en forbindelse åben til hver eneste deltager i chatten, og broadcaster beskeder ved at fylde dem i hver enkelt deltagers
forbindelse,« forklarede han.

Med én tråd kan man således håndtere store mængder brugere, og alle problemerne med trådning er væk som dug for solen.

Læs mere om Node.js hos Mashable.com

Tips og korrekturforslag til denne historie sendes til tip@version2.dk
Kommentarer (20)
sortSortér kommentarer
  • Ældste først
  • Nyeste først
  • Bedste først
Bjarke Walling

Webworkers API'et er blevet portet til node.js. Det giver mulighed for flere tråde/processer med message parsing.

I øvrigt, som kommentar til 'what the fuck'-eksemplerne, så er det en god idé altid at bruge === til sammenligning, da den ikke skifter typerne som ==. Man kan sætte JSLint til at tjekke at man har husket at bruge === eller !==. Og hurtigste konvertering til integer med default 0 er: x | 0.

  • 0
  • 0
Bjarke Walling

En af de fede ting ved node.js i min optik, er at man kan dele moduler mellem browseren og node.js. Man skal dog sørge for at skrive koden, så alle (understøttede) browsere kan være med. F.eks. har arrays en forEach-metode i v8, men det er ikke alle browsere der har det. Eksempel:
[code=javascript](function(myModule) {

// ... kode her ...

myModule.foo = function() {
// ... eksporteret funktion her ...
};

})(typeof(window) === 'undefined' ? exports : window.myModule = {});

// Indlæs med <script src='mymodule.js'></script> i browseren. Husk rækkefølgen mht. dependencies.
// Indlæs med var myModule = require('myModule.js') i node.js.[/code]
Det bruger vi nogle steder ved http://ge.tt/ og jeg bruger det i mine egne projekter, f.eks. i en HTML5-baseret terminal-emulator, hvor selve emulatoren kan køre både klient- og server-side.

  • 0
  • 0
Ulrik Moe

Vi benytter et node.js + redis setup i vores nyeste startup: VilJegSe.dk, som bliver en blanding af imdb og tvtid. Node.js er super fedt at arbejde med og der er ingen grænser for hvad man kan udrette. Kan varmt anbefales :)

  • 0
  • 0
Esben Nielsen

Ja!

Min erfaring er at folk bruger alt, alt for mange tråde. Rigtigt mange problemer kan fint løses i én tråd. Og den samlede løsning i flere processer.

Men desværre er mange ikke blevet opdraget til at tænke events og mange programmeringssprog/omgivelser understøtter det ikke. På unix har man select - men der skal kodes meget ovenpå før det bliver "nemt". Og så virker select ikke på windows, hvor man derimod har WaitForMultibleObjects. Java har senere fået select.

Men mange er fra f.eks. Java men også fra Windows verdenen opdraget til, at når man kommunikere gennem en seriel kanal eller en TCP forbindelse, så skal man altså have én tråd til at modtage data, én tråd til at sende og én tråd til kontrollere det hele. I mange tilfælde bliver trådene ikke brugt som konkrete eksekveringsenheder; men programmeringsabstraktioner på linie med klasser. Og det er de altså alt for dyre til. Ud over at man får alle problemerne fra multitrådet programmering med i købet.

Jeg medgiver at man deværre ofte er tvunget ud i multitrådet programmering af andre. F.eks. skal man lige bruge en FTP klient, som er skrevet med blokerende I/O.

Det virker lidt bagvendt, at udviklerne de sidste mange år er blevet oplært i at kaste unødvendige tråde ind i programmerne; og nu da multi-CPU systemer endelig er blevet almindelige finder man ud af, at man nok burde nøjes med én tråd.

  • 0
  • 0
Robert Larsen

Esben, hvilken verden lever du i ?

Java har NIO, som understøtter non-blocking IO gennem en Selector som holder øje med alle sockets/file descriptors.

Windows understøtter også select(), men har klart nok sine egne måder at gøre tingene på. select() bør nok heller ikke bruges på Unix, for hver kerne har (ligesom windows) sine egne måder at gøre tingene på. Linux har epoll, BSD har kqueue.

Men jeg er enig i, at man skal tænke meget over sit tråd design. Message passing gør det en hel del nemmere, så forskellige tråde/threadpools kan kommunikere og give hinanden opgaver gennem en message kø.

Med CPU intensive opgaver er én tråd bare ikke nok, og det er da også synd ikke at udnytte sine mange kerner. Man skal bare ikke lade flere tråde benytte samme ressource samtidig.

F.eks. kan IO tråden modtage en opgave og levere den videre til en thread pool. Når en worker thread så har løst opgaven leverer den resultatet tilbage til IO tråden som så kan levere den til klienten, uden at nogen træder hinanden over tæerne. Eneste synkroniseringspunkt er message køerne.

Sådan har jeg (med stor success) udviklet i 7 år...de første 6 var et mareridt :-)

  • 0
  • 0
Morten Siebuhr

Det er klart at trådning og event'ing har forskellige styrker, og det gælder selvfølgeligt om at bruge den rigtige teknik til de forskellige problemer.

Ligeledes kan et velskrevet tråd-baseret program ofte holde stand mod et event-ditto (se eks. Varnish), men til udvikling af gennemsnitlige web-backends, syntes jeg selv det er langt nemmere "bare" at skrive noget event-baseret. (Og så kan man jo altid out-source det "hårde" regne-arbejde til en trådet server...)

  • 0
  • 0
Jesper Louis Andersen

Men mange er fra f.eks. Java men også fra Windows verdenen opdraget til, at når man kommunikere gennem en seriel kanal eller en TCP forbindelse, så skal man altså have én tråd til at modtage data, én tråd til at sende og én tråd til kontrollere det hele. I mange tilfælde bliver trådene ikke brugt som konkrete eksekveringsenheder; men programmeringsabstraktioner på linie med klasser. Og det er de altså alt for dyre til.

Med mindre du koder i Erlang, for så er ovenstående model ofte helt fornuftig. Det er meget normalt at du har en "tråd" (kaldet en proces i Erlang fordi der ikke er delt hukommelse mellem dem) per "objekt". Blandt andet fordi det, stort set, er lige så billigt som at have en objekt (næsten, du har lidt færre af dem, men ikke meget).

Faktisk så har jeg præcis hvad du beskriver omking en TCP-socket. Dels fordi det er nemmere at styre den udgående queue, dels fordi jeg godt vil have en separat process til at dekode TCP-inddata med. Og det er ikke ualmindeligt at jeg har godt over 3000 processer.

Men Erlang er også designet til at kunne tåle det - hvilket er grunden til at det kan lade sig gøre.

  • 0
  • 0
Robert Larsen

Som du er inde på skal man nok afvikle CPU tunge opgaver på andet end sin webserver. Og det ligemeget om den kører Node.js, Apache, IIS, Websphere eller noget andet.

Hvem har sagt noget om webserver ?
Node kan andet end det...f.eks. være server side for et JavaScript/HTML baseret multiplayerspil, men så skal tunge regneopgaver uddelegeres til en trådet service eller strikkes ind i noget webworker snask...har ikke kigget så meget på webworkers endnu, så jeg ved ikke om det er løsningen.

  • 0
  • 0
Allan Ebdrup Blogger

Hvem har sagt noget om webserver ?
Node kan andet end det...f.eks. være server side for et JavaScript/HTML baseret multiplayerspil, men så skal tunge regneopgaver uddelegeres til en trådet service eller strikkes ind i noget webworker snask...har ikke kigget så meget på webworkers endnu, så jeg ved ikke om det er løsningen.

Du må undskylde hvis jeg lagde ord i munden på dig, det var ikke min mening. De fleste jeg har set anvende Node.js har anvendt den som webserver. Min pointe var at som sådan er der ikke nogen forskel på Node.js og andre webservere.

Nu er jeg blevet nysgerrig efter hvad du mener med dit eksempel med CPU-tunge opgaver?
Hvilke tunge regneopgaver er der i et JavaScript/HTML baseret multiplayerspil?
Jeg ville tro at de skulle laves så alt det tunge bliver lavet ude på klienten, og at Node.js netop ville være perfekt til det brugsscenarie, hvor der er mange samtidige brugere, der laver små requests der ikke er CPU tunge.... Ellers vil det jo aldrig skalere.

Det korte af det lange er at der er en performance penalty ved at benytte JavaScript til tunge CPU opgaver, sammenlignet med et af de mere traditionelle sprog som C#/Java.

Standardløsningen ville være noget i retningen af at lade Node.js knalde de requests der resulterer i CPU-tunge opgaver på en kø, og lade dedikerede applikationsservere behandle dem asynkront. Så Node.js serveren kan koncentrere sig om det den er god til.

  • 0
  • 0
Esben Nielsen

Pointen er vel at Erlang laver en "process" om til event-behandling. Generelt kan man dette i funktionelle sprog med callcc. (Jeg kender nu ikke Erlang :-( )

Mht. håndtering af buffere: Tjaa, problemet er "kode én gang, brug mange gange". Sjovt nok har jeg lige siddet og kodet samme TCP protokol i Java og C++. Indtil jeg fik bygget mit buffer bibliotek, min event styring omkring select bygget op gik der lang tid. Men siden har det virket upåklageligt. I Java (tidlig runtime uden select), hvor jeg skulle bruge en tråd til at modtage, en tråd til at sende, diverse timere, og så alle de tråde som ville sende, blev der længe ved med dukke problemer op i forbindelse med forskellige fejl situationer. Ting som er meget svære at teste, netop når det multithreaded.

En fordel ved at lave det event styret er, at man kommer til at udtrykke sit program som en funktion

state, event -> ny state, output

Nu er det relativt nemt at skrive en serie unittests som afdækker om denne funktion er implementeret rigtigt - inklusive diverse fejl-tilstande.

  • 0
  • 0
Jesper Louis Andersen

Pointen er vel at Erlang laver en "process" om til event-behandling. Generelt kan man dette i funktionelle sprog med callcc. (Jeg kender nu ikke Erlang :-( )

Det kan ikke rigtigt klassificeres som events og det benytter ej heller en call/cc-lignende løsning. Der er, i modsætning til events, tale om fuldt preemptivt udførte processer. Kontrasten til Node.js og dens cooperative multitasking er slående.

Rent teknisk laves der processer som kører i userland og så mappes, i en n-m skedulering, over til kernetråde. Du har typisk en tråd per (logisk) cpu og også gerne en lille ekstra pulje til blocking I/O. Fordi logikken er placeret i userland, så er processkift lynhurtigt. Sikkerheden kommer fra at sproget er safe og derfor ikke behøver en VM til at beskytte forskellige processer i at pille i hinandens hukommelsesområde.

  • 0
  • 0
Esben Nielsen

Esben, hvilken verden lever du i ?

Nu når du svare så flabet bliver jeg nødt til at svare på dit indlæg

Java har NIO, som understøtter non-blocking IO gennem en Selector som holder øje med alle sockets/file descriptors.

Som sagt har Java først fået det senere. I gamle versioner (som vi stadig er tvunget til at bruge på mit arbejde) er skal man bruge én tråd til modtage... Mange har lært et kode i sådanne versioner.

Windows understøtter også select(), men har klart nok sine egne måder at gøre tingene på.

Den kan kun bruges på sockets. Filer, seriel-porte etc. kan den ikke bruges til.
WaitForMultipleObjects(), som jeg har på fornemmelsen er det kald som ligger inde i maven af select(), er meget mere generelt - også mere generelt end select/poll på Unix. (Jeg fik noget select() baseret kode til VxWorks til at virke på Windows ved bruge WaitForMultipleObjects() engang.)

Men jeg er enig i, at man skal tænke meget over sit tråd design. Message passing gør det en hel del nemmere, så forskellige tråde/threadpools kan kommunikere og give hinanden opgaver gennem en message kø.

Men hvis de er afkoblede via køer, hvorfor ikke bruge forskellige processer? Det vil gøre dit system nemmere at debugge og du kan nemmere skrive i forskellige sprog i de forskellige processer. Kun hvis du skal sende store datamængder rundt mellem trådene/processerne er det dyrt at kopiere.

Med CPU intensive opgaver er én tråd bare ikke nok, og det er da også synd ikke at udnytte sine mange kerner. Man skal bare ikke lade flere tråde benytte samme ressource samtidig.

Ja, men den slags opgaver er relativt sjældne. Det meste man laver er tager ikke ret meget CPU. Det bliver kun tungt, fordi man presser mange af disse opgaver ind i én process og bruger en masse krafter på multithreading. Låse koster. Beskedkøer koster. Hver især koster opgaverne ikke meget og programmer ville flyde udemærket, hvis man blot lod en opgave køre til sit næste I/O punkt uden at kunne blive preempted. Og så genbruger stakken, så man bedre udnytter cachen.

F.eks. kan IO tråden modtage en opgave og levere den videre til en thread pool. Når en worker thread så har løst opgaven leverer den resultatet tilbage til IO tråden som så kan levere den til klienten, uden at nogen træder hinanden over tæerne. Eneste synkroniseringspunkt er message køerne.

Hvis dine opgaver er ægte CPU tunge opgaver, ja så er det en god løsning. Men i mange tilfælde består de enkelte opgaver blot i at lave en lille udregning og sende en kommando videre til noget andet. Så skal men vente på svar og sende dét videre. Men der jo ikke CPU tungt! Det er blot er spørgsmål om at holde styr på sine kommnikationer i nogle datastrukture og opdatere state, når der sker noget.

Det er da rigtigt, at hvis systemet bliver endnu større og belastet er det smart at kunne bruge flere CPU cores. Så må man lave flere tråde i parellel. Men én IO tråd er ikke en god idé, da den så kan blive den begrænsende faktor og skal vægges hver gang. Så er det bedre at disse tråde hver især tager sig af en håndfuld (sammenhørende) IO forbindelser. Du undgår også at data skal gå fra én cache til en anden.

En anden grund til at lave tråde er realtidskrav: Hvis du skal håndtere noget hurtigere end det tungeste event du kan få tager at håndtere, skal du kunne preempte det. Så har du også behov for flere tråde.

Jeg siger ikke tråde er unødvendige. Jeg siger blot at folk misbruger dem, istedet for at bruge non-blocking IO. Men mange gange kan man ikke lave non-blocking IO, da man skal bruge et eksterne kald til database-opslag, GUI, HTTP etc. etc., som typisk ikke er skrevet non-blocking.

Dernæst er det bøvlet i sprog uden en ordentlig closure syntax. I C++ hjælper en ting som boost::function.

  • 0
  • 0
Nikolaj Brinch Jørgensen

@Esben

Som sagt har Java først fået det senere. I gamle versioner (som vi stadig er tvunget til at bruge på mit arbejde) er skal man bruge én tråd til modtage... Mange har lært et kode i sådanne versioner.

Det er interessant at du arbejder professionelt i en pre Java 1.4 udgave. Java 1.4 blev frigivet primo 2002, å du arbejder altså med en Java der er ca 9 år gammel.

Ud over skaleringsproblematikken mht. til trådprogrammering på denne platform (jg går ud fra det er Java 1.3), så der VM så sløv, uopdateret og ikke vel ikke længere sikkerhedsmæssigt up to date?
Hvordan kan det være at der er behov for at benytte denne version?

Hvilken vendor supporterer Java før version 1.4.2, og hvad er grunden til at I er nødt til at benytte en så gammel version?

  • 0
  • 0
Log ind eller Opret konto for at kommentere