Комментарии (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
Лучшие практики
- Всегда логируйте TraceId для корреляции
- Используйте Sleuth/OpenTelemetry для автоматического трассирования
- Называйте spans понятно для быстрой идентификации
- Настраивайте sampling для снижения overhead
- Интегрируйте с Zipkin/Jaeger для визуализации
- Мониторьте latency по каждому сервису
- Сохраняйте контекст в async операциях
Профессиональное трассирование запросов — это обязательный инструмент для отладки и оптимизации микросервисных систем.