Ajoin kerran Audilla, jossa oli V8-kaksoisturbomoottori, ja sen suorituskyky oli uskomaton. Ajoin noin 140MPH: lla IL-80-moottoritiellä lähellä Chicagoa kello 3.00, kun tien päällä ei ollut ketään. Siitä lähtien termi 'V8' on liittynyt minulle korkeaan suorituskykyyn.
Node.js on alusta, joka on rakennettu Chromen V8 JavaScript-moottoriin nopeaan ja skaalautuvaan verkkosovellusten rakentamiseen.Vaikka Audin V8 on erittäin tehokas, polttoainesäiliön kapasiteetti on silti rajallinen. Sama koskee Googlen V8: ta - Node.js: n takana olevaa JavaScript-moottoria. Sen suorituskyky on uskomaton, ja Node.js: lle on monia syitä toimii hyvin monissa käyttötapauksissa , mutta kasan koko rajoittaa sinua aina. Kun sinun on käsiteltävä lisää pyyntöjä Node.js-sovelluksessa, sinulla on kaksi vaihtoehtoa: skaalaa pystysuunnassa tai vaaka vaakasuunnassa. Vaakasuuntainen skaalaus tarkoittaa, että joudut suorittamaan enemmän samanaikaisia sovellusinstansseja. Kun se tehdään oikein, päädyt palvelemaan enemmän pyyntöjä. Pystysuuntainen skaalaus tarkoittaa, että sinun on parannettava sovelluksen muistin käyttöä ja suorituskykyä tai lisättävä sovelluksen ilmentymälle käytettävissä olevia resursseja.
Äskettäin minua pyydettiin työskentelemään Node.js-sovelluksen kanssa yhdelle ApeeScape-asiakkaastani korjaamaan muistivuodon ongelma. Sovellus, API-palvelin, oli tarkoitus pystyä käsittelemään satoja tuhansia pyyntöjä joka minuutti. Alkuperäisessä sovelluksessa oli lähes 600 Mt RAM-muistia, ja siksi päätimme ottaa käyttöön nopeat API-päätepisteet ja toteuttaa ne uudelleen. Yleiskustannuksista tulee erittäin kalliita, kun sinun on palveltava monia pyyntöjä.
Uuteen sovellusliittymään valitsimme uudelleensäätö natiivilla MongoDB-ohjaimella ja Kue taustatyöhön. Kuulostaa erittäin kevyeltä pinolta, eikö? Ei aivan. Huippukuormituksen aikana uusi sovellusinstanssi voisi kuluttaa jopa 270 Mt RAM-muistia. Siksi unelmani saada kaksi hakemustapausta 1X Heroku Dynoa kohti katosi.
Jos etsit 'miten löytää vuoto solmusta', ensimmäinen todennäköisesti löytämäsi työkalu on muistikello . Alkuperäinen paketti hylättiin kauan sitten, eikä sitä enää ylläpidetä. Voit kuitenkin löytää helposti sen uudemmat versiot GitHub'sista haarukkaluettelo arkistoon . Tämä moduuli on hyödyllinen, koska se voi aiheuttaa vuototapahtumia, jos se näkee kasan kasvavan viiden peräkkäisen roskakokoelman yli.
Suuri työkalu, joka sallii Node.js-kehittäjät ottaa kasan tilannekuvan ja tarkastaa ne myöhemmin Chrome Developer Tools -sovelluksella.
Jopa hyödyllisempi vaihtoehto heapdumpille, koska sen avulla voit muodostaa yhteyden käynnissä olevaan sovellukseen, ottaa kasan dumpin ja jopa debugata ja kääntää sen uudelleen lennossa.
Valitettavasti et voi muodostaa yhteyttä tuotantosovelluksiin, jotka ovat käynnissä Herokulla, koska se ei salli signaalien lähettämistä käynnissä oleville prosesseille. Heroku ei kuitenkaan ole ainoa isäntäalusta.
Jos haluat kokea solmutarkastajan toiminnassa, kirjoitamme yksinkertaisen Node.js-sovelluksen käyttämällä restify-toimintoa ja laitamme siihen pienen muistivuodon lähteen. Kaikki tässä olevat kokeet tehdään Node.js v0.12.7: lla, joka on koottu V8 v3.28.71.19: ää vastaan.
var restify = require('restify'); var server = restify.createServer(); var tasks = []; server.pre(function(req, res, next) { tasks.push(function() { return req.headers; }); // Synchronously get user from session, maybe jwt token req.user = { id: 1, username: 'Leaky Master', }; return next(); }); server.get('/', function(req, res, next) { res.send('Hi ' + req.user.username); return next(); }); server.listen(3000, function() { console.log('%s listening at %s', server.name, server.url); });
Sovellus on tässä hyvin yksinkertainen ja siinä on hyvin ilmeinen vuoto. Taulukko tehtäviä kasvaisi sovelluksen käyttöiän ajan aiheuttaen sen hidastumisen ja lopulta kaatumisen. Ongelmana on, että vuotamme paitsi sulkemisen myös kokonaiset pyyntöobjektit.
V8: n GC käyttää maailman pysäytys -strategiaa, joten se tarkoittaa enemmän muistissa olevia esineitä, sitä kauemmin vie roskat. Alla olevasta lokista näet selvästi, että sovelluksen käyttöiän alussa roskien kerääminen vie keskimäärin 20 ms, mutta myöhemmin muutama sata tuhatta pyyntöä kestää noin 230 ms. Ihmiset, jotka yrittävät käyttää sovellustamme, joutuvat odottamaan 230 ms enää nyt GC: n takia. Voit myös nähdä, että GC kutsutaan muutaman sekunnin välein, mikä tarkoittaa, että muutaman sekunnin välein käyttäjillä on ongelmia sovelluksemme käytössä. Ja viive kasvaa, kunnes sovellus kaatuu.
[28093] 7644 ms: Mark-sweep 10.9 (48.5) -> 10.9 (48.5) MB, 25.0 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. [28093] 7717 ms: Mark-sweep 10.9 (48.5) -> 10.9 (48.5) MB, 18.0 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. [28093] 7866 ms: Mark-sweep 11.0 (48.5) -> 10.9 (48.5) MB, 23.2 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. [28093] 8001 ms: Mark-sweep 11.0 (48.5) -> 10.9 (48.5) MB, 18.4 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. ... [28093] 633891 ms: Mark-sweep 235.7 (290.5) -> 235.7 (290.5) MB, 357.3 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. [28093] 635672 ms: Mark-sweep 235.7 (290.5) -> 235.7 (290.5) MB, 331.5 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. [28093] 637508 ms: Mark-sweep 235.7 (290.5) -> 235.7 (290.5) MB, 357.2 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested].
Nämä lokirivit tulostetaan, kun Node.js-sovellus käynnistetään –Jälki_gc lippu:
node --trace_gc app.js
Oletetaan, että olemme jo aloittaneet Node.js-sovelluksemme tällä lipulla. Ennen kuin yhdistämme sovelluksen solmu-tarkastajaan, meidän on lähetettävä sille SIGUSR1-signaali käynnissä olevaan prosessiin. Jos suoritat Node.js: n klusterissa, varmista, että muodostat yhteyden johonkin orjaprosesseihin.
kill -SIGUSR1 $pid # Replace $pid with the actual process ID
Tällä tavoin saamme Node.js-sovelluksen (tarkemmin V8) siirtymään virheenkorjaustilaan. Tässä tilassa sovellus avaa portin 5858 automaattisesti V8-virheenkorjausprotokolla .
Seuraava askel on suorittaa solmu-tarkastaja, joka muodostaa yhteyden käynnissä olevan sovelluksen virheenkorjausrajapintaan ja avaa toisen verkkoliitännän portissa 8080.
$ node-inspector Node Inspector v0.12.2 Visit http://127.0.0.1:8080/?ws=127.0.0.1:8080&port=5858 to start debugging.
Jos sovellus on käynnissä tuotannossa ja sinulla on palomuuri paikallaan, voimme tunnistaa etäportin 8080 paikallishostelle:
ssh -L 8080:localhost:8080 [email protected]
Nyt voit avata Chrome-selaimesi ja saada täyden pääsyn etätuotantosovellukseen liitettyihin Chrome-kehitystyökaluihin. Valitettavasti Chrome-kehittäjätyökalut eivät toimi muissa selaimissa.
Muistivuodot V8: ssa eivät ole todellisia muistivuotoja, koska tunnemme ne C / C ++ -sovelluksista. JavaScriptissä muuttujat eivät häviä tyhjyydessä, ne vain 'unohdetaan'. Tavoitteenamme on löytää nämä unohdetut muuttujat ja muistuttaa heitä siitä, että Dobby on ilmainen.
Chromen kehittäjätyökalujen sisällä meillä on pääsy useisiin profiloijiin. Olemme erityisen kiinnostuneita Kirjaa kasan määrärahat joka suorittaa ja ottaa useita kasan tilannekuvia ajan mittaan. Tämä antaa meille selkeän kurkistuksen, mistä esineistä vuotaa.
Aloita kasan allokointien tallentaminen ja simuloidaan 50 samanaikaista käyttäjää kotisivullamme Apache Benchmarkin avulla.
ab -c 50 -n 1000000 -k http://example.com/
Ennen uusien tilannekuvien ottamista V8 suorittaa merkinnän pyyhkäisyn roskakorin, joten tiedämme ehdottomasti, että tilannekuvassa ei ole vanhaa roskaa.
Kun olet kerännyt kasanjakoon liittyviä tilannekuvia 3 minuuttia päädymme jotain seuraavista:
Voimme selvästi nähdä, että joukossa on joitain jättimäisiä taulukoita, paljon IncomingMessage-, ReadableState-, ServerResponse- ja Domain-objekteja. Yritetään analysoida vuodon lähde.
Kun valitset kasan diff diff 20–40-luvulta, näemme vain objektit, jotka lisättiin 20 sekunnin kuluttua profilointilaitteen käynnistämisestä. Näin voit sulkea pois kaikki normaalit tiedot.
Pidä kirjaa siitä, kuinka monta kullekin tyypille kuuluvaa objektia järjestelmässä on, laajennamme suodatinta 20 sekunnista 1 minuuttiin. Voimme nähdä, että joukot, jotka ovat jo melko jättimäisiä, kasvavat jatkuvasti. Kohdassa “(matriisi)” voimme nähdä, että on paljon esineitä “(objektin ominaisuudet)” samalla etäisyydellä. Nämä esineet ovat muistivuotomme lähde.
Voimme myös nähdä, että myös '(sulkeminen)' -esineet kasvavat nopeasti.
Saattaa olla kätevää katsoa myös kieliä. Merkkijonoluettelon alla on paljon 'Hi Leaky Master' -lauseita. Ne saattavat antaa meille myös vihjeen.
Meidän tapauksessamme tiedämme, että merkkijono ”Hi Leaky Master” voidaan koota vain reitille ”GET /”.
Jos avaat säilytyspolun, näet, että tähän merkkijonoon viitataan jotenkin req , sitten on luotu konteksti ja kaikki tämä lisätään jättimäiseen joukkoihin sulkemisia.
Joten tässä vaiheessa tiedämme, että meillä on jonkinlainen jättimäinen joukko sulkemisia. Mennään ja annetaan nimi kaikille sulkemisillemme reaaliajassa lähteet-välilehdessä.
Kun olemme muokanneet koodia, voimme painaa CTRL + S tallentaa ja kääntää koodin lennossa!
Nauhoitetaan nyt toinen Kasa-allokaatioiden tilannekuva ja katso, mitkä sulkimet vievät muistia.
Se on selvää JotkutKindOfClojure () on meidän roisto. Nyt voimme nähdä sen JotkutKindOfClojure () joillekin nimetyille taulukoille lisätään sulkemisia tehtäviä globaalissa tilassa.
On helppo nähdä, että tämä taulukko on vain hyödytön. Voimme kommentoida sitä. Mutta miten vapautamme muistia jo käytössä olevasta muistista? Erittäin helppoa, osoitamme vain tyhjän taulukon tehtäviä ja seuraavalla pyynnöllä se ohitetaan ja muisti vapautuu seuraavan GC-tapahtuman jälkeen.
Dobby on ilmainen!
V8-kasa on jaettu useisiin eri tiloihin:
mmap
’muokattu alueCell
s, PropertyCell
s ja Map
s. Tätä käytetään yksinkertaistamaan roskien keräystä.Jokainen tila koostuu sivuista. Sivu on käyttöalueelle varattu muistialue mmap-toiminnolla. Jokainen sivu on aina 1 Mt: n kokoista, lukuun ottamatta suuressa objektitilassa olevia sivuja.
V8: lla on kaksi sisäänrakennettua jätteenkeräysmekanismia: Scavenge, Mark-Sweep ja Mark-Compact.
Scavenge on erittäin nopea jätteiden keräystekniikka, ja se toimii sisällä olevien esineiden kanssa Uusi tila . Scavenge on Cheneyn algoritmi . Idea on hyvin yksinkertainen, Uusi tila on jaettu kahteen yhtä suureen puoliväliin: avaruuteen ja avaruudesta. Scavenge GC tapahtuu, kun To-Space on täynnä. Se vain vaihtaa tiloihin ja tiloista ja kopioi kaikki elävät kohteet avaruuteen tai mainostaa ne yhteen vanhoista tiloista, jos ne selviytyivät kahdesta pyyhkäisystä, ja poistetaan sitten kokonaan avaruudesta. Sieppaukset ovat erittäin nopeita, mutta niillä on kaksinkertaisen kasan pitämisen ja esineiden jatkuvan kopioinnin muistissa. Syy käyttää puhdistimia johtuu siitä, että suurin osa esineistä kuolee nuorina.
Mark-Sweep & Mark-Compact on toinen V8: ssa käytetty roskakorin tyyppi. Toinen nimi on täysjätteen keräilijä. Se merkitsee kaikki elävät solmut, pyyhkii sitten kaikki kuolleet solmut ja eheyttää muistin.
Vaikka verkkosovellusten korkea suorituskyky ei ehkä ole niin suuri ongelma, sinun kannattaa silti välttää vuotoja hinnalla millä hyvänsä. Merkkivaiheen aikana koko GC: ssä sovellus on tosiasiassa keskeytetty, kunnes roskien keräys on valmis. Tämä tarkoittaa mitä enemmän esineitä sinulla on kasassa, sitä kauemmin GC: n suorittaminen kestää ja pidempään käyttäjien on odotettava.
On paljon helpompaa tarkastaa pinon jäljet ja kasat, kun kaikilla sulkeilla ja toiminnoilla on nimet.
db.query('GIVE THEM ALL', function GiveThemAllAName(error, data) { ... })
Ihannetapauksessa haluat välttää suuria esineitä kuumien toimintojen sisällä, jotta kaikki tiedot sopivat Uusi tila . Kaikki suorittimen ja muistiin sidotut toiminnot tulisi suorittaa taustalla. Vältä myös kuumien toimintojen optimoinnin laukaisijoita, optimoitu kuuma toiminto kuluttaa vähemmän muistia kuin optimoimattomat.
Nopeammin toimivat, mutta myös vähemmän muistia käyttävät kuumat toiminnot saavat GC: n toimimaan harvemmin. V8 tarjoaa hyödyllisiä virheenkorjaustyökaluja optimoimattomien toimintojen tai optimoimattomien toimintojen havaitsemiseksi.
Sisäisiä välimuisteja (IC) käytetään nopeuttamaan joidenkin koodipalojen suorittamista joko välimuistilla objektin ominaisuuskäyttö obj.key
tai jokin yksinkertainen toiminto.
function x(a, b) { return a + b; } x(1, 2); // monomorphic x(1, “string”); // polymorphic, level 2 x(3.14, 1); // polymorphic, level 3
Kun x (a, b) suoritetaan ensimmäistä kertaa, V8 luo monomorfisen IC: n. Kun soitat x
toisen kerran V8 poistaa vanhan IC: n ja luo uuden polymorfisen IC: n, joka tukee molempia operandityyppejä kokonaisluku ja merkkijono. Kun soitat IC: lle kolmannen kerran, V8 toistaa saman menettelyn ja luo toisen tason 3 polymorfisen IC: n.
On kuitenkin olemassa rajoitus. Kun IC-taso saavuttaa 5 (voidaan muuttaa –Max_inlining_levels lippu), funktio muuttuu megamorfiseksi eikä sitä enää pidetä optimoitavana.
On intuitiivisesti ymmärrettävää, että monomorfiset toiminnot toimivat nopeimmin ja niillä on myös pienempi muistinjälki.
Tämä on ilmeinen ja tunnettu. Jos sinulla on käsiteltäviä suuria tiedostoja, esimerkiksi suuri CSV-tiedosto, lue se rivi riviltä ja käsittele pieninä paloina sen sijaan, että lataat koko tiedoston muistiin. On melko harvinaisia tapauksia, joissa yksi CSV-rivi olisi suurempi kuin 1 Mt, jolloin voit sovittaa sen Uusi tila .
Jos sinulla on jokin kuuma sovellusliittymä, jonka käsittely kestää jonkin aikaa, kuten sovellusliittymä kuvien koon muuttamiseksi, siirrä se erilliseen säikeeseen tai tee siitä taustatyö. Suoritinintensiiviset toiminnot estäisivät pääketjut pakottaen kaikki muut asiakkaat odottamaan ja jatkamaan pyyntöjen lähettämistä. Käsittelemättömät pyyntötiedot kerääntyisivät muistiin, mikä pakottaisi koko GC: n viemään kauemmin aikaa.
Minulla oli kerran outo kokemus uudestaan. Jos lähetät muutama sata tuhatta pyyntöä virheelliseen URL-osoitteeseen, sovelluksen muisti kasvaa nopeasti jopa sadalle megatavulle, kunnes täysi GC käynnistyy muutamassa sekunnissa myöhemmin, jolloin kaikki palaisi normaaliksi. Osoittautuu, että restify luo jokaiselle virheelliselle URL-osoitteelle uuden virheobjektin, joka sisältää pitkiä pinonjälkiä. Tämä pakotti vasta luodut objektit kohdentamaan sisään Suuri objektitila pikemminkin kuin sisään Uusi tila .
Tällaisten tietojen saanti voi olla erittäin hyödyllistä kehityksen aikana, mutta sitä ei tietenkään tarvita tuotannossa. Siksi sääntö on yksinkertainen - älä luo tietoja, ellet todellakaan tarvitse sitä.
Viimeisenä, mutta ei vähäisimpänä, on tietää työkalusi. On olemassa useita virheenkorjauksia, vuotoja ja käyttökaavioiden generaattoreita. Kaikki nämä työkalut voivat auttaa sinua tekemään ohjelmistostasi nopeamman ja tehokkaamman.
Ymmärtäminen V8: n roskien keräyksestä ja koodin optimoinnista on avain sovellusten suorituskykyyn. V8 kääntää JavaScriptin alkuperäiseen kokoonpanoon, ja joissakin tapauksissa hyvin kirjoitettu koodi voisi saavuttaa suorituskyvyn, joka on verrattavissa GCC: n käännettyihin sovelluksiin.
Ja jos mietit, ApeeScape-asiakkaani uusi sovellusliittymäsovellus, vaikka parantamisen varaa onkin, toimii erittäin hyvin!
Joyent julkaisi äskettäin uuden version Node.js: stä, joka käyttää yhtä uusimmista V8-versioista. Jotkin Node.js v0.12.x: lle kirjoitetut sovellukset eivät välttämättä ole yhteensopivia uuden v4.x-version kanssa. Sovellusten suorituskyky ja muistin käyttö paranevat kuitenkin valtavasti Node.js: n uudessa versiossa.