Трасіроўка сэрвісаў, OpenTracing і Jaeger

Трасіроўка сэрвісаў, OpenTracing і Jaeger

У нашых праектах мы выкарыстоўваем мікрасэрвісную архітэктуру. Пры ўзнікненні вузкіх месцаў у прадукцыйнасці дастаткова шмат часу траціцца на маніторынг і разбор логаў. Пры лагаванні таймінгаў асобных аперацый у лог-файл, як правіла, складана зразумець што прывяло да выкліку гэтых аперацый, адсачыць паслядоўнасць дзеянняў або зрушэнне ў часе адной аперацыі адносна іншай у розных сэрвісах.

Для мінімізацыі ручной працы мы вырашылі скарыстацца адной з прылад трасіроўкі. Аб тым, як і для чаго можна выкарыстоўваць трасіроўку і як гэта рабілі мы, і пайдзе прамову ў гэтым артыкуле.

Якія праблемы можна вырашыць з дапамогай трасіроўкі

  1. Знайсці вузкія месцы ў прадукцыйнасці як усярэдзіне аднаго сэрвісу, так і ва ўсім дрэве выканання паміж усімі якія ўдзельнічаюць сэрвісамі. Напрыклад:
    • Шмат кароткіх паслядоўных выклікаў паміж сэрвісамі, напрыклад, на геакодынг ці да базы дадзеных.
    • Доўгія чаканні ўводу высновы, напрыклад, перадача даных па сетцы або чытанне з дыска.
    • Доўгі парсінг даных.
    • Доўгія аперацыі, якія патрабуюць CPU.
    • Участкі кода, якія не патрэбныя для атрымання канчатковага выніку і могуць быць выдалены, альбо запушчаны адкладзена.
  2. Наглядна зразумець у якой паслядоўнасці што выклікаецца і што адбываецца, калі выконваецца аперацыя.
    Трасіроўка сэрвісаў, OpenTracing і Jaeger
    Відаць што, напрыклад, Запыт прыйшоў у сэрвіс WS -> сэрвіс WS дапоўніў дадзеныя праз сэрвіс R -> далей адправіў запыт у сэрвіс V -> сэрвіс V загрузіў шмат дадзеных з сэрвісу R -> схадзіў у сэрвіс P -> сэрвіс Р яшчэ раз схадзіў у сэрвіс R -> сэрвіс V праігнараваў вынік і пайшоў у сэрвіс J -> і толькі потым вярнуў адказ у сэрвіс WS, пры гэтым працягваючы ў фоне вылічаць нешта яшчэ.
    Без такога трэйса або падрабязнай дакументацыі на ўвесь працэс вельмі складана зразумець, што адбываецца, першы раз зірнуўшы на код, ды і код раскіданы па розных сэрвісах і ўтоены за кучай біноў і інтэрфейсаў.
  3. Збор інфармацыі аб дрэве выканання для наступнага адкладзенага аналізу. На кожным этапе выканання ў трэйс можна дадаць інфармацыю, якая даступна на дадзеным этапе і далей разабрацца, якія ўваходныя дадзеныя прывялі да падобнага сцэнара. Напрыклад:
    • ID карыстальніка
    • Правы
    • Тып абранага метаду
    • Лог ці памылка выканання
  4. Ператварэнне трэйсаў у падмноства метрык і далейшы аналіз ужо ў выглядзе метрык.

Што ўмее лагіраваць трасіроўка. Span

У трасіроўцы ёсць паняцце спан, гэта аналаг аднаго лога, у кансоль. У спана ёсць:

  • Назва, звычайна гэта назва метаду які выконваўся
  • Назва сэрвісу, у якім быў згенераваны спан
  • Уласны унікальны ID
  • Нейкая мэта інфармацыя ў выглядзе key/value, якую залагавалі ў яго. Напрыклад, параметры метаду ці скончыўся метад памылкай ці не
  • Час пачатку і канца выканання гэтага спану
  • ID бацькоўскага спану

Кожны спан адпраўляецца ў collector спанаў для захавання ў базу для наступнага прагляду як толькі ён завяршыў сваё выкананне. У далейшым можна пабудаваць дрэва ўсіх спанаў злучаючы па id з бацькоў. Пры аналізе можна знайсці, напрыклад, усе спаны ў нейкім сэрвісе, якія занялі больш нейкага часу. Далей, перайшоўшы на пэўны спан, убачыць усё дрэва вышэй і ніжэй гэтага спану.

