Estratégias de tracing distribuído com Jaeger e Zipkin
1. Fundamentos do Tracing Distribuído em Arquiteturas Modernas
1.1. Conceitos-chave: spans, traces, context propagation e baggage
O tracing distribuído é uma técnica essencial para monitorar requisições que atravessam múltiplos serviços em arquiteturas de microserviços. Os conceitos fundamentais incluem:
- Span: unidade básica de trabalho que representa uma operação específica dentro de um serviço, contendo nome, tempo de início, duração, tags e logs.
- Trace: conjunto hierárquico de spans que representa o caminho completo de uma requisição através do sistema.
- Context Propagation: mecanismo que transporta identificadores de trace entre serviços, geralmente via headers HTTP ou metadados de mensageria.
- Baggage: pares chave-valor propagados ao longo de todo o trace, úteis para transportar informações contextuais como ID de usuário ou versão de deploy.
1.2. Desafios de microserviços: latência, dependências e debugging distribuído
Em sistemas distribuídos, identificar a causa raiz de lentidão ou falhas torna-se complexo. Os principais desafios incluem:
- Latência cumulativa: cada serviço adiciona overhead de rede e processamento.
- Dependências ocultas: chamadas indiretas entre serviços podem criar gargalos inesperados.
- Debugging distribuído: logs centralizados não mostram a relação temporal entre operações em diferentes serviços.
1.3. Por que Jaeger e Zipkin são referências open source no mercado
Ambas as ferramentas seguem o modelo de dados do OpenTracing (hoje OpenTelemetry) e oferecem:
- Visualização de traces em formato de waterfall charts.
- Análise de dependências entre serviços.
- Suporte a múltiplos backends de armazenamento (Elasticsearch, Cassandra, etc.).
- Integração nativa com Kubernetes, Prometheus e outras ferramentas de observabilidade.
2. Arquitetura e Componentes do Jaeger
2.1. Agente, coletor, consulta e armazenamento: fluxo de dados end-to-end
O Jaeger possui quatro componentes principais:
[Serviço Instrumentado] → [Jaeger Agent] → [Jaeger Collector] → [Storage Backend]
↓
[Jaeger Query] → [UI]
- Jaeger Agent: sidecar que recebe spans via UDP e os envia para o coletor.
- Jaeger Collector: valida, indexa e armazena spans.
- Jaeger Query: API REST/GraphQL para consulta de traces.
- Storage Backend: Elasticsearch, Cassandra ou Kafka (para streaming).
2.2. Estratégias de amostragem: probabilística, rate-limiting e amostragem remota
O Jaeger oferece três estratégias de amostragem configuráveis:
# Amostragem probabilística (ex: 10% dos traces)
sampler.type = probabilistic
sampler.param = 0.1
# Amostragem rate-limiting (ex: 100 spans por segundo)
sampler.type = ratelimiting
sampler.param = 100
# Amostragem remota (via servidor de amostragem)
sampler.type = remote
sampler.param = {
"sampling_server_url": "http://jaeger-agent:5778/sampling"
}
2.3. Integração com OpenTelemetry e suporte a múltiplos backends de storage
O Jaeger é compatível com o OpenTelemetry SDK, permitindo instrumentação padronizada:
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
JaegerGrpcSpanExporter exporter = JaegerGrpcSpanExporter.builder()
.setEndpoint("http://localhost:14250")
.build();
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(SimpleSpanProcessor.create(exporter))
.build();
3. Arquitetura e Componentes do Zipkin
3.1. Coleta via HTTP, Kafka ou Scribe: agentes e transportes suportados
O Zipkin suporta múltiplos transportes para coleta de spans:
# Coleta via HTTP (padrão)
POST /api/v2/spans HTTP/1.1
Host: zipkin:9411
Content-Type: application/json
[{"id":"abc123","traceId":"trace123","name":"get-user","timestamp":123456789,"duration":15000}]
# Coleta via Kafka
zipkin.collector.kafka.bootstrap-servers=localhost:9092
zipkin.collector.kafka.topic=zipkin
3.2. Modelo de dados: span model, trace tree e dependências entre serviços
O modelo de dados do Zipkin é JSON-based:
{
"traceId": "abcef1234567890",
"id": "span123",
"parentId": "span000",
"name": "http-get",
"timestamp": 1234567890000,
"duration": 15000,
"tags": {
"http.method": "GET",
"http.status_code": "200"
}
}
3.3. Zipkin UI: busca por traces, análise de latência e gráfico de dependências
A interface do Zipkin permite:
- Busca por traces usando filtros como serviço, span, tags e duração mínima.
- Waterfall chart mostrando a sequência temporal de spans.
- Gráfico de dependências gerado automaticamente a partir dos dados coletados.
4. Estratégias de Instrumentação para Aplicações
4.1. Instrumentação automática vs manual: quando usar cada abordagem
- Automática: ideal para frameworks web (Spring Boot, Express.js, Django) e bibliotecas HTTP/gRPC. Usa agentes ou middlewares que interceptam chamadas automaticamente.
- Manual: necessária para operações de negócio específicas, como processamento de filas ou tarefas agendadas.
4.2. Propagação de contexto em chamadas síncronas (HTTP/gRPC) e assíncronas (mensageria)
Para propagação de contexto em chamadas HTTP:
// Extrair contexto do header
String traceId = request.getHeader("uber-trace-id");
SpanContext parentContext = JaegerSpanContext.fromString(traceId);
// Criar span filho
Span span = tracer.buildSpan("process-request")
.asChildOf(parentContext)
.start();
Para mensageria com Kafka:
// Producer: injetar contexto no header da mensagem
Headers headers = record.headers();
TextMapInjector injector = new TextMapInjectorAdapter(headers);
tracer.inject(span.context(), Format.Builtin.TEXT_MAP, injector);
// Consumer: extrair contexto
TextMapExtractor extractor = new TextMapExtractorAdapter(headers);
SpanContext parentContext = tracer.extract(Format.Builtin.TEXT_MAP, extractor);
4.3. Melhores práticas para nomear spans e adicionar tags relevantes
// Boas práticas de nomenclatura
span.setOperationName("POST /api/users"); // Método + caminho
span.setTag("user.id", userId); // Identificador de negócio
span.setTag("error", true); // Indicador de erro
span.setTag("http.status_code", 500); // Código HTTP
5. Comparação Jaeger vs Zipkin: Casos de Uso e Trade-offs
5.1. Performance e escalabilidade: overhead de coleta e armazenamento
| Característica | Jaeger | Zipkin |
|---|---|---|
| Overhead de coleta | ~5-10μs por span | ~3-8μs por span |
| Armazenamento padrão | Elasticsearch | Cassandra |
| Escalabilidade horizontal | Suporte nativo via Kafka | Suporte via Kafka |
5.2. Recursos de análise: busca avançada, agregação e filtros
- Jaeger: busca por tags, duração e serviço; agregação de métricas (latência p95, p99).
- Zipkin: busca básica por serviço e span; filtros por tags e anotações.
5.3. Ecossistema e integrações: Kubernetes, Prometheus e ferramentas de observabilidade
Ambos integram-se com:
- Kubernetes: sidecar injection para coleta automática.
- Prometheus: exposição de métricas de coleta.
- Grafana: dashboards de observabilidade.
6. Boas Práticas de Implantação e Operação
6.1. Configuração de amostragem para balancear custo e visibilidade
Para ambientes de produção:
# Amostragem adaptativa
sampler.type = remote
sampler.param = {
"sampling_server_url": "http://jaeger-agent:5778/sampling",
"max_traces_per_second": 50
}
# Amostragem baseada em prioridade
sampler.type = probabilistic
sampler.param = 0.01 # 1% para requisições normais
# 100% para requisições com header "X-Debug: true"
6.2. Estratégias de retenção de dados e tuning de storage
# Elasticsearch ILM para retenção
PUT _ilm/policy/tracing_policy
{
"policy": {
"phases": {
"hot": {"min_age": "0ms", "actions": {"rollover": {"max_size": "50GB"}}},
"delete": {"min_age": "30d", "actions": {"delete": {}}}
}
}
}
6.3. Monitoramento da própria infraestrutura de tracing
# Métricas essenciais para monitorar
- jaeger_collector_spans_received_total
- jaeger_collector_spans_dropped_total
- zipkin_collector_spans_received
- zipkin_collector_spans_dropped
7. Exemplos Práticos de Análise de Traces
7.1. Identificando gargalos de latência em uma cadeia de chamadas
Trace ID: abc123
Span A (API Gateway): 500ms
Span B (Auth Service): 200ms
Span C (Database Query): 150ms ← Gargalo identificado
Span D (User Service): 300ms
Span E (Cache Check): 250ms ← Outro gargalo
7.2. Rastreamento de erros e exceções distribuídas com baggage
// Adicionar baggage para rastreamento de erros
tracer.activeSpan().setBaggageItem("error.source", "payment-service");
tracer.activeSpan().setBaggageItem("error.code", "ECONNREFUSED");
// Recuperar em serviços downstream
String errorSource = tracer.activeSpan().getBaggageItem("error.source");
7.3. Uso de trace analytics para otimizar performance de serviços críticos
// Query Jaeger para identificar traces lentos
curl -X GET "http://jaeger-query:16686/api/traces?service=payment-service&minDuration=1000ms&limit=100"
// Resultado: análise de latência p95
{
"data": [
{
"traceID": "abc123",
"spans": [
{"operationName": "process-payment", "duration": 2500},
{"operationName": "charge-card", "duration": 2000}
]
}
]
}
Referências
- Documentação Oficial do Jaeger — Guia completo de instalação, configuração e operação do Jaeger.
- Documentação Oficial do Zipkin — Arquitetura, componentes e guia de implantação do Zipkin.
- OpenTelemetry Tracing Specification — Conceitos fundamentais de tracing distribuído segundo o padrão OpenTelemetry.
- Tutorial de Tracing Distribuído com Jaeger e Kubernetes — Guia prático de implantação do Jaeger em clusters Kubernetes.
- Comparativo Jaeger vs Zipkin: Qual Escolher? — Análise detalhada das diferenças entre as duas ferramentas para diferentes cenários.
- Boas Práticas de Amostragem em Tracing Distribuído — Estratégias avançadas de amostragem para balancear custo e visibilidade.
- Monitoramento de Microserviços com Jaeger e Prometheus — Integração entre Jaeger e Prometheus para métricas de observabilidade.