
Në projektet tona përdorim arkitekturën mikroshërbimore. Kur hasim ngushtësi në performancë, shpesh shpenzohet shumë kohë në monitorim dhe analizë të logeve. Duke regjistruar kohën e operacioneve të veçanta në skedarin e logut, zakonisht është e vështirë të kuptohet çfarë çoi në thirrjen e këtyre operacioneve, të ndjekësh renditjen e veprimeve ose të identifikosh devijimin në kohë të një operacioni në lidhje me një tjetër në shërbime të ndryshme.
Për të minimizuar punën manuale, vendosëm të përdorim një nga mjetet e përcaktimit. Ky artikull do të flasë për se si dhe për çfarë mund të përdoret përcaktimi dhe si e kemi realizuar ne.
ĂfarĂ« probleme mund tĂ« zgjidhen me anĂ« tĂ« pĂ«rcaktimit
- Të gjejmë ngushtësitë në performancë si brenda një shërbimi, ashtu edhe në të gjithë pemën e ekzekutimit mes të gjithë shërbimeve të përfshira. Për shembull:
- Shumë thirrje të shkurtra njëra pas tjetër mes shërbimeve, për shembull, për gjeokodimin ose për bazën e të dhënave.
- Pritje të gjata të hyrjes dhe daljes, për shembull, transferimi i të dhënave në rrjet ose leximi nga disku.
- Analizë e gjatë e të dhënave.
- Operacionet e gjata që kërkojnë CPU.
- Pjesë kodi që nuk janë të nevojshme për të marrë rezultatin përfundimtar dhe mund të hiqen, ose të ekzekutohen me vonesë.
- Të kuptohet qartë se në cilin rend ndodhin thirrjet dhe çfarë ndodh kur ekzekutohet një operacion.

ĂshtĂ« e dukshme se, pĂ«r shembull, KĂ«rkesa erdhi nĂ« shĂ«rbimin WS -> shĂ«rbimi WS e plotĂ«soi informacionin pĂ«rmes shĂ«rbimit R -> mĂ« pas dĂ«rgoi kĂ«rkesĂ«n nĂ« shĂ«rbimin V -> shĂ«rbimi V ngarkoi shumĂ« tĂ« dhĂ«na nga shĂ«rbimi R -> shkoi nĂ« shĂ«rbimin P -> shĂ«rbimi P shkoi pĂ«rsĂ«ri nĂ« shĂ«rbimin R -> shĂ«rbimi V e injoroi rezultatin dhe shkoi nĂ« shĂ«rbimin J -> dhe vetĂ«m atĂ«herĂ« ktheu pĂ«rgjigjen nĂ« shĂ«rbimin WS, duke vazhduar nĂ« sfond tĂ« llogariste diçka tjetĂ«r.
Pa një gjurmimi të tillë ose dokumentim të detajuar për të gjithë procesin, është shumë e vështirë të kuptohet se çfarë ndodh duke e parë për herë të parë kodin, dhe kodet shpesh janë të shpërndara në shërbime të ndryshme dhe të fshehura pas shumë bin-ve dhe ndërfaqeve. - Grumbullimi i informacionit në lidhje me pemën e ekzekutimit për analizë të ardhshme të vonuar. Në çdo fazë të ekzekutimit, mund të shtohet informacion në gjurmë i cili është i disponueshëm në atë fazë dhe më pas të analizojmë se cilat të dhëna hyrëse çuan në një skenar të tillë. Për shembull:
- ID e përdoruesit
- TĂ« drejtat
- Lloji i metodës së zgjedhur
- Logu ose gabim ekzekutimi
- Shndërrimi i gjurmëve në një nënshtresë metrikash dhe analiza e mëtejshme tashmë në formën e metrikeve.
ĂfarĂ« mund tĂ« regjistrojĂ« gjurmimi. Span
Në gjurmim ka një koncept të span-it, i cili është ekuivalenti i një logu në konsolë. Një span ka:
- Emrin, zakonisht emri i metodës që është ekzekutuar
- Emri i shërbimit në të cilin është gjeneruar span-i
- ID-ja unike e tij
- Një lloj informacioni meta në formën e çelësit/p value, të cilin e kanë regjistruar në të. P.sh., parametrat e metodës ose nëse metoda përfundoi me një gabim apo jo
- Koha e fillimit dhe përfundimit të ekzekutimit të këtij span-i
- ID e span-it prind
Ădo span dĂ«rgohet nĂ« kolektorin e span-ave pĂ«r ruajtje nĂ« bazĂ«n e tĂ« dhĂ«nave pĂ«r shikim tĂ« mĂ«vonshĂ«m sapo tĂ« pĂ«rfundojĂ« ekzekutimi i tij. MĂ« pas, mund tĂ« ndĂ«rtohet njĂ« pemĂ« e tĂ« gjitha span-ave duke lidhur sipas ID-sĂ« prind. GjatĂ« analizĂ«s, mund tĂ« gjeni, pĂ«r shembull, tĂ« gjitha span-at nĂ« njĂ« shĂ«rbim tĂ« caktuar qĂ« kanĂ« marrĂ« mĂ« shumĂ« se njĂ« kohĂ« tĂ« caktuar. Pastaj, duke kaluar nĂ« njĂ« span tĂ« caktuar, tĂ« shihni tĂ« gjithĂ« pemĂ«n lart dhe poshtĂ« kĂ«tij span-i.