Трасіроўка сэрвісаў, OpenTracing і Jaeger

Opentrace, Jagger і як мы рэалізавалі гэта для сваіх праектаў

Ёсць агульны стандарт Opentrace, які апісвае як і што павінна збірацца, не прывязваючыся трасіроўкай да канкрэтнай рэалізацыі ў якой-небудзь мове. Напрыклад, у Java уся праца з трэйсамі вядзецца праз агульны API Opentrace, а пад ім можа хавацца, напрыклад, Jaeger ці пустая дэфолтная рэалізацыя якая нічога не робіць.
У нас выкарыстоўваецца Егер як імпліментацыя Opentrace. Ён складаецца з некалькіх кампанентаў:

Трасіроўка сэрвісаў, OpenTracing і Jaeger

  • Jaeger-agent – ​​лакальны агент, які звычайна стаіць на кожнай машыне і ў яго лагіруюць сэрвісы на лакальны дэфолтны порт. Калі агента няма, то трэйсы ўсіх сэрвісаў на гэтай машыне звычайна выключаны.
  • Jaeger-collector - у яго ўсе агенты пасылаюць сабраныя трэйсы, а ён кладзе іх у абраную БД
  • База дадзеных - пераважная ў іх cassandra, але ў нас выкарыстоўваецца elasticsearch, ёсць рэалізацыі яшчэ пад пару іншых бд і in memory рэалізацыя, якая нічога не захоўвае на дыск
  • Jaeger-query - гэта сэрвіс які ходзіць у базу дадзеных і аддае ўжо сабраныя трэйсы для аналізу
  • Jaeger-ui - гэта вэб інтэрфейс для пошуку і праглядаў трэйсаў, ён ходзіць у jaeger-query

Трасіроўка сэрвісаў, OpenTracing і Jaeger

Асобным кампанентам можна назваць рэалізацыю opentrace jaeger пад пэўныя мовы, праз якую спаны адпраўляюцца ў jaeger-agent.
Падключэнне Jagger у Java зводзіцца да таго, каб заімпліментаваць інтэрфейс io.opentracing.Tracer, пасля чаго ўсе трэйсы праз яго будуць ляцець у рэальны агент.

Трасіроўка сэрвісаў, OpenTracing і Jaeger

Гэтак жа для кампанент спрынгу можна падлучыць opentracing-spring-cloud-starter і імплементацыю ад Jaeger opentracing-spring-jaeger-cloud-starter якая заканфігуруе аўтаматычна трасіроўку на ўсё што праходзіць праз гэтыя кампаненты, напрыклад http запыты ў кантролеры, запыты да БД праз jdbc і г.д.

Лагіраванне трэйсаў у Java

Дзесьці на самым верхнім узроўні павінен быць створаны першы Span, гэта можа быць зроблена аўтаматычна напрыклад кантролерам спрынгу пры атрыманні запыту, альбо ўручную калі такога няма. Далей ён перадаецца праз Scope ніжэй. Калі нейкі метад ніжэй жадае дадаць Span, ён бярэ з Scope бягучы activeSpan, стварае новы Span і кажа што яго бацькоўскі атрыманы activeSpan, і робіць новы Span active. Пры выкліку вонкавых сэрвісаў ім перадаецца бягучы актыўны спан, і тыя сэрвісы ствараюць новыя спаны з прывязкай да гэтага спану.
Уся праца ідзе праз інстанс Tracer, атрымаць яго можна праз механізм DI, альбо GlobalTracer.get() як глабальную зменную, калі механізм DI не працуе. Па дэфолце калі tracer не быў праініцыялізаваны вернецца NoopTracer які нічога не робіць.
Далей з tracer праз ScopeManager дастаецца бягучы scope, ствараецца новы scope ад бягучага з прывязкай новага спана, а ў далейшым зачыняецца створаны Scope, які закрывае створаны спан і вяртае ў актыўны стан мінулы Scope. Scope прывязаны да струменя, таму пры шматструменным праграмаванні трэба не забываць перадаваць актыўны спан у іншы струмень, для далейшай актывацыі Scope іншага струменя з прывязкай да гэтага спану.

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

