Service Tracing, OpenTracing și Jaeger

Service Tracing, OpenTracing și Jaeger

Utilizăm arhitectura de microservicii în proiectele noastre. Când apar blocaje de performanță, se petrece mult timp monitorizând și analizând jurnalele. Când înregistrați cronometrarea operațiunilor individuale într-un fișier jurnal, este de obicei dificil de înțeles ce a condus la invocarea acestor operațiuni, să urmăriți succesiunea acțiunilor sau deplasarea în timp a unei operațiuni față de alta în diferite servicii.

Pentru a minimiza munca manuală, am decis să folosim unul dintre instrumentele de urmărire. Despre cum și de ce puteți utiliza urmărirea și cum am făcut-o, și vor fi discutate în acest articol.

Ce probleme pot fi rezolvate cu urmărirea

  1. Găsiți blocajele de performanță atât în ​​cadrul unui singur serviciu, cât și în întregul arbore de execuție între toate serviciile participante. De exemplu:
    • Multe apeluri consecutive scurte între servicii, de exemplu, la geocodare sau la o bază de date.
    • Așteptări lungi de I/O, cum ar fi transferurile de rețea sau citirile de disc.
    • Analiza lungă a datelor.
    • Operațiuni lungi care necesită CPU.
    • Secțiuni de cod care nu sunt necesare pentru a obține rezultatul final și pot fi eliminate sau amânate.
  2. Înțelegeți clar în ce secvență ce se numește și ce se întâmplă atunci când se efectuează operația.
    Service Tracing, OpenTracing și Jaeger
    Se poate observa că, de exemplu, Solicitarea a venit la serviciul WS -> serviciul WS a adăugat date prin serviciul R -> apoi a trimis o cerere către serviciul V -> serviciul V a încărcat multe date din R service -> a mers la serviciul P -> serviciul P a trecut din nou la serviciul R -> serviciul V a ignorat rezultatul și a trecut la serviciul J -> și abia apoi a returnat răspunsul la serviciul WS, continuând să calculeze altceva în fundal.
    Fără o astfel de urmă sau o documentație detaliată pentru întregul proces, este foarte greu de înțeles ce se întâmplă atunci când te uiți la cod pentru prima dată, iar codul este împrăștiat în diferite servicii și ascuns în spatele unei grămadă de coșuri și interfețe.
  3. Colectarea de informații despre arborele de execuție pentru analiza ulterioară amânată. La fiecare etapă de execuție, puteți adăuga informații la urmărirea care este disponibilă în această etapă și apoi vă puteți da seama ce date de intrare au condus la un scenariu similar. De exemplu:
    • ID-ul de utilizator
    • Drepturile
    • Tipul metodei selectate
    • Eroare de jurnal sau de execuție
  4. Transformarea urmelor într-un subset de metrici și analize ulterioare deja sub formă de metrici.

Ce urmă se poate înregistra. Span

În urmărire există conceptul de interval, acesta este un analog al unui jurnal, la consolă. Centrul spa are:

  • Nume, de obicei numele metodei care a fost executată
  • Numele serviciului în care a fost generat intervalul
  • ID unic propriu
  • Un fel de metainformații sub forma unei chei/valoare care a fost conectată la ea. De exemplu, parametrii metodei sau metoda s-au încheiat cu o eroare sau nu
  • Ora de început și de sfârșit pentru această perioadă
  • ID-ul intervalului parental

Fiecare span este trimis la colectorul span pentru a fi stocat în baza de date pentru revizuire ulterioară, de îndată ce și-a încheiat execuția. În viitor, puteți construi un arbore cu toate întinderile conectându-vă prin ID-ul părinte. Când analizați, puteți găsi, de exemplu, toate intervalele dintr-un serviciu care a durat mai mult de ceva timp. În plus, mergând la un anumit interval, vedeți întregul copac deasupra și dedesubtul acestui interval.

Service Tracing, OpenTracing și Jaeger

Opentrace, Jagger și cum l-am implementat pentru proiectele noastre

