Java >> Java tutorial >  >> Tag >> char

Supercharged jstack:Sådan fejlretter du dine servere ved 100 mph

En guide til brug af jstack til at fejlrette live Java-produktionsservere

jstack er ligesom U2 – det har været med os siden tidernes morgen, og vi kan ikke synes at slippe af med det. Bortset fra vittigheder er jstack langt et af de mest handy værktøjer i dit arsenal til at fejlsøge en live produktionsserver. Alligevel føler jeg stadig, at det er dybt underudnyttet i forhold til dets evne til at trække dig ud af ilden, når det går dårligt, så jeg ville gerne dele et par måder, hvorpå du kan overlade til et endnu stærkere våben i din krig mod produktionsfejl .

I sin kerne er jstack et super nemt værktøj til at vise dig stacksporene for alle Java-tråde, der kører inden for en mål-JVM. Bare peg på en JVM-proces via en pid og få en udskrift af alle trådstablesporene på det tidspunkt. Dette giver dig mulighed for at besvare det ældgamle spørgsmål om "hvad laver denne server?", og bringer dig et skridt nærmere for at forstå, hvorfor den rent faktisk gør det. Den største fordel ved jstack er, at den er let - den tilføjer ikke nogen ydelsesoverhead til JVM eller ændrer dens udførelsestilstand (i modsætning til en debugger eller profiler).

Da intet er perfekt, har jstack to væsentlige ulemper. Den første er, at jstack ikke giver dig nogen anden variabel tilstand end en opkaldsstak, hvilket betyder, at mens du måske kigger på en stak, vil du ikke have nogen idé om, hvad der er den tilstand, der fik den der. Et godt eksempel ville være at se på en JVM, der hænger, hvor jstack ville vise dig, at et stort antal tråde udfører DB-forespørgsler eller venter på at få en forbindelse.

Dette betyder sandsynligvis, at nogle forespørgsler tager for lang tid at udføre, hvilket får andre tråde til enten at vente på en forbindelse eller blive nægtet en. Dette er et sted, hvor du virkelig gerne vil vide, hvilken forespørgsel der udføres (eller hvad er dens parametre), der forårsager afmatningen, og hvornår den begyndte. Dette er naturligvis kun et eksempel ud af et væld af scenarier, hvor nogle tråde er blokeret og reducerer gennemstrømningen af ​​din applikation. Men desværre med jstack, da du ikke får nogen variabel tilstand - kan du ikke rigtig se, hvilken tråd der er skyld i. eller kan du?

Den anden ulempe ved jstack er, at det ikke er et værktøj, der altid er tændt. Det betyder, at du skal være der, når problemet opstår – hvilket i produktionen kan være en sjælden begivenhed. Dette er endnu mere sandt i elastiske miljøer, hvor VM'er konstant genstartes.

Her kommer den gode del - lad os tage et kig på to teknikker, der kan hjælpe os med at overvinde disse to mangler og gøre et godt værktøj virkelig fantastisk.

Oprettelse af stateful tråddata

Det første spørgsmål er, hvordan kan du tilføje tilstand til din jstack-udskrift? Svaret er enkelt og kraftfuldt – trådnavne. Mens mange fejlagtigt anser et trådnavn for at være en uforanderlig eller en OS-bestemt egenskab, er det faktisk en foranderlig og utrolig vigtig egenskab, som hver tråd har. Det er også den, der bliver samlet op i din jstack-stream, og deri ligger nøglen.

Den praktiske anvendelse er, at meget ligesom logning bør du kontrollere trådnavnet, når det indtaster din kode gennem et indgangspunkt såsom servlet, skuespiller eller planlægger. På det tidspunkt vil du gerne sætte dets navn til en meningsfuld værdi, der kan hjælpe dig med at forstå eksekveringskonteksten og relevante parametre, der kan hjælpe dig med at isolere transaktionen og dens indhold.

Dette vil højst sandsynligt omfatte -

  1. Formålet med tråden (f.eks. behandling af en besked, besvarelse af brugeranmodning osv..).
  2. Transaktions-id'et, som gør det muligt for dig at identificere denne specifikke datastrøm på tværs af forskellige maskiner og dele af applikationen.
  3. Parameterværdier som f.eks. servletparametre eller ID'et for en meddelelse, der sættes i kø.
  4. Den tid, hvor du fik kontrol over tråden. Dette sidste punkt er yderst vigtigt for dig at vide præcis, hvilke tråde i din kode der sidder fast, når du bruger jstack til at observere dem.
Thread.currentThread().setName(Context + TID + Params + current Time,..);

Disse data vil betyde forskellen mellem at se på en udskrift som den nedenfor, der faktisk ikke fortæller os noget om, hvad en tråd gør eller hvorfor, og en, der er informativ:

“pool-1-thread-1″ #17 prio=5 os_prio=31 tid=0x00007f9d620c9800 nid=0x6d03 i Object.wait() [0x000000013ebcc000]

Sammenlign dette med følgende trådudskrift:

”Købehandlingstråd, MessageID:AB5CAD, type:AnalyzeGraph, kø:ACTIVE_PROD, Transaction_ID:5678956, Starttid:10/8/2014 18:34″

#17 prio=5 os_prio=31 tid=0x00007f9d620c9800 nid=0x6d03 i Object.wait() [0x000000013ebcc000]

Det, du ser her, er en meget mere fyldestgørende forklaring på, hvad denne tråd faktisk gør. Du kan nemt se dens dekø-meddelelser fra en AWS-kø, hvilken meddelelse den analyserer, dens type, ID og transaktions-id. Og sidst, men langt fra mindst – hvornår begyndte tråden at arbejde på det. Dette kan hjælpe dig med at fokusere meget hurtigt på de tråde, der sidder fast, og se den tilstand, de er i. Derefter bliver optimering og reproduktion lokalt en meget lettere opgave.