Для шматструменнага праграмавання гэтак жа ёсць TracedExecutorService і аналагічныя абгорткі, якія аўтаматычна пракідваюць бягучы спан у струмень пры запуску асінхронна цягі:

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

Для вонкавых http запытаў ёсць TracingHttpClient

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

Праблемы, з якімі мы сутыкнуліся

  • Біны і DI не заўсёды працуе калі tracer выкарыстоўваецца не ў сэрвісе ці кампаненце, тады Autowired Tracer можа не працаваць і давядзецца выкарыстоўваць GlobalTracer.get().
  • Ці не працуюць анатацыі калі гэта не кампанент або сэрвіс, або калі выклік метаду адбываецца з суседняга метаду таго ж класа. Трэба быць акуратным, правяраць што працуе, і выкарыстоўваць ручное стварэнне трэйса калі @Traced не працуе. Гэтак жа можна прыкруціць дадатковы кампайлер для java анатацый, тады павінны працаваць усюды.
  • У старых spring і spring boot не працуе аўтаканфігурацыя opentraing spring cloud з-за багаў у DI, тады калі жадаецца каб працавалі аўтаматычна трэйсы ў кампанентах спрынгу можна зрабіць па аналогіі з 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 не працуе try with resources, трэба абавязкова выкарыстоўваць try finally.
  • У кожнага сэрвісу трэба прапісваць свой spring.application.name пад якім будуць лагіравацца трэйсы. Пры чым асобны name для прода і цеста, каб не перашкаджаць іх разам.
  • Калі выкарыстоўваць GlobalTracer і tomcat, то ўсе сэрвісы запушчаныя ў гэтым tomcat маюць адзін GlobalTracer, таму ў іх будзе на ўсіх адно імя сэрвісу.
  • Пры даданні трэйсаў у метад, трэба быць упэўненым што ён не выклікаецца ў цыкле шмат разоў. Трэба дадаць адзін агульны трэйс на ўсе выклікі, які залагуе сумарны час працы. Інакш будзе стварацца залішняя нагрузка.
  • Адзін раз у jaeger-ui рабілі занадта вялікія запыты на вялікую колькасць трэйсаў і бо не чакалі адказу рабілі яшчэ раз. У выніку jaeger-query стаў есці шмат памяці і тармазіць эластык. Дапамагло рэстартам jaeger-query

Сэмпляванне, захоўванне і прагляд трэйсаў

Ёсць тры тыпы сэмплявання трэйсаў:

  1. Const які адпраўляе і захоўвае ўсе трэйсы.
  2. Probabilistic які фільтруе трэйсы з нейкай зададзенай верагоднасцю.
  3. Ratelimiting які абмяжоўвае колькасць трэйсаў у секунду. Можна наладзіць гэтыя параметры на кліенце, альбо на jaeger-agent альбо ў калектары. Цяпер у нас у стэку валюатараў выкарыстоўваецца const 1 бо запытаў не вельмі шмат але яны займаюць працяглы час. У далейшым калі гэта будзе аказваць залішнюю нагрузку на сістэму можна абмежаваць.

Калі выкарыстоўваць cassandra то па дэфолце яна захоўвае трэйсы толькі за два дні. У нас выкарыстоўваецца эластычны пошук і трэйсы захоўваюцца за ўвесь час і не выдаляюцца. На кожны дзень ствараецца асобны індэкс, напрыклад, jaeger-service-2019-03-04. У далейшым трэба наладзіць аўтаматычную падчыстку старых трэйсаў.

Для таго, каб паглядзець трэйсы трэба:

  • Выбраць сэрвіс па якім жадаецца пафільтраваць трэйсы, напрыклад tomcat7-default для сэрвісу, які запушчаны ў тамкаце і не можа мець свайго імя.
  • Далей абраць аперацыю, часавы прамежак і мінімальны час аперацыі, напрыклад ад 10 секунд, каб узяць толькі доўгія выкананні.
    Трасіроўка сэрвісаў, OpenTracing і Jaeger
  • Перайсці ў адзін з трэйсаў і глядзець што там тармазіла.
    Трасіроўка сэрвісаў, OpenTracing і Jaeger

Гэтак жа калі вядомы нейкі id запыту, то можна знайсці трэйс па гэтым id праз пошук па тэгах, калі гэты id лагуецца ў спан трэйса.

Дакументацыя

артыкула

Відэа

Крыніца: habr.com

Дадаць каментар