Śledzenie usług, OpenTracing i Jaeger

Śledzenie usług, OpenTracing i Jaeger

W naszych projektach wykorzystujemy architekturę mikroserwisową. W przypadku wystąpienia wąskich gardeł wydajności wiele czasu spędza się na monitorowaniu i analizowaniu dzienników. Podczas rejestrowania czasów poszczególnych operacji w pliku dziennika zwykle trudno jest zrozumieć, co doprowadziło do wywołania tych operacji, prześledzić sekwencję działań lub przesunięcie czasowe jednej operacji względem drugiej w różnych usługach.

Aby zminimalizować pracę ręczną, zdecydowaliśmy się użyć jednego z narzędzi śledzenia. O tym, jak i dlaczego można używać śledzenia i jak to zrobiliśmy, zostanie omówione w tym artykule.

Jakie problemy można rozwiązać za pomocą śledzenia

  1. Znajdź wąskie gardła wydajności zarówno w ramach pojedynczej usługi, jak i w całym drzewie wykonania między wszystkimi uczestniczącymi usługami. Na przykład:
    • Wiele krótkich, następujących po sobie połączeń między usługami, na przykład do geokodowania lub do bazy danych.
    • Długie oczekiwania we/wy, takie jak transfery sieciowe lub odczyty dysków.
    • Długie przetwarzanie danych.
    • Długie operacje wymagające procesora.
    • Sekcje kodu, które nie są potrzebne do uzyskania końcowego wyniku i można je usunąć lub opóźnić.
  2. Wyraźnie zrozum, w jakiej kolejności nazywa się i co się dzieje, gdy operacja jest wykonywana.
    Śledzenie usług, OpenTracing i Jaeger
    Widać, że np. Żądanie przyszło do usługi WS -> usługa WS dodała dane przez usługę R -> następnie wysłała żądanie do usługi V -> usługa V załadowała dużo danych z usługi R serwis -> poszedł do serwisu P -> serwis P ponownie poszedł do serwisu R -> serwis V zignorował wynik i poszedł do serwisu J -> i dopiero wtedy zwrócił odpowiedź do serwisu WS, kontynuując wyliczanie czegoś innego w tło.
    Bez takiego śladu lub szczegółowej dokumentacji całego procesu bardzo trudno jest zrozumieć, co się dzieje, gdy patrzysz na kod po raz pierwszy, a kod jest rozproszony po różnych usługach i ukryty za wiązką koszy i interfejsów.
  3. Zbieranie informacji o drzewie wykonania do późniejszej odroczonej analizy. Na każdym etapie realizacji można dodać informacje do śledzenia, które są dostępne na tym etapie, a następnie dowiedzieć się, jakie dane wejściowe doprowadziły do ​​podobnego scenariusza. Na przykład:
    • Identyfikator użytkownika
    • Prawa
    • Rodzaj wybranej metody
    • Błąd dziennika lub wykonania
  4. Przekształcenie śladów w podzbiór metryk i dalsza analiza już w postaci metryk.

Jaki ślad można zarejestrować. Zakres

W śledzeniu istnieje koncepcja przęsła, jest to odpowiednik jednego dziennika, do konsoli. Uzdrowisko posiada:

  • Nazwa, zwykle nazwa metody, która została wykonana
  • Nazwa usługi, w której został wygenerowany zakres
  • Własny unikalny identyfikator
  • Jakiś rodzaj metainformacji w postaci klucza/wartości, który został do niego zalogowany. Na przykład parametry metody lub metoda zakończyła się błędem lub nie
  • Godziny rozpoczęcia i zakończenia dla tego zakresu
  • Identyfikator zakresu nadrzędnego

Każdy zakres jest wysyłany do kolektora zakresów w celu zapisania go w bazie danych do późniejszego przeglądu, gdy tylko zakończy się jego wykonanie. W przyszłości możesz zbudować drzewo wszystkich rozpiętości, łącząc się według identyfikatora rodzica. Analizując, możesz znaleźć na przykład wszystkie przęsła w jakiejś usłudze, które trwały dłużej niż jakiś czas. Ponadto, przechodząc do określonego przęsła, zobacz całe drzewo powyżej i poniżej tego przęsła.

