Rastreamento de serviço, OpenTracing e Jaeger

Rastreamento de serviço, OpenTracing e Jaeger

Utilizamos arquitetura de microsserviços em nossos projetos. Quando ocorrem gargalos de desempenho, muito tempo é gasto monitorando e analisando logs. Ao registrar os tempos de operações individuais em um arquivo de log, geralmente é difícil entender o que levou à invocação dessas operações, rastrear a sequência de ações ou a mudança de tempo de uma operação em relação a outra em diferentes serviços.

Para minimizar o trabalho manual, decidimos usar uma das ferramentas de rastreamento. Sobre como e por que você pode usar o rastreamento e como o fizemos, isso será discutido neste artigo.

Quais problemas podem ser resolvidos com rastreamento

  1. Encontre gargalos de desempenho em um único serviço e em toda a árvore de execução entre todos os serviços participantes. Por exemplo:
    • Muitas chamadas consecutivas curtas entre serviços, por exemplo, para geocodificação ou para um banco de dados.
    • Longas esperas de E/S, como transferências de rede ou leituras de disco.
    • Longa análise de dados.
    • Operações longas que requerem CPU.
    • Seções de código que não são necessárias para obter o resultado final e podem ser removidas ou adiadas.
  2. Entenda claramente em que sequência o que é chamado e o que acontece quando a operação é executada.
    Rastreamento de serviço, OpenTracing e Jaeger
    Pode-se ver, por exemplo, que a Solicitação chegou ao serviço WS -> o serviço WS complementou os dados por meio do serviço R -> depois enviou uma solicitação ao serviço V -> o serviço V carregou muitos dados do serviço R -> foi para o serviço P -> o serviço P foi novamente para o serviço R -> serviço V ignorou o resultado e foi para o serviço J -> e só então retornou a resposta ao serviço WS, continuando a calcular outra coisa em o fundo.
    Sem esse rastreamento ou documentação detalhada de todo o processo, é muito difícil entender o que está acontecendo ao olhar o código pela primeira vez, e o código está espalhado por diferentes serviços e escondido atrás de um monte de caixas e interfaces.
  3. Coleta de informações sobre a árvore de execução para posterior análise diferida. Em cada estágio de execução, você pode adicionar informações ao rastreamento que está disponível neste estágio e descobrir quais dados de entrada levaram a um cenário semelhante. Por exemplo:
    • ID do usuário
    • Direitos
    • Tipo de método selecionado
    • Log ou erro de execução
  4. Transformando rastros em um subconjunto de métricas e análises posteriores já na forma de métricas.

Qual rastreamento pode registrar. Período

No rastreamento existe o conceito de span, este é um análogo de um log, para o console. O spa dispõe de:

  • Nome, geralmente o nome do método que foi executado
  • O nome do serviço no qual o span foi gerado
  • ID exclusivo próprio
  • Algum tipo de metainformação na forma de uma chave/valor que foi registrado nela. Por exemplo, parâmetros do método ou o método terminou com um erro ou não
  • Horas de início e término para este intervalo
  • ID do período pai

Cada span é enviado ao coletor de span para ser armazenado no banco de dados para posterior revisão assim que sua execução for concluída. No futuro, você pode construir uma árvore de todos os spans conectando-se por ID pai. Ao analisar, você pode encontrar, por exemplo, todos os vãos em algum serviço que levou mais tempo. Além disso, indo para um trecho específico, veja toda a árvore acima e abaixo desse trecho.

Rastreamento de serviço, OpenTracing e Jaeger

Opentrace, Jagger e como o implementamos em nossos projetos

Existe um padrão comum opentrace, que descreve como e o que deve ser coletado, sem estar vinculado ao rastreamento de uma implementação específica em qualquer idioma. Por exemplo, em Java, todo o trabalho com rastreamentos é realizado por meio da API Opentrace comum e, sob ela, por exemplo, Jaeger ou uma implementação padrão vazia que não faz nada pode ser ocultada.
Nós estamos usando Jaeger como uma implementação do Opentrace. É composto por vários componentes:

Rastreamento de serviço, OpenTracing e Jaeger

  • Jaeger-agent é um agente local que geralmente é instalado em cada máquina e os serviços são conectados a ele na porta padrão local. Se não houver agente, os rastreamentos de todos os serviços nesta máquina geralmente são desativados
  • Jaeger-coletor - todos os agentes enviam rastreamentos coletados para ele e os coloca no banco de dados selecionado
  • O banco de dados é o cassandra preferido deles, mas usamos elasticsearch, existem implementações para alguns outros bancos de dados e uma implementação na memória que não salva nada no disco
  • Jaeger-query é um serviço que vai ao banco de dados e retorna os traces já coletados para análise
  • Jaeger-ui é uma interface da web para pesquisar e visualizar rastreamentos, vai para jaeger-query

Rastreamento de serviço, OpenTracing e Jaeger

Um componente separado pode ser chamado de implementação do opentrace jaeger para idiomas específicos, através dos quais os spans são enviados para o jaeger-agent.
Conectando Jagger em Java resume-se à implementação da interface io.opentracing.Tracer, após a qual todos os rastreamentos através dela voarão para o agente real.

Rastreamento de serviço, OpenTracing e Jaeger

