Как реализуется трассировка запросов в распределенных системах?

«Как реализуется трассировка запросов в распределенных системах?» — вопрос из категории DevOps, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Трассировка (Distributed Tracing) — это механизм отслеживания запроса через все сервисы распределенной системы. Ключевые концепции: Trace (весь путь запроса), Span (отдельная операция), Context Propagation (передача идентификаторов).

1. Основные компоненты трассировки:

Trace (traceId: abc123)
├── Span 1 (spanId: 1, service: Gateway, operation: auth) [50ms]
│   └── Span 2 (spanId: 2, service: AuthService, operation: validate) [30ms]
└── Span 3 (spanId: 3, service: OrderService, operation: create) [120ms]
    ├── Span 4 (spanId: 4, service: PaymentService, operation: charge) [80ms]
    └── Span 5 (spanId: 5, service: InventoryService, operation: reserve) [40ms]

2. Реализация с Spring Cloud Sleuth + Zipkin:

# application.yml
spring:
  sleuth:
    sampler:
      probability: 1.0 # Процент трассируемых запросов (1.0 = 100%)
  zipkin:
    base-url: http://localhost:9411
    sender:
      type: web # Отправка через HTTP
// Автоматическое создание span в контроллере
@RestController
@Slf4j
public class OrderController {

    private final Tracer tracer; // Инжектируется Sleuth

    @PostMapping("/orders")
    public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
        // Создание кастомного span
        Span customSpan = tracer.nextSpan().name("validateOrderRequest").start();
        try (SpanInScope ws = tracer.withSpan(customSpan)) {
            // Логи с traceId и spanId
            log.info("Validating order request for user: {}", request.getUserId());

            // Бизнес-логика
            Order order = orderService.create(request);

            // Добавление тегов для аналитики
            customSpan.tag("order.amount", request.getAmount().toString());
            customSpan.tag("user.tier", request.getUserTier());

            return ResponseEntity.ok(order);
        } finally {
            customSpan.end();
        }
    }

    // Асинхронная трассировка
    @Async
    public CompletableFuture<Void> asyncOperation() {
        // Sleuth автоматически передает контекст в @Async методы
        log.info("Async operation with traceId");
        return CompletableFuture.completedFuture(null);
    }
}

3. Контекст и propagation:

// Ручная передача контекста между потоками
Span span = tracer.currentSpan();
TraceContext context = span.context();
String traceId = context.traceId();
String spanId = context.spanId();

// Для передачи вручную (например, в кастомный executor)
TraceContext traceContext = tracer.currentTraceContext().context();
executor.submit(() -> {
    try (TraceContext.Scope scope = tracer.currentTraceContext().newScope(traceContext)) {
        // Код с унаследованным контекстом трассировки
        log.info("Executing in thread with traceId: {}", traceId);
    }
});

4. Интеграция с другими инструментами:

// Добавление traceId в HTTP-заголовки для внешних вызовов
@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();

    // Sleuth автоматически добавляет заголовки X-B3-TraceId, X-B3-SpanId
    restTemplate.setInterceptors(Collections.singletonList(
        new RestTemplateInterceptor()
    ));

    return restTemplate;
}

// Интеграция с Messaging (Kafka)
@Bean
public ProducerFactory<String, String> producerFactory() {
    Map<String, Object> config = new HashMap<>();
    config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");

    // Добавление инжектора контекста для Kafka
    return new DefaultKafkaProducerFactory<>(config, 
        new StringSerializer(), 
        new StringSerializer(),
        new PropagatingSenderInterceptor<>() // Передает trace контекст в headers
    );
}

5. Best Practices:

  • Сэмплинг: На production использовать adaptive sampling (например, 10-20% запросов)
  • Теги: Добавлять business-теги (userId, orderId, endpoint) для поиска
  • Производительность: Использовать асинхронную отправку spans в collector
  • Корреляция: Связывать трассировку с логами через MDC и метриками
  • Алертинг: Настраивать алерты на медленные traces (по percentiles: p95, p99)