Śledzenie usług, OpenTracing i Jaeger

Opentrace, Jagger i jak zaimplementowaliśmy to w naszych projektach

Istnieje wspólny standard otwórz ślad, który opisuje, jak i co należy gromadzić, bez wiązania się z konkretną implementacją w jakimkolwiek języku. Na przykład w Javie cała praca ze śladami odbywa się za pośrednictwem wspólnego API Opentrace, a pod nim można ukryć np. Jaeger lub pustą domyślną implementację, która nic nie robi.
Używamy Jaeger jako implementacja Opentrace. Składa się z kilku elementów:

Śledzenie usług, OpenTracing i Jaeger

  • Jaeger-agent to lokalny agent, który jest zwykle instalowany na każdym komputerze, a usługi są do niego logowane na lokalnym porcie domyślnym. Jeśli nie ma agenta, śledzenie wszystkich usług na tej maszynie jest zwykle wyłączone
  • Jaeger-collector - wszyscy agenci przesyłają do niego zebrane ślady, a on umieszcza je w wybranej bazie danych
  • Bazą danych jest ich preferowana Cassandra, ale używamy Elasticsearch, istnieją implementacje dla kilku innych baz danych i implementacja w pamięci, która nie zapisuje niczego na dysku
  • Jaeger-query to usługa, która trafia do bazy danych i zwraca już zebrane ślady do analizy
  • Jaeger-ui to interfejs internetowy do wyszukiwania i przeglądania śladów, przechodzi do jaeger-query

Śledzenie usług, OpenTracing i Jaeger

Odrębny komponent można nazwać implementacją opentrace jaeger dla określonych języków, przez który przesyłane są span do jaeger-agent.
Łączenie Jaggera w Javie sprowadza się do zaimplementowania interfejsu io.opentracing.Tracer, po czym wszystkie ślady przez niego przelatują do prawdziwego agenta.

Śledzenie usług, OpenTracing i Jaeger

Również w przypadku elementu sprężynowego można połączyć opentracing-wiosna-cloud-starter i wdrożenie firmy Jaeger opentracing-spring-jaeger-cloud-starter który automatycznie skonfiguruje śledzenie dla wszystkiego, co przechodzi przez te komponenty, na przykład żądania http do kontrolerów, żądania do bazy danych przez jdbc itp.

Rejestrowanie śladów w Javie

Gdzieś na najwyższym poziomie musi zostać utworzony pierwszy Span, można to zrobić automatycznie, na przykład przez kontroler sprężyny po otrzymaniu żądania lub ręcznie, jeśli go nie ma. Następnie jest przesyłany przez zakres poniżej. Jeśli dowolna z poniższych metod chce dodać Span, pobiera bieżący ActiveSpan z zakresu, tworzy nowy Span i mówi, że jego rodzicem jest wynikowy ActiveSpan i sprawia, że ​​nowy Span jest aktywny. Podczas wywoływania usług zewnętrznych przekazywany jest im bieżący aktywny zakres, a te usługi tworzą nowe zakresy w odniesieniu do tego zakresu.
Cała praca przechodzi przez instancję Tracer, możesz ją uzyskać poprzez mechanizm DI, lub GlobalTracer.get() jako zmienną globalną, jeśli mechanizm DI nie działa. Domyślnie, jeśli tracer nie został zainicjowany, NoopTracer zwróci, co nic nie robi.
Ponadto bieżący zakres jest uzyskiwany ze znacznika poprzez ScopeManager, z bieżącego tworzony jest nowy zakres z powiązaniem nowego zakresu, a następnie utworzony zakres jest zamykany, co zamyka utworzony zakres i zwraca poprzedni zakres do stan aktywny. Zakres jest powiązany z wątkiem, więc podczas programowania wielowątkowego nie można zapomnieć o przeniesieniu aktywnego zakresu do innego wątku w celu dalszej aktywacji zakresu innego wątku w odniesieniu do tego zakresu.

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)) {
                ...
            }
        });
    }
}

W przypadku programowania wielowątkowego istnieje również TracedExecutorService i podobne opakowania, które automatycznie przekazują bieżący zakres do wątku, gdy uruchamiane są zadania asynchroniczne:

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