Există un standard comun opentrace, care descrie cum și ce ar trebui colectat, fără a fi legat de o anumită implementare în orice limbă. De exemplu, în Java, toate lucrările cu urme sunt efectuate prin intermediul API-ului Opentrace comun, iar sub acesta, de exemplu, Jaeger sau o implementare implicită goală care nu face nimic poate fi ascunsă.
Noi folosim Jaeger ca implementare a Opentrace. Este format din mai multe componente:

Service Tracing, OpenTracing și Jaeger

  • Jaeger-agent este un agent local care este de obicei instalat pe fiecare mașină și serviciile sunt conectate la acesta pe portul implicit local. Dacă nu există niciun agent, atunci urmele tuturor serviciilor de pe această mașină sunt de obicei dezactivate
  • Jaeger-collector - toți agenții îi trimit urme colectate și le pune în baza de date selectată
  • Baza de date este Cassandra lor preferată, dar folosim elasticsearch, există implementări pentru alte câteva baze de date și o implementare în memorie care nu salvează nimic pe disc.
  • Jaeger-query este un serviciu care merge la baza de date și returnează urmele deja colectate pentru analiză
  • Jaeger-ui este o interfață web pentru căutarea și vizualizarea urmelor, merge la jaeger-query

Service Tracing, OpenTracing și Jaeger

O componentă separată poate fi numită implementarea opentrace jaeger pentru anumite limbaje, prin care intervalele sunt trimise către jaeger-agent.
Conectarea lui Jagger în Java se reduce la implementarea interfeței io.opentracing.Tracer, după care toate urmele prin aceasta vor zbura către agentul real.

Service Tracing, OpenTracing și Jaeger

De asemenea, pentru componenta cu arc, puteți conecta opentracing-spring-cloud-starter și implementare de la Jaeger opentracing-spring-jaeger-cloud-starter care va configura automat urmărirea pentru tot ceea ce trece prin aceste componente, de exemplu solicitări http către controlori, solicitări către baza de date prin jdbc etc.

Înregistrarea urmelor în Java

Undeva, la nivelul superior, trebuie creat primul Span, acest lucru se poate face automat, de exemplu, de către controlerul cu arc atunci când se primește o solicitare, sau manual dacă nu există. Acesta este apoi transmis prin Scopul de mai jos. Dacă vreo metodă de mai jos dorește să adauge un Span, ia ActiveSpan curent din Scop, creează un nou Span și spune că părintele său este ActiveSpan rezultat și activează noul Span. Când apelați servicii externe, li se transmite intervalul activ curent, iar aceste servicii creează noi intervale cu referire la acest interval.
Toată munca trece prin instanța Tracer, o puteți obține prin mecanismul DI sau GlobalTracer.get () ca variabilă globală dacă mecanismul DI nu funcționează. În mod implicit, dacă tracerul nu a fost inițializat, NoopTracer va reveni care nu face nimic.
În plus, domeniul curent este obținut de la urmăritor prin ScopeManager, un domeniu nou este creat din cel curent cu o legare a noului interval, iar apoi Scopul creat este închis, care închide intervalul creat și returnează domeniul anterior la starea activă. Scopul este legat de un fir, așa că atunci când programați cu mai multe fire de execuție, nu trebuie să uitați să transferați intervalul activ într-un alt thread, pentru activarea ulterioară a Scopului altui thread cu referire la acest interval.

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)) {
        // Do things.
    } 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(() -> {
            // STEP 2 ABOVE: reactivate the Span in the callback, passing true to
            // startActive() if/when the Span must be finished.
            try (Scope scope = tracer.scopeManager().activate(span, false)) {
                ...
            }
        });
    }
}

Pentru programarea cu mai multe fire de execuție, există, de asemenea, TracedExecutorService și wrapper-uri similare care redirecționează automat intervalul curent către fir atunci când sunt lansate sarcini asincrone:

private ExecutorService executor = new TracedExecutorService(
    Executors.newFixedThreadPool(10), GlobalTracer.get()
);

Pentru solicitările http externe există UrmărireHttpClient

HttpClient httpClient = new TracingHttpClientBuilder().build();

