Xidmətlərin izlənişi, OpenTracing və Jaeger

Xidmətlərin izlənişi, OpenTracing və Jaeger

Layihələrimizdə mikroservis arxitekturasından istifadə edirik. Performansda dar boğazlar meydana gəldikdə, yoxlama və logların təhlilinə xeyli vaxt sərf olunur. Ayrı əməliyyatların zamanlamalarının log faylına yazılmasında, adətən, bu əməliyyatların çağırılmasına səbəb olan amilləri anlamaq, ayrı-ayrı xidmətlərdə bir əməliyyatın digərinə nisbətən zaman cədvəlini izləmək çətindir.

Əllə işləməni azaltmaq üçün biz izləmə vasitələrindən birini istifadə etməyə qərar verdik. Bu məqalədə izləmədən necə istifadə oluna biləcəyini və bunu necə etdiyimizi müzakirə edəcəyik.

İzləmə vasitəsilə hansı problemləri həll edə bilərik

  1. Performansda dar boğazları tapmaq, həm bir xidmətdə, həm də iştirak edən bütün xidmətlər arasında icra ağacında. Məsələn:
    • Xidmətlər arasında çox sayda qısa ardıcıl çağırışlar, məsələn, geokodlaşdırma və ya verilənlər bazasına müraciət.
    • Veri ötürmələri zamanı uzun müddət gözləmələr, məsələn, şəbəkə vasitəsilə məlumatların ötürülməsi və ya diskdən oxumaq.
    • Məlumatların uzun müddət emalı.
    • CPU tələb edən uzun əməliyyatlar.
    • Son nəticəyə çatmaq üçün lazım olmayan kod hissələri, ya silinə bilər, ya da gecikmiş şəkildə icra oluna bilər.
  2. Hansı ardıcıllıqla çağırışların baş verdiyini və əməliyyat yerinə yetiriləndə nə baş verdiyini aydın şəkildə anlayın.
    Xidmətlərin izlənişi, OpenTracing və Jaeger
    Məsələn, görünür ki, sorğu WS xidmətinə daxil olub -> WS xidməti R xidmətindən məlumatı tamamlayıb -> sonra V xidmətinə sorğu göndərib -> V xidməti R xidmətindən çoxlu məlumat yükləyib -> P xidmətinə müraciət edib -> P xidməti bir daha R xidmətinə yönəlib -> V xidməti nəticəni görməzlikdən gəlib və J xidmətinə yönəlir -> yalnız sonra WS xidmətinə cavabı qaytarıb, arxa planda hələ də bir şeyləri hesablamağa davam edir.
    Belə bir iz izi olmadan və ya prosesin tam sənədinə sahib olmadan, kodda ilk dəfə baxan zaman nə baş verdiyini anlamaq çox çətindir, həmçinin kod müxtəlif xidmətlərə yayılıb və bir çox binlərin və interfeyslərin arxasında gizlənib.
  3. İcra ağacının məlumatlarını toplamaq, sonrakı gecikmiş analiz üçün. İcra zamanı hər mərhələdə izə, həmin mərhələdə mövcud olan məlumatları əlavə etmək olar və sonra bu cür ssenarinin yaranmasına səbəb olan giriş məlumatlarını öyrənmək mümkündür. Məsələn:
    • İstifadəçinin ID-si
    • Haqlar
    • Seçilmiş metodun tipi
    • Log və ya icra xətası
  4. İzlərin metrikalara çevrilməsi və daha sonra metriklər şəklində analizi.

Trazit tərtibatını qeydə almaq bacarığı. Span

Trazitdə span anlayışı var, bu, bir loqun analoqudur, konsolda. Spanın aşağıdakılar var:

  • Adı, adətən icra edilən metodun adı
  • Spanın yaradıldığı xidmətin adı
  • Özünəməxsus unikal ID
  • Qeydə alınmış bəzi meta məlumatlar key/value şəklində. Məsələn, metodun parametrləri və ya metodun səhvlə tamamlanıb-tamamlanmadığı
  • Bu spanın icrasının başlanğıc və son vaxtı
  • Valideyn spanın ID-si

Hər span, tamamlandıqdan sonra görüntülənməsi üçün verilənlər bazasında saxlanması məqsədilə span kollektoruna göndərilir. Sonradan valideyn ID-yə görə bütün spaların ağacını qurmaq olar. Analiz zamanı məsələn, müəyyən bir xidmətdə müəyyən vaxtdan çox çəkən bütün spaları tapmaq mümkündür. Daha sonra, müəyyən bir spana keçərək, bu spanın üstündə və altındakı bütün ağacı görmək olar.

Xidmətlərin izlənişi, OpenTracing və Jaeger

Opentrace, Jaeger və bunu layihələrimizdə necə həyata keçirdiyimiz