W przypadku zewnętrznych żądań HTTP jest ŚledzenieHttpClient

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

Problemy, przed którymi stanęliśmy

  • Fasola i DI nie zawsze działają, jeśli znacznik nie jest używany w usłudze lub komponencie Automatyczny Tracer może nie działać i będziesz musiał użyć GlobalTracer.get().
  • Adnotacje nie działają, jeśli nie jest to komponent lub usługa, lub jeśli metoda jest wywoływana z sąsiedniej metody tej samej klasy. Musisz uważać, aby sprawdzić, co działa i użyć ręcznego tworzenia śledzenia, jeśli @Traced nie działa. Możesz też dołączyć dodatkowy kompilator dla adnotacji java, wtedy powinny działać wszędzie.
  • W starym wiosennym i wiosennym bucie nie działa autokonfiguracja opentraing spring cloud z powodu błędów w DI, więc jeśli chcesz, aby ślady w komponentach sprężynowych działały automatycznie, możesz to zrobić analogicznie z https://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
  • Spróbuj z zasobami nie działa w groovy, musisz użyć try w końcu.
  • Każda usługa musi mieć własną nazwę spring.application.name, pod którą będą rejestrowane ślady. Co oznacza osobna nazwa dla sprzedaży i testu, żeby nie kolidować z nimi razem.
  • Jeśli używasz GlobalTracer i tomcat, wszystkie usługi działające w tym tomcacie mają jeden GlobalTracer, więc wszystkie będą miały tę samą nazwę usługi.
  • Dodając ślady do metody, musisz mieć pewność, że nie jest ona wywoływana wiele razy w pętli. Konieczne jest dodanie jednego wspólnego śladu dla wszystkich połączeń, co gwarantuje łączny czas pracy. W przeciwnym razie powstanie nadmierne obciążenie.
  • Raz w jaeger-ui złożono zbyt duże prośby o dużą liczbę śladów, a ponieważ nie czekali na odpowiedź, zrobili to ponownie. W rezultacie jaeger-query zaczął pochłaniać dużo pamięci i spowalniać program Elastic. Pomogło ponowne uruchomienie jaeger-query

Pobieranie próbek, przechowywanie i przeglądanie śladów

Istnieją trzy typy ślady pobierania próbek:

  1. Const, który wysyła i zapisuje wszystkie ślady.
  2. Probabilistyczny, który filtruje ślady z określonym prawdopodobieństwem.
  3. Ratelimiting, który ogranicza liczbę śladów na sekundę. Możesz skonfigurować te ustawienia na kliencie, na jaeger-agent lub na kolektorze. Teraz używamy const 1 w stosie valuatora, ponieważ nie ma zbyt wielu żądań, ale zajmują one dużo czasu. W przyszłości, jeśli będzie to nadmiernie obciążać system, można to ograniczyć.

Jeśli używasz Cassandry, to domyślnie przechowuje ślady tylko przez dwa dni. Używamy wyszukiwanie elastyczne a ślady są przechowywane przez cały czas i nie są usuwane. Dla każdego dnia tworzony jest osobny indeks, na przykład jaeger-service-2019-03-04. W przyszłości musisz skonfigurować automatyczne czyszczenie starych śladów.

Aby wyświetlić ślady, potrzebujesz:

  • Wybierz usługę, według której chcesz filtrować ślady, na przykład tomcat7-default dla usługi uruchomionej w tomcat i nie może mieć własnej nazwy.
  • Następnie wybierz operację, przedział czasu i minimalny czas operacji, na przykład od 10 sekund, aby wykonywać tylko długie wykonania.
    Śledzenie usług, OpenTracing i Jaeger
  • Podejdź do jednego ze śladów i zobacz, co tam zwalnia.
    Śledzenie usług, OpenTracing i Jaeger

Ponadto, jeśli znany jest jakiś identyfikator żądania, można znaleźć ślad według tego identyfikatora za pomocą wyszukiwania tagów, jeśli ten identyfikator jest zarejestrowany w zakresie śledzenia.

Dokumentacja

Artykuły

Wideo

Źródło: www.habr.com

Dodaj komentarz