← Назад к вопросам

Как происходит трассировка запроса

1.7 Middle🔥 201 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Трассировка запроса (Request Tracing) в микросервисной архитектуре

Трассировка запроса — это механизм для отслеживания пути запроса через различные микросервисы и компоненты системы. Это критично для отладки, мониторинга и анализа производительности в распределённых системах.

1. Основная концепция: Trace ID и Span ID

// Trace ID — уникальный идентификатор для всего запроса
// Span ID — уникальный идентификатор для отдельной операции

// Пример пути запроса:
// Client → Service A → Service B → Service C → Database
// 
// TraceId: abc-123-def (одинаков для всего пути)
// SpanIds: 
//   Service A: span-1
//   Service B: span-2
//   Service C: span-3
//   Database: span-4

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

2.1 Подключение зависимостей

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

<!-- Для интеграции с Zipkin (опционально) -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>

2.2 Конфигурация

# application.yml
spring:
  application:
    name: user-service
  sleuth:
    sampler:
      probability: 1.0  # 100% вероятность отслеживания (для dev)
    baggage:
      correlation-enabled: true
  zipkin:
    base-url: http://localhost:9411  # адрес Zipkin сервера

logging:
  pattern:
    level: "%5p [${spring.application.name},%X{traceId},%X{spanId}]"

3. Автоматическое трассирование

import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;

@RestController
@RequestMapping("/api/v1/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping("/{userId}")
    public User getUser(@PathVariable Long userId) {
        // Spring Sleuth автоматически добавляет TraceId и SpanId
        // в MDC (Mapped Diagnostic Context) для логирования
        
        System.out.println("Getting user: " + userId);
        // Лог: [INFO] [user-service,abc-123-def,span-1] Getting user: 1
        
        return userService.findById(userId);
    }
}

@Service
public class UserService {
    
    @Autowired
    private UserRepository repository;
    
    @Autowired
    private OrderServiceClient orderClient;
    
    public User findById(Long userId) {
        // Trace ID остаётся одинаковым, но Span ID меняется
        System.out.println("Finding user in DB");
        // Лог: [INFO] [user-service,abc-123-def,span-2] Finding user in DB
        
        User user = repository.findById(userId);
        
        // Вызов другого сервиса
        List<Order> orders = orderClient.getOrdersByUserId(userId);
        user.setOrders(orders);
        
        return user;
    }
}

4. Пользовательские Spans

import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.Span;

@Service
public class ComplexService {
    
    @Autowired
    private Tracer tracer;
    
    public void processComplexOperation() {
        // Создание пользовательского span
        Span span = tracer.nextSpan().name("custom-operation");
        
        try (Tracer.SpanInScope ws = tracer.withSpan(span.start())) {
            // Код выполняется внутри созданного span
            step1();
            step2();
            step3();
        } finally {
            span.end();
        }
    }
    
    private void step1() {
        Span span = tracer.nextSpan().name("step-1");
        try (Tracer.SpanInScope ws = tracer.withSpan(span.start())) {
            System.out.println("Step 1");
        } finally {
            span.end();
        }
    }
    
    private void step2() {
        Span span = tracer.nextSpan().name("step-2");
        try (Tracer.SpanInScope ws = tracer.withSpan(span.start())) {
            System.out.println("Step 2");
        } finally {
            span.end();
        }
    }
    
    private void step3() {
        Span span = tracer.nextSpan().name("step-3");
        try (Tracer.SpanInScope ws = tracer.withSpan(span.start())) {
            System.out.println("Step 3");
        } finally {
            span.end();
        }
    }
}

5. Трассирование асинхронных операций

import java.util.concurrent.CompletableFuture;
import org.springframework.scheduling.annotation.Async;

@Service
public class AsyncTracingService {
    
    @Autowired
    private Tracer tracer;
    
    @Async
    public CompletableFuture<String> asyncOperation(String data) {
        // Trace ID может потеряться в async контексте
        // Решение: явно передаём span контекст
        
        Span span = tracer.currentSpan();
        String traceId = span.context().traceId();
        
        return CompletableFuture.supplyAsync(() -> {
            try (Tracer.SpanInScope ws = tracer.withSpan(tracer.nextSpan())) {
                // Работаем с сохранённым контекстом
                System.out.println("Processing: " + data);
                return "Result for " + data;
            }
        });
    }
}

// Конфигурация для async
@Configuration
public class AsyncConfig {
    
    @Bean
    public Executor taskExecutor(
            org.springframework.cloud.sleuth.instrument.async.LazyTraceExecutor lazyTraceExecutor) {
        return lazyTraceExecutor;
    }
}