Ümumi standart var Opentrace, hansı ki, nəyin hansı şəkildə toplanacağına dair bir təsvir verir, müəyyən bir proqramlaşdırma dilində konkret reallaşmaya bağlı olmadan. Məsələn, Java-da bütün izlərlə iş ümumi Opentrace API-sı vasitəsilə aparılır, onun altında isə mümkün olan, məsələn, Jaeger və ya heç nə etmir olan boş standart reallaşma ola bilər.
Bizim istifadə etdiyimiz Jaeger Opentrace-in reallaşması kimi. O, bir neçə komponentdən ibarətdir:

Xidmətlərin izlənişi, OpenTracing və Jaeger

  • Jaeger-agent — adətən hər bir maşında yerləşən yerli agentdir və oraya lokaldakı standart porta xidmətlər tərəfindən qeydlər edilir. Əgər agent yoxdursa, həmin maşındakı bütün xidmətlərin izləri adətən söndürülür.
  • Jaeger-collector — bütün agentlərin topladığı izləri göndərdiyi yerdir və o, bunları seçilmiş verilənlər bazasına yerləşdirir.
  • Verilənlər bazası — onların üstünlük verdiyi cassandra-dır, lakin biz elasticsearch istifadə edirik, başqa bir neçə verilənlər bazası üçün reallaşmalar və diski heç bir şey saxlamayan yaddaşda reallaşma da var.
  • Jaeger-query — verilənlər bazasına müraciət edən və analiz üçün artıq toplanmış izləri təqdim edən xidmətdir.
  • Jaeger-ui — izlərin axtarışı və görüntülənməsi üçün veb interfeysdir, jaeger-query-ə müraciət edir.

Xidmətlərin izlənişi, OpenTracing və Jaeger

Xüsusi bir komponent olaraq xüsusi dillər üçün opentrace jaeger-in reallaşdırılmasını qeyd etmək olar, hansında ki spalar jaeger-agent-ə göndərilir.
Java-da Jagger-in bağlanması ümumi olaraq, io.opentracing.Tracer interfeysini tətbiq etməklə başlayır, bundan sonra bütün izlər onun vasitəsilə real agentə yollanacaq.

Xidmətlərin izlənişi, OpenTracing və Jaeger

Eyni zamanda Spring komponentləri üçün qoşula bilər opentracing-spring-cloud-starter və Jaeger tərəfindən tətbiq etmə opentracing-spring-jaeger-cloud-starter hansı ki, bu komponentlərdən keçən bütün prosesslər, məsələn HTTP sorğuları kontrollerlərə, JDBC vasitəsilə verilənlər bazasına sorğular və s. üçün avtomatik izləri konfiqurasiya edəcək.

Java-da izlərin qeydiyyatı

Ən yuxarı səviyyədə ilk Span yaradılmalıdır, bu, sorğu alındıqda Spring kontrolleri tərəfindən avtomatik edilə bilər, ya da əgər belə yoxdursa, əl ilə. Sonra o, Scope vasitəsilə aşağıya ötürülür. Əgər aşağıdakı bir metod Span əlavə etmək istəyirsə, o, cari activeSpan-dan alır, yeni bir Span yaradır və onun valideynin aldığı activeSpan olduğunu bildirir, və yeni Span aktiv edir. Xarici xidmətlərə çağırıldıqda, onlara cari aktiv span ötürülür və o xidmətlər bu spana bağlı yeni spandlar yaradır.
Bütün iş Tracer instansiyası vasitəsilə həyata keçirilir, onu DI mexanizmi ilə əldə etmək olar, ya da DI mexanizmi işləmirsə, GlobalTracer.get() kimi qlobal dəyişən olaraq. Əgər tracer ilkinləşdirilməyibsə, default olaraq heç bir şey etməyən NoopTracer geri dönəcək.
Tracer-dən ScopeManager vasitəsilə cari scope əldə olunur, cari scope-dan yeni bir scope yaradılır yeni span-ın bağlanması ilə, daha sonra yaradılan Scope bağlanır, bu da yaradılan spanı bitirir və əvvəllki Scope-u aktiv vəziyyətə qaytarır. Scope, ipə bağlı olduğuna görə, çox ipli proqramlaşdırma zamanı aktiv span-ı başqa ipə ötürməyi unutmamalıyıq, bu da o spanla bağlı digər ipin Scope-un aktivləşdirilməsini təmin edir.

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)) {
        // İşləri edin.
    } 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(() -> {
            // YUXARIDAKI ADDIM 2: Callback-da Span-ı yenidən aktivləşdirin, Span bitməli olduqda startActive() -i true olaraq ötürün.
            try (Scope scope = tracer.scopeManager().activate(span, false)) {
                ...
            }
        });
    }
}

Çoxsahəli proqramlaşdırma üçün, TracedExecutorService və oxşar paketlər var ki, bunlar avtomatik olaraq cari span-i ip ilə göndərirlər, asinxron vəzifələri başladarkən:

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

