
Распределенная трассировка
Отслеживание — самая сложная часть микросервисов. Решением для нее является распределенная трассировка, благодаря которой разработчики мониторят и визуализируют протекание запросов по сложным распределенным системам.
Что такое OpenTelemetry?
Это универсальный стандарт, которым предоставляются метрики, журналы, трассировки в распределенных приложениях и обеспечивается комплексная наблюдаемость без привязки к конкретной платформе мониторинга. OpenTelemetry также предоставляются:
- Автоматическое и ручное инструментирование кода.
- Совместимость со многими серверными системами.
Настройка приложения Spring Boot 3 с OpenTelemetry
Сконфигурируем OpenTelemetry для приложения Gradle. Ниже приведены внесенные изменения.
Структура проекта
Создадим два микросервиса:
- Служба заказов с портом 8084: ею обрабатываются операции над заказами и вызывается служба ценообразования.
- Служба ценообразования с портом 8083: ею предоставляется информация о ценах.
opentelemetry-spring-boot/
├── order-service/
│ ├── src/
│ └── build.gradle
├── price-service/
│ ├── src/
│ └── build.gradle
├── build.gradle
├── settings.gradle
├── docker-compose.yml
└── otel-config.yml
Основные зависимости
Вот ключевые зависимости:
dependencies {
// Основные зависимости Spring Boot
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
// Основные зависимости OpenTelemetry
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'io.micrometer:micrometer-tracing-bridge-otel'
implementation 'io.opentelemetry:opentelemetry-api'
implementation 'io.opentelemetry:opentelemetry-sdk'
implementation 'io.opentelemetry:opentelemetry-exporter-otlp'
implementation 'io.opentelemetry.instrumentation:opentelemetry-instrumentation-api'
implementation 'io.opentelemetry:opentelemetry-exporter-logging'
}
Подробнее о ключевых зависимостях:
micrometer-tracing-bridge-otel: трассировка Micrometer соединяется с OpenTelemetry.opentelemetry-api: основной API-интерфейс OpenTelemetry.opentelemetry-sdk: реализация API-интерфейса OpenTelemetry.opentelemetry-exporter-otlp: данные телеметрии экспортируются по протоколу OTLP.
Реализация сервиса
Служба заказов:
@RestController
@RequestMapping("/orders")
public class OrderController {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderController.class);
private final PriceGateway priceGateway;
@Autowired
public OrderController(PriceGateway priceGateway) {
this.priceGateway = priceGateway;
}
@GetMapping("/{id}")
public Order findById(@PathVariable Long id) {
// Логируется с контекстом трассировки
LOGGER.info("Processing order request for id: {}", id);
// Служба ценообразования вызывается через шлюз
Price price = priceGateway.getPrice(id);
// Создается и возвращается заказ
return new Order(id, 1L, ZonedDateTime.now(), price.getAmount());
}
}
Шлюз ценообразования, важен для распределенной трассировки:
@Component
public class PriceGateway {
private final RestTemplate restTemplate;
private static final Logger LOGGER = LoggerFactory.getLogger(PriceGateway.class);
private static final String BASE_URL = "http://localhost:8083";
@Autowired
public PriceGateway(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public Price getPrice(long productId) {
LOGGER.info("Fetching price details for product: {}", productId);
String url = String.format("%s/prices/%d", BASE_URL, productId);
try {
ResponseEntity<Price> response = restTemplate.getForEntity(url, Price.class);
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
return response.getBody();
}
throw new RuntimeException("Failed to fetch price");
} catch (Exception e) {
LOGGER.error("Error fetching price: {}", e.getMessage());
throw new RuntimeException("Price service communication failed", e);
}
}
}
Конфигурация RestTemplate, важна для распространения трассировки:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Служба ценообразования:
@RestController
@RequestMapping("/prices")
public class PriceController {
private final static Logger LOGGER = LoggerFactory.getLogger(PriceController.class);
@GetMapping("/{id}")
public Price findById(@PathVariable Long id) {
LOGGER.info("Retrieving price for product: {}", id);
// Моделируется продолжительность обработки
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return new Price(id, BigDecimal.valueOf(Math.random() * 100));
}
}
Конфигурация OpenTelemetry
Свойства приложения
Файл свойств важен для корректной настройки OpenTelemetry:
# Конфигурация службы
spring.application.name=order-service
server.port=8084
# Основная конфигурация OpenTelemetry
otel.service.name=${spring.application.name}
otel.exporter.otlp.endpoint=http://localhost:4317
otel.traces.exporter=otlp
otel.metrics.exporter=otlp
otel.logs.exporter=otlp
# Конфигурация выборки
management.tracing.sampling.probability=1.0
# Шаблон логирования с идентификаторами трассировок и спанов
logging.pattern.level=%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]
# Настройки инструментирования
otel.instrumentation.spring-webmvc.enabled=true
otel.instrumentation.spring-webflux.enabled=true
otel.resource.attributes=deployment.environment=development
Конфигурация коллектора OpenTelemetry:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 1s
send_batch_size: 1024
attributes:
actions:
- key: service.name
action: upsert
from_attribute: service.name
exporters:
logging:
loglevel: debug
jaeger:
endpoint: jaeger:14250
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, attributes]
exporters: [logging, jaeger]
Настройка Docker
Dockerfile службы:
FROM eclipse-temurin:17-jdk
WORKDIR /app
COPY build/libs/*.jar app.jar
EXPOSE 8081
ENTRYPOINT ["java","-jar","app.jar"]
Конфигурация Docker Compose:
version: '3.8'
services:
order-service:
build:
context: ./order-service
dockerfile: Dockerfile
ports:
- "8084:8084"
environment:
- SPRING_PROFILES_ACTIVE=docker
- OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
networks:
- otel-network
price-service:
build:
context: ./price-service
dockerfile: Dockerfile
ports:
- "8083:8083"
environment:
- SPRING_PROFILES_ACTIVE=docker
- OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
networks:
- otel-network
otel-collector:
image: otel/opentelemetry-collector:0.88.0
volumes:
- ./otel-config.yml:/etc/otel-collector-config.yml
ports:
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
- "8888:8888" # Метрики Prometheus
networks:
- otel-network
jaeger:
image: jaegertracing/all-in-one:1.47
environment:
- COLLECTOR_OTLP_ENABLED=true
ports:
- "16686:16686" # Пользовательский интерфейс
- "14250:14250" # Коллектор
networks:
- otel-network
networks:
otel-network:
driver: bridge
Изменения кода завершены, теперь создадим приложение.
Запуск и тестирование
- Собираем проект:
./gradlew clean build
2. Запускаем контейнеры Docker:
docker-compose up --build
3. Проверяем, запускаются ли службы:
docker ps
После этой команды видим такие результаты:

4. Затем тестируем конечную точку с помощью команды curl или postman:
curl http://localhost:8080/product/1
curl http://localhost:8081/price/1
Если вызов API прерывается, прибегаем к таким командам:
# Просматриваются логи службы продуктов
docker-compose logs -f product-service
# Просматриваются логи службы ценообразования
docker-compose logs -f price-service
# Просматриваются логи коллектора OpenTelemetry
docker-compose logs -f otel-collector
# Просматриваются логи Jaeger
docker-compose logs -f jaeger
5. Получаем доступ к пользовательскому интерфейсу Jaeger:
- Вводим в браузере:
http://localhost:16686. - Выбираем из выпадающего списка службу.
- Нажимаем Find Traces и видим распределенные трассировки.
Появится такой же пользовательский интерфейс. Финальный вызов API отслеживается до того места, где был выполнен. В этом красота OpenTelemetry.
Устранение неполадок
Типичные проблемы и их решения:
- В Jaeger не появляются трассировки:
- Проверяем конфигурацию конечной точки OTLP.
- Проверяем логи коллектора:
docker-compose logs otel-collector. - Вероятность выборки задаем
1,0.
2. Службы не «общаются»:
- Проверяем конфигурацию сети в docker-compose.
- Проверяем порты и URL-адреса служб.
- Проверяем логи служб:
docker-compose logs service-name.
3. Контекст трассировки не распространяется:
- Корректно конфигурируем RestTemplate.
- Проверяем наличие идентификаторов трассировки в шаблоне логирования.
- Проверяем настройки инструментирования OpenTelemetry.
Рекомендации
Стратегия выборки:
- Использовать 1,0 при разработке.
- Для продакшена делать корректировку, исходя из трафика.
Логирование:
- Всегда включать идентификаторы трассировки и спанов.
- Использовать согласованные уровни ведения журнала.
- Добавлять в логи содержательный контекст.
Принадлежность ресурсов:
- Помечать трассировки с окружением.
- Добавлять версию службы.
- Включать информацию о развертывании.
Обработка ошибок:
- Корректно распространять и логировать ошибки.
- Включать в трассировки контекст ошибки.
- Использовать соответствующие коды состояния HTTP.
Стратегия выборки:
- Использовать 1,0 при разработке.
- Для продакшена делать корректировку, исходя из трафика.
Логирование:
- Всегда включать идентификаторы трассировки и спанов.
- Использовать согласованные уровни ведения журнала.
- Добавлять в логи содержательный контекст.
Принадлежность ресурсов:
- Помечать трассировки с окружением.
- Добавлять версию службы.
- Включать информацию о развертывании.
Обработка ошибок:
- Корректно распространять и логировать ошибки.
- Включать в трассировки контекст ошибки.
- Использовать соответствующие коды состояния HTTP.
Заключение
Распределенная трассировка — это передовой подход к мониторингу микросервисов и прояснению сложных потоков запросов. OpenTelemetry является универсальным решением, которым обеспечивается наблюдаемость в распределенных системах.
Интеграцией OpenTelemetry в приложение Spring Boot 3 реализуется сквозная трассировка, благодаря которой упрощаются отладка и мониторинг производительности. А такими инструментами, как Jaeger и OpenTelemetry Collector, разработчики визуализируют трассировки и эффективнее оптимизируют микросервисы. Этой настройкой обеспечивается масштабируемый и сопровождаемый подход к наблюдаемости, необходимый для современных приложений.
Код без наблюдаемости — все равно что навигация в темноте. С OpenTelemetry не просто пишешь код — создаешь систему, которую можно понять, отладить, доработать. Благодарим за внимание, и пусть ваши трассировки всегда будут суперинформативными!
Читайте также:
- OpenTelemetry и Sentry - недооцененные инструменты трассировки распределенных систем на Golang
- Вкратце о прокси Spring Boot
- Эффективное ведение журнала для приложений Spring Boot
Читайте нас в Telegram, VK и Дзен
Перевод статьи Chanuka Dinuwan: Implementing Distributed Tracing with OpenTelemetry and Spring Boot 3