6. Передача Trace Context между сервисами

// Когда Service A вызывает Service B, нужно передать TraceId

@Service
public class ServiceAClient {
    
    @Autowired
    private RestTemplate restTemplate;  // автоматически инструментирован Sleuth
    
    public User callServiceB(Long userId) {
        // Sleuth автоматически добавит заголовки:
        // X-B3-TraceId, X-B3-SpanId, X-B3-ParentSpanId
        
        String url = "http://service-b:8080/api/v1/users/" + userId;
        
        return restTemplate.getForObject(url, User.class);
    }
}

// Service B получит те же заголовки и продолжит трассировку
@RestController
@RequestMapping("/api/v1/users")
public class ServiceBController {
    
    @GetMapping("/{userId}")
    public User getUser(@PathVariable Long userId) {
        // Trace ID будет таким же, как и в Service A
        System.out.println("Service B processing user: " + userId);
        // Лог: [INFO] [service-b,abc-123-def,span-3] Service B processing user: 1
        
        return new User(userId);
    }
}

7. Zipkin для визуализации трассировок

Запуск Zipkin

# Docker
docker run -d -p 9411:9411 openzipkin/zipkin

# или на локальной машине
download и запустить zipkin.jar
java -jar zipkin.jar

Просмотр трассировок

// Запрос пройдёт через несколько сервисов
GET /api/v1/users/1

// В Zipkin (http://localhost:9411) можно увидеть:
// Trace ID: abc-123-def
// ├── user-service: 50ms (span-1)
// │   ├── database: 20ms (span-2)
// │   └── order-service: 25ms (span-3)
// │       ├── cache: 5ms (span-4)
// │       └── database: 15ms (span-5)

8. Логирование с TraceId

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

@Service
public class LoggingService {
    
    private static final Logger logger = LoggerFactory.getLogger(LoggingService.class);
    
    @Autowired
    private Tracer tracer;
    
    public void logWithTrace(String message) {
        String traceId = tracer.currentSpan().context().traceId();
        String spanId = tracer.currentSpan().context().spanId();
        
        // Способ 1: явно добавить в лог
        logger.info("[{}-{}] {}", traceId, spanId, message);
        
        // Способ 2: через MDC (Mapped Diagnostic Context)
        MDC.put("traceId", traceId);
        MDC.put("spanId", spanId);
        logger.info(message);
        MDC.remove("traceId");
        MDC.remove("spanId");
    }
}

// logback.xml для форматирования
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - [%X{traceId}-%X{spanId}] %msg%n
            </pattern>
        </encoder>
    </appender>
    <root level="info">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

9. Трассирование в Kubernetes

# deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
spec:
  template:
    spec:
      containers:
      - name: user-service
        image: user-service:1.0
        env:
        - name: JAEGER_AGENT_HOST
          value: jaeger-agent
        - name: JAEGER_AGENT_PORT
          value: "6831"
        - name: JAEGER_SAMPLER_TYPE
          value: const
        - name: JAEGER_SAMPLER_PARAM
          value: "1"

10. OpenTelemetry (современный подход)

<!-- pom.xml -->
<dependency>
    <groupId>io.opentelemetry.instrumentation</groupId>
    <artifactId>opentelemetry-instrumentation-bom</artifactId>
    <version>1.27.0</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.Span;

@Service
public class OpenTelemetryService {
    
    @Autowired
    private Tracer tracer;
    
    public void traceOperation() {
        Span span = tracer.spanBuilder("my-operation")
            .startSpan();
        
        try (var scope = span.makeCurrent()) {
            // Операция с трассировкой
            System.out.println("Operation in progress");
        } finally {
            span.end();
        }
    }
}

Ключевые концепции

Trace — полный путь запроса через систему Span — отдельная операция в цепи TraceId — уникальный идентификатор trace SpanId — уникальный идентификатор span ParentSpanId — ссылка на родительский span

Лучшие практики

  1. Всегда логируйте TraceId для корреляции
  2. Используйте Sleuth/OpenTelemetry для автоматического трассирования
  3. Называйте spans понятно для быстрой идентификации
  4. Настраивайте sampling для снижения overhead
  5. Интегрируйте с Zipkin/Jaeger для визуализации
  6. Мониторьте latency по каждому сервису
  7. Сохраняйте контекст в async операциях

Профессиональное трассирование запросов — это обязательный инструмент для отладки и оптимизации микросервисных систем.

Как происходит трассировка запроса | PrepBro