Tarkvaraarendus võib olla väga keeruline protsess. Meie kui arendajad peame arvestama paljude erinevate muutujatega. Mõni pole meie kontrolli all, mõni on meile tegeliku koodi käivitamise hetkel tundmatu ja osa on meie otsese kontrolli all. Ja .NET arendajad pole sellest erand.
Arvestades seda reaalsust, lähevad asjad kontrollitud keskkondades töötades tavaliselt plaanipäraselt. Näiteks on meie arendusmasin või integreerimiskeskkond, millele meil on täielik juurdepääs. Nendes olukordades on meie käsutuses tööriistad erinevate muutujate analüüsimiseks, mis mõjutavad meie koodi ja tarkvara. Nendel juhtudel ei pea me tegelema ka serveri raskete koormustega ega samaaegsete kasutajatega, kes üritavad sama asja korraga teha.
Kirjeldatud ja ohututes olukordades töötab meie kood hästi, kuid suure koormuse või mõne muu välise teguri all tootmisel võivad tekkida ootamatud probleemid. Tarkvara jõudlust tootmises on raske analüüsida. Enamasti peame võimalike probleemidega tegelema teoreetilises stsenaariumis: me teame, et probleem võib juhtuda, kuid me ei saa seda testida. Sellepärast peame oma arendamisel lähtuma kasutatava keele parimatest tavadest ja dokumentatsioonist ning seda vältima levinud vead .
Nagu mainitud, võivad tarkvara käivitamisel asjad valesti minna ja koodi võib hakata käivitama viisil, mida me ei plaaninud. Me võime sattuda olukorda, kus peame probleemidega toime tulema, ilma et oleks võimalik siluda või kindlalt teada, mis toimub. Mida saame sel juhul teha?
kuidas teha kasutatavuse testimistKui protsess kasutab pikka aega üle 90% protsessorist, oleme hädas Piiksuma
Selles artiklis analüüsime a. Kõrge protsessori kasutamise tegelikku stsenaariumi .NET veebirakendus Windowsi põhises serveris probleemide tuvastamiseks vajalikud protsessid ja veelgi olulisem, miks see probleem üldse juhtus ja kuidas me selle lahendame.
Protsessori kasutamine ja mälu tarbimine on laialdaselt arutatud teemad. Tavaliselt on väga raske kindlalt teada saada, milline on õige ressursside hulk (protsessor, RAM, sisend / väljund), mida konkreetne protsess peaks kasutama ja mis aja jooksul. Kuigi üks on kindel - kui protsess kasutab pikema aja vältel üle 90% protsessorist, oleme hädas just seetõttu, et server ei saa antud olukorras töödelda ühtegi muud taotlust.
Kas see tähendab, et protsessis endas on probleem? Mitte tingimata. Võib juhtuda, et protsess vajab suuremat töötlemisvõimsust või töötleb palju andmeid. Alustuseks on ainus asi, mida saame teha, proovida tuvastada, miks see juhtub.
Kõigis opsüsteemides on mitu erinevat tööriista serveris toimuva jälgimiseks. Windowsi serveritel on spetsiaalselt ülesannete haldur, Performance Monitor või meie puhul kasutasime Uued reliikviaserverid mis on suurepärane vahend serverite jälgimiseks.
Pärast oma rakenduse juurutamist hakkasime esimese kahe nädala jooksul nägema, et serveril on protsessori kasutuse tipud, mis muutis serveri reageerimata. Selle uuesti kättesaadavaks tegemiseks pidime selle taaskäivitama ja see sündmus juhtus selle aja jooksul kolm korda. Nagu ma varem mainisin, kasutasime serverimonitorina uusi reliikviaservereid ja see näitas, et w3wp.exe
protsess kasutas serveri krahhi ajal 94% protsessorist.
Interneti-teabeteenuste (IIS) tööprotsess on Windowsi protsess (w3wp.exe
), mis käitab veebirakendusi ja vastutab konkreetse rakenduste kogumi veebiserverisse saadetud taotluste töötlemise eest. IIS-serveril võib olla mitu rakenduste kogumit (ja mitu erinevat w3wp.exe
protsessi), mis võivad probleemi tekitada. Tuginedes protsessil olnud kasutajale (seda näidati New Relicu aruannetes), tegime kindlaks, et probleem oli meie .NET C # veebivormi pärandrakendus.
.NET Framework on tihedalt integreeritud Windowsi silumisvahenditega, nii et esimese asjana proovisime sündmuste vaataja ja rakenduste logifaile vaadata, et leida toimuva kohta kasulikku teavet. Kas meil oli sündmuste vaataja sisse logitud mõni erand, ei andnud nad analüüsimiseks piisavalt andmeid. Sellepärast otsustasime astuda sammu edasi ja koguda rohkem andmeid, nii et kui sündmus uuesti esile kerkib, oleksime valmis.
Lihtsaim viis kasutajarežiimi protsesside väljavõtete kogumiseks on Silumisdiagnostika tööriistad v2.0 või lihtsalt DebugDiag. DebugDiagil on tööriistakomplekt andmete kogumiseks (DebugDiag Collection) ja andmete analüüsimiseks (DebugDiag Analysis).
Alustame siis silumisdiagnostika tööriistadega andmete kogumise reeglite määratlemist:
Avage DebugDiagi kogu ja valige Performance
.
Performance Counters
ja klõpsake nuppu Next
.Add Perf Triggers
.Processor
(mitte Process
) objekti ja valige % Processor Time
. Pange tähele, et kui kasutate Windows Server 2008 R2 ja teil on rohkem kui 64 protsessorit, valige palun Processor Information
objekti Processor
asemel objekt._Total
.Add
ja seejärel klõpsake nuppu OK
Valige äsja lisatud päästik ja klõpsake nuppu Edit Thresholds
Above
rippmenüüs.80
.Sisestage 20
sekundite arvu jaoks. Vajadusel saate seda väärtust reguleerida, kuid valede päästikute vältimiseks olge ettevaatlik ja ärge määrake väikest arvu sekundeid.
OK
.Next
.Add Dump Target
.Web Application Pool
rippmenüüst.OK
.Next
.Next
uuesti.Next
.Activate the Rule Now
ja klõpsake nuppu Finish
.Kirjeldatud reegel loob minidump-failide komplekti, mis on üsna väike. Viimane prügimägi on täieliku mäluga prügimägi ja see on palju suurem. Nüüd peame ainult ootama, kuni kõrge protsessori sündmus kordub.
Kui meil on valitud kaustas prügifailid, kasutame kogutud andmete analüüsimiseks tööriista DebugDiag Analysis:
Valige jõudlusanalüsaatorid.
Lisage prügifailid.
Alusta analüüsi.
DebugDiag võtab prügimägede parsimiseks ja analüüsi esitamiseks paar (või mitu) minutit. Kui analüüs on lõpule jõudnud, näete veebilehte, milles on kokkuvõte ja palju teavet lõimede kohta, mis sarnaneb järgmisega:
Nagu kokkuvõttes näete, on hoiatus, mis ütleb: 'Ühes või mitmes lõimes tuvastati prügifailide vahel suur protsessori kasutamine.' Kui klõpsame soovitusel, hakkame mõistma, kus probleem on meie rakendusega. Meie näidisaruanne näeb välja selline:
Nagu aruandest näeme, on protsessori kasutamisel muster. Kõik niidid, millel on suur protsessorikasutus, on seotud sama klassiga. Enne koodi juurde hüppamist heidame pilgu esimesele.
See on meie probleemiga seotud esimese lõime detail. Meie jaoks huvitav osa on järgmine:
Siin on meil kõne meie koodile GameHub.OnDisconnected()
mis käivitas probleemse toimingu, kuid enne seda kõnet on meil kaks sõnastiku kõnet, mis võivad anda aimu toimuvast. Heitkem pilk .NET-koodile, et näha, mida see meetod teeb:
public override Task OnDisconnected() { try { var userId = GetUserId(); string connId; if (onlineSessions.TryGetValue(userId, out connId)) onlineSessions.Remove(userId); } catch (Exception) { // ignored } return base.OnDisconnected(); }
Ilmselt on meil siin probleem. Aruannete kõnekogus öeldi, et probleem oli sõnastikus ja selles koodis pääseme juurde sõnaraamatule, täpsemalt on see probleem põhjustav rida järgmine:
tutvumisrakendused kasutajate arvu järgi
if (onlineSessions.TryGetValue(userId, out connId))
See on sõnastiku deklaratsioon:
static Dictionary onlineSessions = new Dictionary();
Kõik, kellel on objektorienteeritud programmeerimiskogemus, teavad staatilisi muutujaid, mida jagavad kõik selle klassi eksemplarid. Vaatame põhjalikumalt, mida staatiline tähendab .NET-maailmas.
Vastavalt .NET C # spetsifikatsioonile:
Kasuta staatiline modifikaator staatilise liikme deklareerimiseks, mis kuulub pigem tüübi enda kui konkreetse objekti juurde.
Seda räägivad .NET C # langunge spetsifikatsioonid staatilised klassid ja liikmed :
Nagu kõigi klassitüüpide puhul, laadib staatilise klassi tüübiinfo .NET Framework ühise keele käitamise aeg (CLR), kui klassile viitav programm laaditakse. Programm ei saa täpselt täpsustada, millal klass laaditakse. Kuid selle laadimine ja väljade lähtestamine ning staatilise konstruktori kutsumine on tagatud enne, kui klassile teie programmis esimest korda viidatakse. Staatilist konstruktorit nimetatakse ainult üks kord ja staatiline klass jääb mällu kogu selle rakenduse domeeni eluea jooksul, kus teie programm asub.
Mittestaatiline klass võib sisaldada staatilisi meetodeid, välju, atribuute või sündmusi. Staatiline liige saab klassi kutsuda ka siis, kui klassi eksemplari pole loodud. Staatilisele liikmele pääseb alati juurde klassi nimi, mitte eksemplari nimi. Staatilisest liikmest eksisteerib ainult üks eksemplar, olenemata sellest, kui palju klassi eksemplare on loodud. Staatilised meetodid ja atribuudid ei pääse juurde mittestaatilistele väljadele ja sündmustele nende sisaldavas tüübis ning nad ei pääse juurde ühegi objekti eksemplari muutujale, kui see pole meetodi parameetris sõnaselgelt edastatud.
See tähendab, et staatilised liikmed kuuluvad tüübile endale, mitte objektile. Samuti laadib need CLR rakenduse domeeni, seetõttu kuuluvad staatilised liikmed protsessi, mis majutab rakendust, mitte konkreetseid lõime.
Arvestades tõsiasja, et veebikeskkond on mitmekihiline keskkond, sest iga taotlus on uus lõim, mille w3wp.exe
protsess; ja arvestades, et staatilised liikmed on osa protsessist, võib meil olla stsenaarium, kus mitu erinevat lõime üritavad pääseda juurde staatiliste (mitme lõime poolt jagatud) muutujate andmetele, mis võib lõpuks viia mitme lõime probleemideni.
Sõnaraamat dokumentatsioon keerme ohutuse all on järgmine:
A
Dictionary
saab korraga toetada mitut lugejat, kui kogu pole muudetud. Isegi nii, et kogu kaudu loendamine pole sisuliselt niiditurvaline protseduur. Harvadel juhtudel, kui loend konkureerib kirjutusjuurdepääsudega, tuleb kogu loendi vältel kogu lukustada. Kui soovite kollektsiooni lugemiseks ja kirjutamiseks juurde pääseda mitme lõime kaudu, peate rakendama oma sünkroonimise.
See avaldus selgitab, miks meil võib see probleem tekkida. Prügimägede teabe põhjal oli probleem sõnastiku FindEntry meetodiga:
kuidas kasutada erisboti ebakõlasid
Kui vaatame sõnaraamatut FindEntry rakendamine näeme, et meetod kordab väärtuse leidmiseks läbi sisemise struktuuri (ämbrid).
Nii et järgmine .NET-kood loetleb kogu, mis pole niiditurvaline toiming.
public override Task OnDisconnected() { try { var userId = GetUserId(); string connId; if (onlineSessions.TryGetValue(userId, out connId)) onlineSessions.Remove(userId); } catch (Exception) { // ignored } return base.OnDisconnected(); }
Nagu nägime prügimägedes, on ühiselt ressurssi (staatiline sõnastik) kordamiseks ja muutmiseks korraga mitu lõime, mis lõpuks põhjustasid iteratsiooni sisenemise lõpmatusse tsüklisse, põhjustades lõime tarbimiseks üle 90% protsessorist .
Sellele probleemile on mitu võimalikku lahendust. Esimesena rakendasime sõnastikule juurdepääsu lukustamise ja sünkroonimise jõudluse kaotamise hinnaga. Server kukkus sel ajal iga päev kokku, nii et pidime selle võimalikult kiiresti parandama. Isegi kui see polnud optimaalne lahendus, lahendas see probleemi.
Järgmine samm selle probleemi lahendamisel oleks koodi analüüsimine ja sellele optimaalse lahenduse leidmine. Koodi ümbertegemine on võimalus: uus Samaaegne sõnastik klass võiks selle probleemi lahendada, kuna see lukustub ainult ämber tasemel, mis parandab üldist jõudlust. Kuigi see on suur samm ja vaja oleks täiendavat analüüsi.