Vi använder mikrotjänstarkitektur i våra projekt. När prestandaflaskhalsar uppstår ägnas mycket tid åt att övervaka och analysera loggar. När man loggar tidpunkterna för enskilda operationer till en loggfil är det vanligtvis svårt att förstå vad som ledde till anropet av dessa operationer, att spåra sekvensen av åtgärder eller tidsförskjutningen av en operation i förhållande till en annan i olika tjänster.
För att minimera manuellt arbete bestämde vi oss för att använda ett av spårningsverktygen. Om hur och varför du kan använda spårning och hur vi gjorde det, och kommer att diskuteras i den här artikeln.
Vilka problem kan lösas med spårning
Hitta prestandaflaskhalsar både inom en enskild tjänst och i hela exekveringsträdet mellan alla deltagande tjänster. Till exempel:
Många korta på varandra följande samtal mellan tjänster, till exempel till geokodning eller till en databas.
Långa I/O-väntningar, som nätverksöverföringar eller diskläsning.
Lång dataanalys.
Långa operationer som kräver cpu.
Kodavsnitt som inte behövs för att få det slutliga resultatet och som kan tas bort eller försenas.
Förstå tydligt i vilken ordning vad som kallas och vad som händer när operationen utförs.
Det kan ses att till exempel Request kom till WS-tjänsten -> WS-tjänsten lade till data genom R-tjänsten -> skickade sedan en förfrågan till V-tjänsten -> V-tjänsten laddade mycket data från R-tjänsten tjänst -> gick till P-tjänsten -> P-tjänsten gick igen till tjänst R -> tjänst V ignorerade resultatet och gick till tjänst J -> och returnerade först då svaret till tjänst WS, samtidigt som man fortsatte att beräkna något annat i bakgrund.
Utan ett sådant spår eller detaljerad dokumentation för hela processen är det väldigt svårt att förstå vad som händer när man tittar på koden för första gången, och koden är spridd över olika tjänster och gömd bakom ett gäng bins och gränssnitt.
Insamling av information om exekveringsträdet för efterföljande uppskjuten analys. I varje skede av exekveringen kan du lägga till information till spåret som är tillgängligt i detta skede och sedan ta reda på vilken indata som ledde till ett liknande scenario. Till exempel:
användar ID
Rights
Typ av vald metod
Logg- eller exekveringsfel
Omvandla spår till en delmängd av mått och ytterligare analys redan i form av mått.
Vilket spår kan logga. Spänna
I spårning finns konceptet med ett span, detta är en analog av en stock, till konsolen. Spaet har:
Namn, vanligtvis namnet på metoden som kördes
Namnet på tjänsten där spann genererades
Eget unikt ID
Någon sorts metainformation i form av en nyckel/värde som har loggats in i den. Till exempel, metodparametrar eller metoden slutade med ett fel eller inte
Start- och sluttider för detta intervall
ID för förälderspann
Varje span skickas till span-samlaren för att lagras i databasen för senare granskning så snart den har slutfört sin exekvering. I framtiden kan du bygga ett träd av alla spann genom att ansluta med föräldra-id. När man analyserar kan man till exempel hitta alla spann i någon tjänst som tagit mer än lite tid. Vidare, genom att gå till ett specifikt intervall, se hela trädet ovanför och under detta intervall.
Opentrace, Jagger och hur vi implementerade det för våra projekt
Det finns en gemensam standard opentrace, som beskriver hur och vad som ska samlas in, utan att vara knuten till en specifik implementering på något språk. Till exempel i Java utförs allt arbete med spår genom det gemensamma Opentrace API, och under det kan till exempel Jaeger eller en tom standardimplementation som inte gör något döljas.
Vi använder Jaeger som en implementering av Opentrace. Den består av flera komponenter:
Jaeger-agent är en lokal agent som vanligtvis är installerad på varje maskin och tjänster loggas in på den på den lokala standardporten. Om det inte finns någon agent, är spår av alla tjänster på den här maskinen vanligtvis inaktiverade
Jaeger-collector - alla agenter skickar insamlade spår till den, och den placerar dem i den valda databasen
Databasen är deras föredragna cassandra, men vi använder elasticsearch, det finns implementeringar för ett par andra databaser och en in-memory implementering som inte sparar något på disken
Jaeger-query är en tjänst som går till databasen och returnerar redan insamlade spår för analys
Jaeger-ui är ett webbgränssnitt för att söka och visa spår, det går till jaeger-query
En separat komponent kan kallas implementeringen av opentrace jaeger för specifika språk, genom vilka spann skickas till jaeger-agent. Ansluter Jagger i Java handlar om att implementera io.opentracing.Tracer-gränssnittet, varefter alla spår genom det kommer att flyga till den verkliga agenten.
Även för fjäderkomponenten kan du ansluta opentracing-fjäder-moln-startare och implementering från Jaeger opentracing-spring-jaeger-cloud-starter som automatiskt kommer att konfigurera spårning för allt som passerar genom dessa komponenter, till exempel http-förfrågningar till kontroller, förfrågningar till databasen via jdbc, etc.
Spårar loggning i Java
Någonstans på toppnivån måste det första Spännet skapas, detta kan göras automatiskt, till exempel av fjäderregulatorn när en förfrågan tas emot, eller manuellt om det inte finns någon. Den sänds sedan genom Scope nedan. Om någon av metoderna nedan vill lägga till ett Span, tar det nuvarande activeSpan från Scope, skapar ett nytt Span och säger att dess överordnade är det resulterande activeSpan, och gör det nya Span aktivt. När externa tjänster anropas skickas det aktuella aktiva intervallet till dem, och dessa tjänster skapar nya intervall med hänvisning till detta intervall.
Allt arbete går genom Tracer-instansen, du kan få det genom DI-mekanismen, eller GlobalTracer.get () som en global variabel om DI-mekanismen inte fungerar. Som standard, om spåraren inte har initierats, kommer NoopTracer att returnera vilket inte gör något.
Vidare erhålls det aktuella omfånget från spåraren genom ScopeManager, ett nytt omfång skapas från det nuvarande med en bindning av det nya omfånget, och sedan stängs det skapade omfånget, vilket stänger det skapade omfånget och returnerar det tidigare omfånget till det aktiva tillståndet. Scope är knutet till en tråd, så vid flertrådsprogrammering får du inte glömma att överföra det aktiva spannet till en annan tråd, för ytterligare aktivering av Scope för en annan tråd med hänvisning till detta intervall.
För flertrådsprogrammering finns det också TracedExecutorService och liknande omslag som automatiskt vidarebefordrar det aktuella spannet till tråden när asynkrona uppgifter startas:
private ExecutorService executor = new TracedExecutorService(
Executors.newFixedThreadPool(10), GlobalTracer.get()
);
HttpClient httpClient = new TracingHttpClientBuilder().build();
Problem vi stötte på
Bönor och DI fungerar inte alltid om spårämnet inte används i en tjänst eller komponent, alltså Autowired Tracer kanske inte fungerar och du måste använda GlobalTracer.get().
Anteckningar fungerar inte om det inte är en komponent eller tjänst, eller om metoden anropas från en angränsande metod av samma klass. Du måste vara noga med att kontrollera vad som fungerar och använda manuellt spårskapande om @Traced inte fungerar. Du kan även bifoga en extra kompilator för java-kommentarer, då borde de fungera överallt.
Prova med resurser fungerar inte i groovy, du måste använda försök äntligen.
Varje tjänst måste ha sitt eget spring.application.name under vilket spår kommer att loggas. Vad gör ett separat namn för försäljningen och testet, för att inte störa dem tillsammans.
Om du använder GlobalTracer och tomcat, så har alla tjänster som körs i denna tomcat en GlobalTracer, så de kommer alla att ha samma tjänstnamn.
När du lägger till spår till en metod måste du vara säker på att den inte anropas många gånger i en loop. Det är nödvändigt att lägga till ett gemensamt spår för alla samtal, vilket garanterar den totala arbetstiden. Annars skapas en överbelastning.
Väl i jaeger-ui gjordes för stora förfrågningar om ett stort antal spår, och eftersom de inte väntade på svar gjorde de det igen. Som ett resultat började jaeger-query äta mycket minne och sakta ner elastisk. Hjälpte till genom att starta om jaeger-query
Probabilistisk som filtrerar spår med viss sannolikhet.
Ratelimiting som begränsar antalet spår per sekund. Du kan konfigurera dessa inställningar på klienten, antingen på jaeger-agenten eller på insamlaren. Nu använder vi const 1 i värderingsstacken, eftersom det inte finns så många förfrågningar, men de tar lång tid. I framtiden, om detta kommer att utöva en överdriven belastning på systemet, kan du begränsa det.
Om du använder cassandra lagrar den som standard bara spår i två dagar. Vi använder elastisk sökning och spår lagras för all framtid och raderas inte. Ett separat index skapas för varje dag, till exempel jaeger-service-2019-03-04. I framtiden måste du konfigurera automatisk rengöring av gamla spår.
För att se spåren behöver du:
Välj tjänsten som du vill filtrera spår efter, till exempel tomcat7-default för en tjänst som körs i tomcat och inte kan ha ett eget namn.
Välj sedan operation, tidsintervall och minsta drifttid, till exempel från 10 sekunder, för att endast ta långa exekveringar.
Gå till ett av spåren och se vad som saktade ner där.
Dessutom, om något förfrågnings-id är känt, kan du hitta ett spår av detta ID genom en taggsökning, om detta ID är loggat i spårningsintervallet.