Opentrace, Jaeger dhe si e kemi realizuar këtë për projektet tona
Ekziston një standard i përgjithshëm , i cili përshkruan se si dhe çfarë duhet të mbledhë, pa lidhur gjurmimin me një implementim të caktuar në një gjuhë të caktuar. Për shembull, në Java, të gjitha veprimet me gjurmët realizohen përmes një API të përbashkët Opentrace, dhe nën të mund të fshihet, p.sh., Jaeger ose një implementim default bosh që nuk bën asgjë.
Ne përdorim si implementim të Opentrace. Ai përbëhet nga disa komponentë:

- Jaeger-agent â agjenti lokal, i cili zakonisht gjendet nĂ« çdo makinĂ« dhe nĂ« tĂ« regjistrohen shĂ«rbimet nĂ« portin e tij default lokal. NĂ«se agjenti mungon, atĂ«herĂ« gjurmĂ«t e tĂ« gjitha shĂ«rbimeve nĂ« kĂ«tĂ« makinĂ« zakonisht janĂ« tĂ« fikura.
- Jaeger-collector â tĂ« gjithĂ« agjentĂ«t dĂ«rgojnĂ« gjurmĂ«t e mbledhura nĂ« tĂ«, dhe ai i ruan ato nĂ« bazĂ«n e tĂ« dhĂ«nave tĂ« zgjedhur.
- Baza e tĂ« dhĂ«nave â e preferuara pĂ«r ta Ă«shtĂ« Cassandra, por ne pĂ«rdorim Elasticsearch, ka implementime edhe pĂ«r disa baza tĂ« tjera tĂ« dhĂ«nash dhe njĂ« implementim nĂ« memorie, i cili nuk ruan asgjĂ« nĂ« disk.
- Jaeger-query â Ă«shtĂ« njĂ« shĂ«rbim qĂ« shkon nĂ« bazĂ«n e tĂ« dhĂ«nave dhe jep gjurmĂ«t e mbledhura pĂ«r analizĂ«.
- Jaeger-ui â Ă«shtĂ« ndĂ«rfaqja web pĂ«r kĂ«rkimin dhe shikimin e gjurmĂ«ve, ajo shkon tek jaeger-query.

Një komponent i veçantë mund të quhet implementimi i opentrace jaeger për gjuhë të caktuara, përmes së cilës span-at dërgohen tek jaeger-agent.
duhet të implementohet ndërfaqja io.opentracing.Tracer, pas së cilës të gjitha gjurmët do të dërgohen përmes saj në agjentin e vërtetë.