Probleme cu care ne-am confruntat

  • Fasolea și DI nu funcționează întotdeauna dacă trasorul nu este utilizat într-un serviciu sau componentă, atunci Cablat automat Este posibil ca Tracer să nu funcționeze și va trebui să utilizați GlobalTracer.get().
  • Adnotările nu funcționează dacă nu este o componentă sau serviciu, sau dacă metoda este apelată dintr-o metodă vecină din aceeași clasă. Trebuie să aveți grijă să verificați ce funcționează și să utilizați crearea manuală a urmei dacă @Traced nu funcționează. De asemenea, puteți atașa un compilator suplimentar pentru adnotările java, atunci acestea ar trebui să funcționeze peste tot.
  • În vechea cizmă cu arc și arc, autoconfigurarea opentraing spring cloud nu funcționează din cauza erorilor din DI, atunci dacă doriți ca urmele din componentele arcului să funcționeze automat, puteți face acest lucru prin analogie cu github.com/opentracing-contrib/java-spring-jaeger/blob/master/opentracing-spring-jaeger-starter/src/main/java/io/opentracing/contrib/java/spring/jaeger/starter/JaegerAutoConfiguration.java
  • Încercați cu resurse nu funcționează în groovy, trebuie să utilizați încercați în sfârșit.
  • Fiecare serviciu trebuie să aibă propriul său spring.application.name sub care vor fi înregistrate urmele. Ce înseamnă un nume separat pentru vânzare și testare, pentru a nu interfera cu ele împreună.
  • Dacă utilizați GlobalTracer și tomcat, atunci toate serviciile care rulează în acest tomcat au un GlobalTracer, astfel încât toate vor avea același nume de serviciu.
  • Când adăugați urme la o metodă, trebuie să vă asigurați că aceasta nu este apelată de multe ori într-o buclă. Este necesar să adăugați o urmă comună pentru toate apelurile, care garantează timpul total de lucru. În caz contrar, va fi creată o sarcină în exces.
  • Odată ajuns în jaeger-ui, s-au făcut cereri prea mari pentru un număr mare de urme și, din moment ce nu au așteptat un răspuns, au făcut-o din nou. Ca urmare, Jaeger-Query a început să mănânce multă memorie și să încetinească elasticitatea. Ajutat prin repornirea jaeger-query

Eșantionarea, stocarea și vizualizarea urmelor

Există trei tipuri urme de prelevare:

  1. Const care trimite și salvează toate urmele.
  2. Probabilistică care filtrează urmele cu o anumită probabilitate dată.
  3. Ratelimiting care limitează numărul de urme pe secundă. Puteți configura aceste setări pe client, fie pe agentul jaeger, fie pe colector. Acum folosim const 1 în stiva de evaluatori, deoarece nu sunt foarte multe cereri, dar durează mult. În viitor, dacă acest lucru va exercita o sarcină excesivă asupra sistemului, o puteți limita.

Dacă utilizați Cassandra, atunci în mod implicit stochează doar urme timp de două zile. Noi folosim elastic căutare iar urmele sunt stocate pentru tot timpul și nu sunt șterse. Un index separat este creat pentru fiecare zi, de exemplu jaeger-service-2019-03-04. În viitor, trebuie să configurați curățarea automată a urmelor vechi.

Pentru a vizualiza urmele aveți nevoie de:

  • Selectați serviciul prin care doriți să filtrați urmele, de exemplu, tomcat7-default pentru un serviciu care rulează în tomcat și nu poate avea propriul nume.
  • Apoi selectați operația, intervalul de timp și timpul minim de funcționare, de exemplu de la 10 secunde, pentru a dura doar execuții lungi.
    Service Tracing, OpenTracing și Jaeger
  • Du-te la una dintre urme și vezi ce încetinește acolo.
    Service Tracing, OpenTracing și Jaeger

De asemenea, dacă se cunoaște un ID de cerere, atunci puteți găsi o urmă după acest id printr-o căutare de etichete, dacă acest id este înregistrat în intervalul de urmărire.

Documentație

Articole

video

Sursa: www.habr.com

Adauga un comentariu