Xarici http sorğuları üçün TracingHttpClient

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

Karşılaşdığımız problemlər

  • Bean-lar və DI, tracer serverdə və ya komponentdə istifadə olunmadıqda həmişə işləməyə bilər, beləliklə @Autowired Tracer işləməyə bilər və GlobalTracer.get() istifadə etmək lazım gələcək.
  • Annotasiyalar işləməyəcək əgər bu, komponent və ya xidmət deyilsə, yaxud metod çağırışı eyni sinifin qonşu metodundan olarsa. Diqqətli olmaq, işinin olub olmadığını yoxlamaq və @Traced işləmədiyi halda əl ilə trace yaradılmasını istifadə etmək lazımdır. Eyni zamanda, Java annotasiyaları üçün əlavə bir kompilyator qoşmaq olar ki, o zaman hər yerdə işləməlidir.
  • Köhnə spring və spring boot-da opentracing spring cloud-un avtomatik konfiqurasiyası DI-dəki xətalara görə işləmir, beləliklə, əgər spring komponentlərində avtomatik trace-lərin işləməsini istəyirsənsə, bunu 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
  • Groovy-də try with resources işləməyə bilər, mütləq try finally istifadə etmək lazımdır.
  • Hər xidmət üçün spring.application.name yazılmalıdır ki, bu ad altında trace-lər loglaşdırılacaq. Həmçinin, test və istehsal üçün ayrı bir ad olmalıdır ki, bunlar qarışmasın.
  • GlobalTracer və tomcat istifadə edərkən, bu tomcat-da işə salınan bütün xidmətlər bir GlobalTracer sahibi olur, buna görə hamısının eyni xidmət adı olacaq.
  • Metoda trace əlavə edərkən, onun bir döngədə çox sayda dəfə çağrılmadığına əmin olmaq lazımdır. Bütün çağırışlar üçün bir ümumi trace əlavə etmək lazımdır ki, ümumi iş müddətini loglaşdırsın. Əks halda, əlavə yük yaradılacaq.
  • Bir dəfə jaeger-ui-da çox sayda trace-ların olduğu böyük sorğular etdikdə və cavabını gözləmədikdə yenidən etdik. Nəticədə jaeger-query çox yaddaş istehlak etməyə və elastiklə xizəkləməyə başladı. jaeger-query-in yeniden başlaması kömək etdi.

Trace-ların nümunələşdirilməsi, saxlanması və baxılması

Üç növ var trace nümunələşdirməsi:

  1. Const, bütün trace-ləri göndərir və saxlayır.
  2. Probabilistic, müəyyən bir ehtimal ilə trace-ləri süzgəcdən keçirir.
  3. Ratelimiting, hansı ki, bir saniyədəki treyserlərin sayını məhdudlaşdırır. Bu parametrləri müştəri tərəfində, jaeger-agent-də və ya kollektorla tənzimləmək olar. Hal-hazırda bizim valyuta yığımı stekində const 1 istifadə olunur, çünki sorğular çox olmur, amma onlar uzun müddət tələb edir. Gələcəkdə əgər bu sistemə həddindən artıq yük gətirərsə, məhdudlaşdırmaq olar.

Cassandra istifadə edildikdə, o, standart olaraq yalnız iki günlük treyserləri saxlayır. Bizdə isə :9200/", :error_type=>LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError, :error=>"Elasticsearch Unreachable: [http://elasticsearch:9200/][Manticore::ResolutionFailure] elasticsearch"} və treyserlər bütün müddət üçün saxlanılır və silinmir. Hər gün üçün ayrıca indeks yaradılır, məsələn, jaeger-service-2019-03-04. Gələcəkdə köhnə treyserlərin avtomatik silinməsini tənzimləmək lazımdır.

Treyserləri görmək üçün:

  • Filtrləmək istədiyiniz xidməti seçin, məsələn, tomcat7-default, tomcatda işə salınmış xidmət üçün və onun adı olmur.
  • Sonra əməliyyatı, zaman aralığını və əməliyyatın minimum müddətini seçin, məsələn, 10 saniyədən başlayaraq yalnız uzun icra edilmələri götürmək.
    Xidmətlərin izlənişi, OpenTracing və Jaeger
  • Bir treyserə keçin və orada nə səbəb olduğunu yoxlayın.
    Xidmətlərin izlənişi, OpenTracing və Jaeger

Eyni zamanda, əgər müəyyən bir sorğu id-si məlumdursa, o id vasitəsilə treyser tapmaq olar, əgər bu id treyserin span-ında qeyd olunursa.

Sənəd

Məqalələr

Video

Mənbə: habr.com

DDoS qoruması olan saytlara etibarlı hosting satın alın, VPS VDS serverlər 🔥 DDoS qoruması olan saytlara etibarlı hosting satın alın, VPS VDS serverlər | ProHoster