Também para o componente de mola, você pode conectar opentracing-spring-cloud-starter e implementação de Jaeger opentracing-spring-jaeger-cloud-starter que irá configurar automaticamente o rastreamento para tudo que passar por esses componentes, por exemplo requisições http aos controllers, requisições ao banco de dados via jdbc, etc.

Rastreia o login em Java

Em algum lugar no nível superior, o primeiro Span deve ser criado, isso pode ser feito automaticamente, por exemplo, pelo controlador de mola quando um pedido é recebido, ou manualmente se não houver nenhum. Em seguida, é transmitido através do Escopo abaixo. Se algum dos métodos abaixo quiser adicionar um Span, ele pega o activeSpan atual do Scope, cria um novo Span e diz que seu pai é o activeSpan resultante e torna o novo Span ativo. Ao chamar serviços externos, o span ativo atual é passado para eles e esses serviços criam novos spans com referência a esse span.
Todo o trabalho passa pela instância do Tracer, você pode obtê-lo por meio do mecanismo DI ou GlobalTracer.get () como uma variável global se o mecanismo DI não funcionar. Por padrão, se o rastreador não foi inicializado, o NoopTracer retornará, o que não faz nada.
Além disso, o escopo atual é obtido do rastreador por meio do ScopeManager, um novo escopo é criado a partir do atual com uma ligação do novo span e, em seguida, o escopo criado é fechado, o que fecha o span criado e retorna o escopo anterior para o estado ativo. O escopo está vinculado a um thread, portanto, ao programar multithread, você não deve esquecer de transferir o span ativo para outro thread, para posterior ativação do escopo de outro thread com referência a este 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)) {
        // 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)) {
                ...
            }
        });
    }
}

Para programação multiencadeada, também há TracedExecutorService e wrappers semelhantes que encaminham automaticamente o intervalo atual para o encadeamento quando as tarefas assíncronas são iniciadas:

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

Para solicitações http externas, há RastreamentoHttpClient

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

Problemas que enfrentamos

  • Beans e DI nem sempre funcionam se o rastreador não for usado em um serviço ou componente, então Com fio automático O Tracer pode não funcionar e você terá que usar GlobalTracer.get().
  • As anotações não funcionam se não for um componente ou serviço, ou se o método for chamado de um método vizinho da mesma classe. Você deve ter cuidado para verificar o que funciona e usar a criação de rastreamento manual se @Traced não funcionar. Você também pode anexar um compilador adicional para anotações java, então eles devem funcionar em qualquer lugar.
  • No antigo spring e spring boot, a autoconfiguração opentraing spring cloud não funciona devido a bugs no DI, então se você quiser que os traces nos componentes spring funcionem automaticamente, você pode fazer isso por analogia com 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
  • Tentar com recursos não funciona em groovy, você deve usar tente finalmente.
  • Cada serviço deve ter seu próprio spring.application.name sob o qual os rastreamentos serão registrados. O que faz um nome separado para a venda e o teste, para não interferir com eles juntos.
  • Se você usar GlobalTracer e tomcat, todos os serviços em execução neste tomcat terão um GlobalTracer, portanto, todos terão o mesmo nome de serviço.
  • Ao adicionar rastreamentos a um método, você precisa ter certeza de que ele não será chamado muitas vezes em um loop. É necessário adicionar um rastreamento comum para todas as chamadas, o que garante o tempo total de trabalho. Caso contrário, será criado um excesso de carga.
  • Uma vez em jaeger-ui, solicitações muito grandes foram feitas para um grande número de rastreamentos e, como eles não esperaram por uma resposta, eles o fizeram novamente. Como resultado, o jaeger-query começou a consumir muita memória e a desacelerar o elastic. Ajudado reiniciando jaeger-query

Amostragem, armazenamento e visualização de rastros

Existem três tipos traços de amostragem:

  1. Const que envia e salva todos os rastreamentos.
  2. Probabilístico que filtra os traços com alguma probabilidade dada.
  3. Ratelimiting que limita o número de traços por segundo. Você pode definir essas configurações no cliente, no jaeger-agent ou no coletor. Agora usamos const 1 na pilha do avaliador, pois não há muitas solicitações, mas demoram muito. No futuro, se isso exercer uma carga excessiva no sistema, você poderá limitá-lo.

Se você usar o cassandra, por padrão, ele armazena apenas os rastreamentos por dois dias. Nós estamos usando elasticsearch e os traços são armazenados para sempre e não são excluídos. Um índice separado é criado para cada dia, por exemplo jaeger-service-2019-03-04. No futuro, você precisará configurar a limpeza automática de rastros antigos.

Para visualizar os rastros você precisa:

  • Selecione o serviço pelo qual deseja filtrar os rastreios, por exemplo, tomcat7-default para um serviço que está em execução no tomcat e não pode ter seu próprio nome.
  • Em seguida, selecione a operação, o intervalo de tempo e o tempo mínimo de operação, por exemplo, de 10 segundos, para realizar apenas execuções longas.
    Rastreamento de serviço, OpenTracing e Jaeger
  • Vá até um dos rastros e veja o que estava desacelerando ali.
    Rastreamento de serviço, OpenTracing e Jaeger

Além disso, se algum ID de solicitação for conhecido, você poderá encontrar um rastreamento por esse ID por meio de uma pesquisa de tag, se esse ID estiver registrado no intervalo de rastreamento.

Documentação

Artigos

Vídeo

Fonte: habr.com

Adicionar um comentário