Po ashtu për komponentët e Spring mund të lidhni dhe implementimin nga Jaeger e cila do të konfigurojë automatikisht gjurmimin për çdo gjë që kalon përmes këtyre komponentëve, për shembull kërkesat http në kontrollet, kërkesat për databazën përmes jdbc, etj.
Logging i gjurmëve në Java
Diku në nivelin më të lartë duhet të krijohet Span-i i parë, kjo mund të bëhet automatikisht për shembull nga kontrolluesi i Spring kur merr një kërkesë, ose manualisht nëse nuk ka të tillë. Më pas ai kalon përmes Scope-it poshtë. Nëse ndonjë metodë poshtë dëshiron të shtojë një Span, ajo merr nga Scope-i current activeSpan, krijon një Span të ri dhe tregon që parent-i i tij është activeSpan i marrë, dhe e bën Span-in e ri aktiv. Kur thirren shërbime të jashtme, atyre u kalon aktuali active span, dhe ato shërbime krijojnë Spans të reja në lidhje me këtë span.
E gjithë puna bëhet përmes instancës Tracer, mund ta merrni atë përmes mekanizmit DI, ose GlobalTracer.get() si një variabël globale, nëse mekanizmi DI nuk funksionon. Nëse traci nuk është inicializuar, do të kthehet NoopTracer që nuk bën asgjë.
Më pas nga traci përmes ScopeManager merret scope-i aktual, krijohet një scope i ri nga ai aktual me lidhje nga një span i ri, dhe më pas mbyllet Scope-i i krijuar, i cili mbyll span-in e krijuar dhe kthen në gjendje aktive Scope-in e mëparshëm. Scope-i është i lidhur me thread-in, prandaj gjatë programimit me shumë threads nuk duhet të harrohet të kaloni active span-in në thread tjetër, për aktivizimin më të tutjeshëm të Scope-it të thread-it tjetër me lidhje me këtë span.
io.opentracing.Tracer tracer = ...; // GlobalTracer.get()
void DoSmth () {
try (Scope scope = tracer.buildSpan("DoSmth").startActive(true)) {
...
}
}
void DoOther () {
Span span = tracer.buildSpan("someWork").start();
try (Scope scope = tracer.scopeManager().activate(span, false)) {
// Bëni gjëra.
} catch(Exception ex) {
Tags.ERROR.set(span, true);
span.log(Map.of(Fields.EVENT, "error", Fields.ERROR_OBJECT, ex, Fields.MESSAGE, ex.getMessage()));
} finally {
span.finish();
}
}
void DoAsync () {
try (Scope scope = tracer.buildSpan("ServiceHandlerSpan").startActive(false)) {
...
final Span span = scope.span();
doAsyncWork(() -> {
// HAPI 2 MĂ SIPĂR: rikthe Span-in nĂ« callback, duke kaluar true nĂ«
// startActive() nëse/kohë kur duhet përfunduar Span-i.
try (Scope scope = tracer.scopeManager().activate(span, false)) {
...
}
});
}
}Për programimin me shumë përgjegjësi, ekziston gjithashtu TracedExecutorService dhe mbështetje të ngjashme, të cilat automatikisht përçojnë spanin aktual në thread kur fillojnë detyrat asinkrone:
private ExecutorService executor = new TracedExecutorService(
Executors.newFixedThreadPool(10), GlobalTracer.get()
);Për kërkesat http të jashtme, ka
HttpClient httpClient = new TracingHttpClientBuilder().build();Problemet me të cilat u përballëm
- Bean-ët dhe DI nuk funksionojnë gjithmonë nëse tracer përdoret jashtë shërbimit ose komponentit, në atë rast Tracer mund të mos funksionojë dhe do të duhet të përdorim GlobalTracer.get().
- Anotacionet nuk funksionojnë nëse nuk është një komponent ose shërbim, ose nëse thirrja e metodës ndodh nga një metodë tjetër e së njëjtës klasë. Duhet të jemi të kujdesshëm, të kontrollojmë çfarë funksionon dhe të përdorim krijimin manual të traces nëse @Traced nuk funksionon. Gjithashtu mund të lidhim një kompajler shtesë për anotacionet java, atëherë duhet të funksionojnë në çdo vend.
- Në versionet e vjetra të spring dhe spring boot, konfigurimi automatik i opentracing spring cloud nuk funksionon për shkak të defekteve në DI, kështu që nëse dëshirojmë që traces në komponentet e springut të funksionojnë automatikisht, mund të veprojmë si te
- Në groovy, nuk funksionon try with resources, duhet të përdorim gjithmonë try finally.
- Ădo shĂ«rbim duhet tĂ« ketĂ« emrin e vet spring.application.name nĂ«n tĂ« cilin do tĂ« regjistrohen traces. Po ashtu njĂ« emĂ«r tĂ« veçantĂ« pĂ«r prodhimin dhe testin, pĂ«r tĂ« mos e ndĂ«rthurur ato sĂ« bashku.
- Nëse përdorim GlobalTracer dhe tomcat, të gjitha shërbimet e nisura në këtë tomcat kanë një GlobalTracer, ndaj do të kenë të gjithë të njëjtin emër shërbimi.
- Në momentin që shtojmë traces në metodë, duhet të jemi të sigurt që ajo nuk thirret në një cikël shumë herë. Duhet të shtojmë një trace të përbashkët për të gjitha thirrjet, e cila do të regjistrojë kohën totale të punës. Ndryshe do të krijohet një ngarkesë e tepërt.
- Një herë në jaeger-ui bëmë kërkesa shumë të mëdha për një sasi të madhe traces dhe pasi nuk prisnim përgjigjen, bëmë atë përsëri. Si rezultat jaeger-query filloi të hante shumë memorje dhe ngadalësonte elastikun. E ndihmoi rinovimi i jaeger-query.
Sampling, ruajtja dhe shikimi i traces
Ekzistojnë tre tipe :
- Const i cili dërgon dhe ruan të gjitha traces.
- Probabilistic i cili filtrin traces me një probabilitet të caktuar.
- Ratelimiting që kufizon numrin e trasheve në sekondë. Këto parametra mund të konfigurohen në klient, ose në jaeger-agent ose në kolektor. Tani në stack-un tonë të validuesve përdorim const 1 pasi kërkesat nuk janë shumë, por ato zgjatin një kohë të gjatë. Në të ardhmen, nëse kjo do të shkaktojë një ngarkesë të tepruar në sistem, mund të kufizojmë.
Nëse përdorim Cassandra, atëherë për default ajo ruan trashet vetëm për dy ditë. Ne përdorim dhe trashet ruhen gjithmonë dhe nuk fshihen. Për çdo ditë krijohet një indeks i veçantë, për shembull jaeger-service-2019-03-04. Në të ardhmen duhet të konfigurojmë pastrimin automatik të trasheve të vjetra.
Për të parë trashet, duhet:
- Të zgjedhim shërbimin për të cilin dëshirojmë të filtrojmë trashet, për shembull tomcat7-default për shërbimin që është aktivizuar në Tomcat dhe nuk mund të ketë emrin e tij.
- Pastaj të zgjedhim operacionin, intervalin kohor dhe kohën minimale të operacionit, për shembull nga 10 sekonda, për të marrë vetëm ekzekutimet e gjata.

- Të kalojmë në një nga trashet dhe të shikojmë çfarë e ngadalësonte atje.

Po ashtu, nëse dihet një id e caktuar e kërkesës, atëherë mund të gjejmë trashe të dhënë përmes këtij id nëpërmjet kërkimit sipas etiketeve, nëse ky id logarohet në span e trashe.
Dokumentacioni
- Dokumentacioni opentracing
- Dokumentacioni jaeger
- Kufizimi jaeger java
- Kufizimi spring opentracing
Artikuj
- Jaeger Opentracing dhe Microservices në një projekt real në PHP dhe Golang
- Evolving Distributed Tracing at Uber Engineering
- Running Jaeger Agent on bare metal
Video
- How We Used Jaeger and Prometheus to Deliver Lightning-Fast User Queries â Bryan Boreham
- Intro: Jaeger â Yuri Shkuro, Uber & Pavol Loffay, Red Hat
- Serghei Iakovlev, âNjĂ« histori e vogĂ«l e njĂ« fitore tĂ« madhe: OpenTracing, AWS dhe Jaegerâ
Burimi: habr.com