Alternativet her ville være enten at håbe på, at der er data i logfilerne, og være i stand til at korrelere data i logfilerne til netop denne tråd. En anden mulighed ville være at vedhæfte en debugger i produktionen enten lokalt eller eksternt. Både ikke særlig behageligt og tidskrævende.

At skrive disse oplysninger i trådnavnet hjælper også med traditionel logning. Selvom de fleste logningsrammer giver trådbaseret kontekst, der kan tilføjes til loggen, skal du sørge for at konfigurere den korrekt. Brug af trådnavn kan også sikre, at du har alle de data, du har brug for, i loggen.

Bemærk:Nogle folk vil måske sige, at trådnavne ikke skal tempereres med eller ændres. Jeg er en meget lille tilhænger af dette, både fra min personlige erfaring med at gøre det i årevis og mange kollegers.

Gør jstack altid tændt

Den anden udfordring, vi står over for, når vi bruger jstack, er, at ligesom en debugger, er det et værktøj, som du manuelt skal betjene i det øjeblik, hvor problemet opstår, for at fange den korrupte tilstand. Der er dog en mere aktiv måde at bruge jstack til automatisk at generere udskrifter, når en server hænger eller falder under eller over en vis tærskel. Nøglen er at påkalde jstack programmatisk, ligesom du ville gøre med enhver logningsfunktion inde fra JVM, når specifikke applikationsbetingelser er opfyldt.
De to vigtigste udfordringer her er, hvornår og hvordan du gør det.

Hvordan aktiverer man jstack programmatisk?

Da jstack er en almindelig OS-proces, er det ret ligetil at påkalde den. Alt du skal gøre er at aktivere jstack-processen og pege på dig selv. Kickeren her er, hvordan du får pid til din proces inde fra JVM. Der er faktisk ingen standard Java API til at gøre det (i hvert fald ikke før Java 9). Her er et lille uddrag, der får jobbet gjort (omend ikke en del af et dokumenteret api):

String mxName = ManagementFactory.getRuntimeMXBean().getName();

int index = mxName.indexOf(PID_SEPERATOR);

String result;

if (index != -1) {
    result = mxName.substring(0, index);
} else {
    throw new IllegalStateException("Could not acquire pid using " + mxName);
}

En anden mindre udfordring er at dirigere jstack-output ind i din log. Det er også ret nemt at konfigurere ved hjælp af output stream gobblers. Se her for et eksempel på, hvordan du dirigerer outputdata, der udskrives af en proces, som du påberåber dig i din logfil eller outputstrøm.

Selvom det er muligt at fange stack-sporet af kørende tråde internt ved hjælp af getAllStackTraces, foretrækker jeg at gøre det ved at køre jstack af en række årsager. Den første er, at dette er noget, som jeg normalt ville ønske skal ske eksternt til den kørende applikation (selvom JVM deltager i at levere oplysningerne) for at sikre, at jeg ikke påvirker applikationens stabilitet ved at foretage introspektive opkald. En anden grund er, at jstack er mere kraftfuld med hensyn til dens muligheder, såsom at vise dig native frames og låsetilstand, noget der ikke er tilgængeligt fra JVM.

Hvornår aktiverer du jstack?

Den anden beslutning, du skal tage, er, under hvilke betingelser du vil have, at JVM'en skal logge en jstack. Dette ville sandsynligvis blive gjort efter en opvarmningsperiode, når serveren falder under eller over en specifik behandlingstærskel (dvs. anmodning eller meddelelsesbehandling). Du vil måske også sørge for, at du tager nok tid mellem hver aktivering; bare for at sikre, at du ikke oversvømmer dine træstammer under lav eller høj belastning.

Det mønster, du vil bruge her, er at indlæse en vagthund-tråd fra JVM'en, som med jævne mellemrum kan se på applikationens gennemløbstilstand (f.eks. antallet af meddelelser behandlet i de sidste to minutter) og beslutte, om et "skærmbillede" af trådtilstand ville være nyttig, i hvilket tilfælde den ville aktivere jstack og logge den til filen.

Indstil navnet på denne tråd til at indeholde målet og den faktiske gennemstrømningstilstand, så når du tager et automatisk jstack-øjebliksbillede, kan du se præcis, hvorfor vagthund-tråden besluttede at gøre det. Da dette kun ville ske med få minutters mellemrum, er der ingen reel ydeevne overhead til processen - især sammenlignet med kvaliteten af ​​de leverede data.

Nedenfor er et uddrag, der viser dette mønster i aktion. StartScheduleTask indlæser en overvågningstråd for periodisk at kontrollere en gennemløbsværdi, som øges ved hjælp af en Java 8 samtidig adder, når en meddelelse behandles.

public void startScheduleTask() {

    scheduler.scheduleAtFixedRate(new Runnable() {
        public void run() {

            checkThroughput();

        }
    }, APP_WARMUP, POLLING_CYCLE, TimeUnit.SECONDS);
}

private void checkThroughput()
{
    int throughput = adder.intValue(); //the adder in inc’d when a message is processed

    if (throughput < MIN_THROUGHPUT) {
        Thread.currentThread().setName("Throughput jstack thread: " + throughput);
        System.err.println("Minimal throughput failed: exexuting jstack");
        executeJstack(); //see the code on github to see how this is done
    }

    adder.reset();
}
  • Den fulde kildekode til forebyggende påkaldelse af jstack fra din kode kan findes her.

Java tag