Ответ
Трассировка (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)
Ответ 18+ 🔞
А, слушай, вот эта ваша трассировка распределённая... Это ж как если бы ты, блядь, пошёл в пять разных контор за справкой, а в каждой тебе на лоб штамп ставят, и потом по отпечаткам можно восстановить, какой же ты, сука, путь прошёл и где тебя больше всего ебали. Вот это и есть Trace — весь маршрут твоего запроса, от начала и до пиздеца.
А Span — это каждая отдельная остановка, где тебя конкретно имели. Зашёл в шлюз — один спан. Потом тебя авторизовали — второй спан. Создали заказ — третий. И так далее, пока запрос не сдохнет или не выполнится.
Вот, смотри, как это выглядит, если нарисовать:
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]
Видишь? Весь путь — это трейс. А каждый шаг, где сервис что-то делал — это спан. И главная фишка — Context Propagation, то есть как этот штамп traceId передаётся из одного сервиса в другой, чтобы все они знали, что они мучают один и тот же запрос.
Ну и как это, блядь, прикрутить?
Возьмём Spring Cloud Sleuth, он как раз этим и занимается. И Zipkin, куда все эти спаны слать. В конфиге пишешь:
# application.yml
spring:
sleuth:
sampler:
probability: 1.0 # Трассируем ВСЕ запросы, нахуй. На проде так не делай, а то логов будет овердохуища.
zipkin:
base-url: http://localhost:9411
sender:
type: web
А в коде Sleuth сам всё сделает, но если хочешь ручками поковыряться, то вот:
@RestController
@Slf4j
public class OrderController {
private final Tracer tracer; // Sleuth его сам подсунет
@PostMapping("/orders")
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
// Хочешь свой, кастомный спан? Пожалуйста!
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);
// Можно навесить теги, чтобы потом в Zipkin искать. Типа "а покажи-ка мне все заказы дороже тысячи рублей".
customSpan.tag("order.amount", request.getAmount().toString());
customSpan.tag("user.tier", request.getUserTier());
return ResponseEntity.ok(order);
} finally {
customSpan.end(); // Не забудь закрыть, а то память потечёт.
}
}
// А если асинхронно? С @Async Sleuth тоже умеет, контекст протащит.
@Async
public CompletableFuture<Void> asyncOperation() {
log.info("Async operation with traceId");
return CompletableFuture.completedFuture(null);
}
}
А если тебе надо контекст между потоками передать вручную, потому что твой Executor — говно и Sleuth его не знает?
// Лови текущий контекст
Span span = tracer.currentSpan();
TraceContext context = span.context();
String traceId = context.traceId(); // Вот он, родной!
// И тащи его в свой поток
TraceContext traceContext = tracer.currentTraceContext().context();
executor.submit(() -> {
try (TraceContext.Scope scope = tracer.currentTraceContext().newScope(traceContext)) {
// Всё, теперь этот поток в том же trace. Магия, ёпта.
log.info("Executing in thread with traceId: {}", traceId);
}
});
Интеграция с другими штуками? Да легко. Например, для HTTP-вызовов через RestTemplate Sleuth сам в заголовки X-B3-TraceId и X-B3-SpanId всё засунет. Для Kafka тоже есть специальные инжекторы, которые контекст в хедеры сообщения запихивают.
// Для Kafka Producer
@Bean
public ProducerFactory<String, String> producerFactory() {
// ... конфиг
return new DefaultKafkaProducerFactory<>(config,
new StringSerializer(),
new StringSerializer(),
new PropagatingSenderInterceptor<>() // Вот этот пацан trace контекст в headers засунет
);
}
И напоследок, блядь, советы, чтобы не обосраться:
- Сэмплинг: На проде не трассируй 100% запросов, ёбта. Выставь probability на 0.1 или 0.2. Хватит. Или используй adaptive sampling, который умный.
- Теги: Вешай бизнес-теги (
userId,orderId,errorCode). Потом в Zipkin сможешь искать все трейсы, где юзерVasyaкупил телевизор. Очень удобно для дебага. - Производительность: Отправку спанов в коллектор (Zipkin) делай асинхронной, чтобы твоё приложение не ждало, пока они улетят.
- Корреляция: Связывай трейсы с логами (через MDC) и метриками. Чтобы когда алерт сработал, ты мог сразу найти все логи по этому ебанному
traceId. - Алертинг: Настрой алерты на медленные трейсы. Следи за перцентилями p95, p99. Если p99 время выполнения заказа стало 5 секунд — пора, сука, копать, что там с платежкой случилось.
Вот и вся философия. Запрос идёт, как конь по полю, а ты за ним следишь, где он споткнулся, где его волки съели. Удобная хуйня, в общем.