Voitteko vilkaista järjestelmäämme? Ohjelman kirjoittanutta kaveria ei ole enää lähellä, ja meillä on ollut useita ongelmia. Tarvitsemme jonkun tarkastelemaan sitä ja puhdistamaan sen meille.
Jokainen, joka on ollut sisällä ohjelmistotuotanto kohtuullisen ajan tietää, että tämä näennäisesti viaton pyyntö on usein alku hankkeelle, jonka 'on kirjoittanut katastrofi kaikkialle'. Joku toisen koodin periminen voi olla painajainen, varsinkin kun koodi on huonosti suunniteltu ja puuttuu dokumentaatio.
Joten kun sain äskettäin yhden asiakkaamme pyynnön tarkastella hänen olemassa olevaa asiakasta socket.io chat - palvelinsovellus (kirjoitettu Node.js ) ja parantamaan sitä, olin erittäin varovainen. Mutta ennen juoksemista kukkuloille päätin ainakin suostua katsomaan koodia.
Valitettavasti koodin tarkastelu vain vahvisti huoleni. Tämä chat-palvelin oli toteutettu yhtenä suurena JavaScript-tiedostona. Tämän yhden monoliittisen tiedoston uudelleen suunnittelu puhtaasti suunnitelluksi ja helposti ylläpidettäväksi ohjelmistoksi olisi todellakin haaste. Mutta nautin haasteesta, joten suostuin.
Nykyinen ohjelmisto koostui yhdestä tiedostosta, joka sisälsi 1200 riviä dokumentoimattomia koodeja. Yikes. Lisäksi sen tiedettiin sisältävän joitain vikoja ja suorituskykyongelmia.
Lisäksi lokitiedostojen (aina hyvä paikka aloittaa, kun perit jonkun toisen koodin) tutkiminen paljasti mahdollisia muistivuotoja. Jossakin vaiheessa prosessin ilmoitettiin käyttävän yli 1 Gt RAM-muistia.
Kun otetaan huomioon nämä ongelmat, kävi heti selväksi, että koodi olisi järjestettävä uudelleen ja moduloitava ennen kuin yrität edes virheenkorjausta tai parantaa liiketoimintalogiikkaa. Tätä tarkoitusta varten joihinkin alkuperäisiin ongelmiin, jotka oli ratkaistava, kuului:
Aloittaessani koodin uudelleenjärjestelyä, halusin käsitellä yllä yksilöityjä erityiskysymyksiä, lisäksi käsitellä joitain keskeisiä arkkitehtonisia tavoitteita, jotka ovat (tai ainakin pitäisi olla) yhteisiä minkä tahansa ohjelmistojärjestelmän suunnittelulle . Nämä sisältävät:
Tavoitteenamme on siirtyä yhdestä monoliittisesta mongolaisesta lähdekooditiedostosta modularisoituun sarjaan puhtaasti suunniteltuja komponentteja. Tuloksena olevan koodin on oltava huomattavasti helpompi ylläpitää, parantaa ja virheenkorjausta.
Tätä sovellusta varten olen päättänyt järjestää koodin seuraaviin erillisiin arkkitehtonisiin komponentteihin:
logs
kansion, joka sisältää kaikki lokitiedostot)Tässä erityisessä lähestymistavassa ei ole mitään taikaa; koodin uudelleenjärjestelyyn voisi olla monia eri tapoja. Minusta tuntui vain henkilökohtaisesti, että tämä organisaatio oli riittävän puhdas ja hyvin järjestetty olematta liian monimutkainen.
Tuloksena oleva hakemisto ja tiedostojärjestely on esitetty alla.
Kirjauspaketit on kehitetty suurimmalle osalle nykypäivän kehitysympäristöjä ja kieliä, joten nykyään on harvinaista, että joudut käyttämään 'omaa' kirjauskapasiteettiasi.
Koska työskentelemme Node.js: n kanssa, valitsin log4js-solmu , joka on pohjimmiltaan versio log4js kirjasto käytettäväksi Node.js: n kanssa. Tässä kirjastossa on joitain hienoja ominaisuuksia, kuten kyky kirjata useita tasoja viestejä (VAROITUS, VIRHE jne.), Ja meillä voi olla liikkuva tiedosto, joka voidaan jakaa esimerkiksi päivittäin, joten meidän ei tarvitse käsitellä valtavia tiedostoja, joiden avaaminen vie paljon aikaa ja joita on vaikea analysoida ja jäsentää.
Tarkoituksia varten olen luonut pienen kääreen log4js-solmun ympärille lisätäksesi tiettyjä haluttuja lisäominaisuuksia. Huomaa, että olen päättänyt luoda kääreen log4js-solmun ympärille, jota käytän sitten koko koodissani. Tämä lokalisoi näiden laajennettujen kirjausominaisuuksien toteuttamisen yhdessä paikassa välttäen turhuutta ja tarpeetonta monimutkaisuutta koko koodissani, kun käytän kirjaamista.
Koska työskentelemme I / O: n kanssa ja meillä olisi useita asiakkaita (käyttäjiä), jotka synnyttävät useita yhteyksiä (pistorasioita), haluan pystyä jäljittämään tietyn käyttäjän toiminnan lokitiedostoista ja haluan myös tietää kunkin lokimerkinnän lähde. Siksi odotan, että sovelluksen tilaan liittyy joitain lokimerkintöjä, ja joitain, jotka liittyvät nimenomaan käyttäjien toimintaan.
Kirjaamalla käärekoodissani pystyn kartoittamaan käyttäjätunnuksen ja pistorasiat, joiden avulla voin seurata toimintoja, jotka tehtiin ennen ERROR-tapahtumaa ja sen jälkeen. Kirjauskääre antaa minulle myös mahdollisuuden luoda erilaisia kirjaajia, joilla on erilaiset asiayhteyteen liittyvät tiedot, jotka voin välittää tapahtumankäsittelijöille, jotta tiedän lokimerkinnän lähteen.
Kirjauskääreen koodi on käytettävissä tässä .
Usein on tarpeen tukea järjestelmän eri kokoonpanoja. Nämä erot voivat olla joko eroja kehitys- ja tuotantoympäristöjen välillä tai jopa perustua tarpeeseen näyttää erilaisia asiakasympäristöjä ja käyttöskenaarioita.
Sen sijaan, että vaaditaan koodin muutoksia tämän tueksi, yleinen käytäntö on hallita näitä käyttäytymiseroja konfigurointiparametrien avulla. Minun tapauksessani tarvitsin mahdollisuuden käyttää erilaisia toteutusympäristöjä (lavastus ja tuotanto), joilla voi olla erilaiset asetukset. Halusin myös varmistaa, että testattu koodi toimi hyvin sekä lavastuksessa että tuotannossa, ja jos minun olisi pitänyt vaihtaa koodi tätä tarkoitusta varten, se olisi mitätöinyt testausprosessin.
Käyttämällä Node.js-ympäristömuuttujaa voin määrittää, mitä kokoonpanotiedostoa haluan käyttää tiettyyn suoritukseen. Siksi siirsin kaikki aiemmin kovakoodatut määritysparametrit kokoonpanotiedostoihin ja loin yksinkertaisen määritysmoduulin, joka lataa oikean määritystiedoston haluttuilla asetuksilla. Ryhmittelin myös kaikki asetukset pakottaakseni jonkinasteisen organisaation kokoonpanotiedostoon ja helpottaakseni navigointia.
Tässä on esimerkki tuloksena olevasta määritystiedostosta:
{ 'app': { 'port': 8889, 'invRepeatInterval':1000, 'invTimeOut':300000, 'chatLogInterval':60000, 'updateUsersInterval':600000, 'dbgCurrentStatusInterval':3600000, 'roomDelimiter':'_', 'roomPrefix':'/' }, 'webSite':{ 'host': 'mysite.com', 'port': 80, 'friendListHandler':'/MyMethods.aspx/FriendsList', 'userCanChatHandler':'/MyMethods.aspx/UserCanChat', 'chatLogsHandler':'/MyMethods.aspx/SaveLogs' }, 'logging': { 'appenders': [ { 'type': 'dateFile', 'filename': 'logs/chat-server', 'pattern': '-yyyy-MM-dd', 'alwaysIncludePattern': false } ], 'level': 'DEBUG' } }
Toistaiseksi olemme luoneet kansiorakenteen eri moduulien isännöimiseksi, olemme luoneet tavan ladata ympäristökohtaisia tietoja ja luoneet kirjausjärjestelmän, joten katsotaanpa, kuinka voimme sitoa kaikki kappaleet muuttamatta yrityskohtaista koodia.
Uuden modulaarisen koodirakenteen ansiosta pääsykohta app.js
on tarpeeksi yksinkertainen ja sisältää vain alustuskoodin:
var config = require('./config'); var logging = require('./logging'); var ioW = require('./ioW'); var obj = config.getCurrent(); logging.initialize(obj.logging); ioW.initialize(config);
Kun määritimme koodirakenteen, sanoimme, että ioW
kansioon mahtuu liiketoimintaan ja socket.io liittyvä koodi. Erityisesti se sisältää seuraavat tiedostot (huomaa, että voit napsauttaa mitä tahansa luetellun tiedostonimeä nähdäksesi vastaavan lähdekoodin):
index.js
- hoitaa socket.io -alustan ja -yhteydet sekä tapahtumien tilauksen sekä tapahtumien keskitetyn virhekäsittelijäneventManager.js
- isännöi kaiken liiketoimintaan liittyvän logiikan (tapahtumankäsittelijät)webHelper.js
- auttajamenetelmät verkkopyyntöjen tekemiseen.linkedList.js
- linkitetty luettelo hyödyllisyysluokkaTeimme verkkopyynnön tekevän koodin uudelleen ja siirrimme sen erilliseen tiedostoon, ja onnistuimme pitämään liiketoimintalogiikkamme samassa paikassa muuttumattomana.
Yksi tärkeä huomautus: Tässä vaiheessa eventManager.js
sisältää edelleen joitain auttajatoimintoja, jotka todella pitäisi purkaa erilliseen moduuliin. Koska tavoitteenamme oli kuitenkin tällä ensimmäisellä kierroksella järjestää koodi uudelleen samalla, kun minimoidaan vaikutukset liiketoimintalogiikkaan, ja nämä aputoiminnot ovat liian monimutkaisesti sidottuja liiketoimintalogiikkaan, päätimme lykätä tämän myöhempään siirtoon organisaation parantamiseksi. koodi.
Koska Node.js on määritelmänsä mukaan asynkroninen, kohtaamme usein vähän rotan pesän 'soittopyynnön', mikä tekee koodista erityisen vaikean navigoida ja virheenkorjaus. Tämän kuopan välttämiseksi olen käyttänyt uudessa toteutuksessani lupaa mallia ja ne hyödyntävät erityisesti sinikka joka on erittäin mukava ja nopea lupaus kirjasto. Lupaukset antavat meille mahdollisuuden seurata koodia ikään kuin se olisi synkronoitu, ja ne tarjoavat myös virheiden hallinnan ja puhtaan tavan standardoida vastaukset puheluiden välillä. Koodissamme on implisiittinen sopimus, jonka mukaan jokaisen tapahtumankäsittelijän on palautettava lupaus, jotta voimme hallita keskitettyä virheiden käsittelyä ja kirjaamista.
Kaikki tapahtumankäsittelijät palauttavat lupauksen (soittavatko he asynkronisia puheluja vai eivät). Kun tämä on paikallaan, voimme keskittää virheenkäsittelyn ja lokien kirjaamisen ja varmistamme, että jos tapahtumakäsittelijässä on käsittelemätön virhe, virhe havaitaan.
function execEventHandler(socket, eventName, eventHandler, data){ var sLogger = logging.createLogger(socket.id + ' - ' + eventName); sLogger.info(''); eventHandler(socket, data, sLogger).then(null, function(err){ sLogger.error(err.stack); }); };
Lokitietokeskustelussamme mainitsimme, että jokaisella yhteydellä olisi oma logger, jossa on asiayhteyteen liittyviä tietoja. Erityisesti sidomme socket id: n ja tapahtuman nimen loggeriin, kun luomme sen, joten kun välitämme loggerin tapahtumankäsittelijälle, jokaisella lokirivillä on kyseiset tiedot:
var sLogger = logging.createLogger(socket.id + ' - ' + eventName);
Yksi toinen mainitsemisen arvoinen seikka tapahtumien käsittelystä: Alkuperäisessä tiedostossa meillä oli setInterval
toimintokutsu, joka oli socket.io-yhteyden tapahtuman tapahtumakäsittelijän sisällä, ja olemme tunnistaneet tämän toiminnon ongelmaksi.
io.on('connection', function (socket) { ... Several event handlers .... setInterval(function() { try { var date = Date.now(); var tmp = []; while (0 Tämä koodi luo ajastinta tietyllä aikavälillä (meidän tapauksessamme se oli 1 minuutti) jokainen yhteyspyyntö että saamme. Joten jos meillä esimerkiksi on milloin tahansa 300 online-pistorasiaa, meillä olisi 300 ajastinta suorittamaan joka minuutti. Kuten yllä olevasta koodista voi nähdä, ongelmana on, että pistoketta ei käytetä eikä mitään muuttujaa, joka on määritelty tapahtumakäsittelijän piirissä. Ainoa muuttuja, jota käytetään, on messageHub
muuttuja, joka ilmoitetaan moduulitasolla, mikä tarkoittaa, että se on sama kaikille yhteyksille. Siksi ei ole mitään erillistä ajastinta liitäntää kohti. Joten olemme poistaneet tämän yhteystapahtumakäsittelijästä ja sisällyttäneet sen yleiseen alustuskoodiin, joka tässä tapauksessa on initialize
toiminto.
Lopuksi, käsittelyssä vastauksistamme webHelper.js
-sovelluksessa lisättiin käsittely kaikille tunnistamattomille vastauksille, jotka kirjaavat tietoja, jotka ovat hyödyllisiä virheenkorjausprosessissa:
if (!res || !res.d || !res.d.IsValid){ logger.debug(sendData); logger.debug(data); reject(new Error('Request failed. Path ' + params.path + ' . Invalid return data.')); return; }
Viimeinen vaihe on luoda lokitiedosto Node.js-standardivirheelle. Tämä tiedosto sisältää käsittelemättömiä virheitä, jotka olemme saattaneet unohtaa. Asetamme solmuprosessin Windowsissa (ei ihanteellinen, mutta tiedät…) palveluna, käytämme työkalua nimeltä nssm jolla on visuaalinen käyttöliittymä, jonka avulla voit määrittää vakiotulostustiedoston, tavallisen virhetiedoston ja ympäristömuuttujat.
Tietoja Node.js Performance -sovelluksesta
Node.js on yksisäikeinen ohjelmointikieli. Skaalautuvuuden parantamiseksi voimme käyttää useita vaihtoehtoja. Siellä on solmuklusterimoduuli tai yksinkertaisesti vain lisää solmuprosesseja ja laita nginx niiden päälle suorittamaan edelleenlähetys ja kuormituksen tasapainotus.
Meidän tapauksessamme, vaikka jokaisella solmuryhmän aliprosessilla tai solmuprosessilla on oma muistitila, emme voi jakaa tietoja näiden prosessien välillä helposti. Joten tässä tapauksessa meidän on käytettävä ulkoista tietovarastoa (kuten toistaa ) pitääkseen online-pistorasiat käytettävissä eri prosesseille.
Johtopäätös
Kun kaikki tämä on paikallaan, olemme saavuttaneet merkittävän puhdistuksen alun perin meille luovutetusta koodista. Kyse ei ole koodin täydellisestä tekemisestä, vaan pikemminkin sen uudelleensuunnittelusta, jotta luodaan puhdas arkkitehtoninen perusta, jota on helpompi tukea ja ylläpitää ja joka helpottaa ja yksinkertaistaa virheenkorjausta.
Noudattamalla aiemmin lueteltuja keskeisiä ohjelmistosuunnitteluperiaatteita - ylläpidettävyys, laajennettavuus, modulaarisuus ja skaalautuvuus - loimme moduulit ja koodirakenteen, jotka yksilöivät selkeästi ja siististi moduulien erilaiset vastuut. Olemme myös tunnistaneet joitain ongelmia alkuperäisessä toteutuksessa, jotka johtavat korkeaan muistin kulutukseen, joka heikensi suorituskykyä.
Toivottavasti pidit artikkelista. Kerro minulle, jos sinulla on vielä kommentteja tai kysymyksiä.