İçeriğe geç

Dağıtık Tracing — Bir İsteği Beş Servis Boyunca İzlemek

Orta 10 dk Çok sık karşılaşılır

Önce şunu oku: Feign Client — Kolaylığın Gizlediği Şeyler , Actuator ve Gözlemlenebilirlik — Sağlık, Metrik, İz

30 saniyede özet

Bir istek birçok servisten geçer. Hepsine aynı traceId taşınırsa istek tek bir ağaç olarak görünür ve bütün log'larda tek aramayla bulunur. Bağlamı taşımayan tek bir geçiş, ağacı orada ikiye böler.

Bir koli sipariş ettin ve takip numarasıyla nerede olduğuna bakıyorsun. Koli dört depodan geçiyor ama sen tek bir numarayla bütün yolculuğu görüyorsun.

Numara her etikete kopyalanırsa yolculuk tek parça görünür.
Adım adım oku
  1. Paket depodan bir takip numarasıyla çıkar.
  2. Her aktarma merkezi aynı numarayı yeni etikete yazar.
  3. Bir merkez eski numarayı yazmak yerine yeni bir numara basar.
  4. Takip ekranında yolculuk yarıda kesilir, başka yerde de nereden geldiği bilinmeyen bir paket belirir.
  1. Bayt: Sepet onayı patladı. BFF'in log'unda hata var ama sebebi hangi serviste?

  2. Sen: Beş servisin log'unu açıp saate göre mi eşleştireceğiz?

  3. Bayt: Aynı saniyede yüzlerce istek var. Saat yetmez, isteğin bir kimliği olmalı.

  4. Bayt: Kargo gibi: her servise aynı takip numarası gitsin, gerisini tek aramayla bulalım!

Trace, span ve ağaç

Bir isteğin bütün yolculuğuna trace denir ve tek bir traceId taşır. Yolculuğun her adımı ise bir spanBir trace'in tek bir adımı: bir gelen istek, bir giden çağrı ya da bir mesaj. Kendi spanId'si, süresi ve kendisini başlatan span'in id'si (parent) vardır.Sözlükte gör →’dir: kendi spanId’si, başlangıç zamanı ve süresi vardır.

Her span, kendisini başlatan span’in id’sini parent olarak saklar. BFF’in gelen istek span’i köktür; Feign ile orders’a yaptığı çağrı onun çocuğu, orders’ın o çağrıyı karşılayan span’i de onun çocuğudur.

Zipkin ya da Jaeger bu parent bağlantılarını izleyip ağacı bir şelale gibi çizer. Hangi adımın ne kadar sürdüğünü ve kimin kimi beklediğini tek ekranda görürsün.

Kafam karıştı, daha basit anlat

traceId kolinin takip numarasıdır, yolculuk boyunca değişmez. spanId ise her aktarmanın kendi fiş numarasıdır. Her fiş bir öncekini gösterir, ağaç böyle kurulur.

Hızlı kontrolBaşlangıç

Bir istek BFF, orders ve pricing servislerinden geçti. Üç servisin kayıtlarında da aynı olan hangisidir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

BFF, orders servisini Feign ile çağırıyor ve bağlam taşınıyor. orders'ın gelen istek span'inin parent'ı hangi span'dir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Satır satır: kimlik bir servisten ötekine

Kimlik bir HTTP başlığıyla taşınır: W3C standardındaki traceparentW3C Trace Context standardının HTTP (ve mesaj) başlığı. Sürüm, traceId, çağıranın spanId'si ve örnekleme bayrağını taşır; karşı servis aynı trace'e buradan katılır.Sözlükte gör →. İçinde traceId, çağıranın spanId’si ve örnekleme kararı yazar.

orders siparişi Kafka'ya yazıyor, shipping mesajı beş dakika sonra okuyor. shipping'in işi aynı trace'e girebilir mi? Cevabı göster

Girebilir. Kafka mesajlarının da başlıkları vardır ve traceparent oraya yazılır. Producer ve listener tarafında observation açıksa, shipping’in span’i producer span’inin çocuğu olur; aradaki beş dakika şelalede boşluk olarak görünür.

Tek istek, tek traceId

CheckoutFlow.java
1// BFF
şu an çalışan satır@GetMapping("/checkout")
3CheckoutDto checkout(@RequestParam String cartId) {
4 log.info("sepet onayı başladı");
5 Order order = orders.create(cartId); // Feign
6 return new CheckoutDto(order);
7}
8
9// orders
10@PostMapping("/orders")
11Order create(@RequestBody String cartId) {
12 Order order = repository.save(Order.from(cartId));
13 kafka.send("order-created", order.id(), order);
14 return order;
15}

Debug

Adım 1/5

BFF İstek geldi, gelen başlıkta traceparent yok. Micrometer Tracing yeni bir trace ve kök span açar.

traceId
= 4bf92f35…
spanId
= a1 (kök)
Java 21UTF-8LF2:1

Sol/sağ ok tuşlarıyla da gezebilirsin.

Burada kimse traceId’yi elle geçirmedi. Onu taşıyan şey, isteği yapan ya da karşılayan izlenen bileşenlerdir. Log tarafında ise işi MDCMapped Diagnostic Context: SLF4J/Logback'in thread'e bağlı anahtar-değer tablosu. Log pattern'i buradan okur; Micrometer Tracing traceId ve spanId'yi oraya koyar.Sözlükte gör → yapar: log çerçevesi traceId’yi oradan okur.

Kafam karıştı, daha basit anlat

Kimlik, her çağrıya eklenen bir başlıkla bir sonraki servise geçer. Karşı taraf başlığı görünce yeni numara basmaz, aynı numaranın altına yeni bir adım ekler.

Hızlı kontrolOrta

`traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01` başlığında üçüncü alan (`00f067aa0ba902b7`) neyi söyler?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Spring Boot 3 servisinde Micrometer Tracing açık. `log.info("sipariş kaydedildi")` satırında traceId'yi log'a kim yazar?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kendin gör

Bir sepet onayı isteği BFF’ten orders ile pricing’e, orders’tan bir @Async işe ve Kafka üzerinden shipping’e gidiyor. Her geçişin bağlamı taşıyıp taşımadığını sen seçiyorsun.

Trace propagation — istek ağacı nerede kopuyor?

Tohum 1
CheckoutFlow.java
1// BFF
2orders.create(cart); // Feign: traceparent eklenir
3http.send(req("/prices"), ofString()); // elle HttpClient: başlık yok
4
5// orders
6executor.setTaskDecorator(new ContextPropagatingTaskDecorator());
7audit.record(order); // @Async, başka thread
8kafkaTemplate.setObservationEnabled(true); // header: traceparent
Java 21UTF-8LF
  • ölçek: 0–500 ms
Hız
Adım 0

Şu an ne oldu?

Sepeti onayla isteği BFF'e geldi

BFF bir traceId üretir ve iki servisi çağırır. Her geçişte bu kimliğin karşıya taşınıp taşınmadığına bak.

Görevler0/3

  • Elle yazılmış bir HTTP çağrısıyla ağacı ikiye bölaçık

    İpucu

    Varsayılan ayarlarla oynat: pricing çağrısı Feign yerine elle kurulmuş bir HttpClient ile yapılıyor.

  • Yalnızca thread değiştirerek bir yetim trace üretaçık

    İpucu

    İki HTTP çağrısını da Feign yap, Kafka'yı açık bırak, @Async işi çıplak executor'a ver.

  • Sekiz span'i tek bir trace'te toplaaçık

    İpucu

    Dört geçişin dördü de bağlamı taşımalı: Feign, TaskDecorator ve iki tarafta da Kafka observation.

Olay günlüğü (0)

Henüz olay yok. Oynat veya adımla.

  1. Varsayılanla oynat. pricing çağrısı elle kurulmuş bir HttpClient ile yapılıyor. Ana trace’te pricing’e giden adım yok; pricing ise ⚠ işaretli, ayrı bir trace’in kökü.
  2. pricing’i Feign yap, @Async işi çıplak executor’a ver. HTTP geçişleri tamam ama tek bir thread değişimi yeni bir yetim trace üretiyor.
  3. Kafka’yı “yalnızca listener” yap. Producer başlık eklemiyor; shipping yeni bir trace ile başlıyor.
  4. Dört geçişi de düzelt. Sekiz span tek ağaçta toplanıyor ve orders’ın log satırı BFF ile aynı traceId’yi taşıyor.
Hızlı kontrolOrta

BFF, pricing servisini `HttpClient.newHttpClient()` ile elle kurulmuş bir istemciyle çağırıyor. Zipkin'de ne görürsün?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Tuzaklar

Yeni bir thread. Tracing bağlamı ThreadLocal üzerinde durur ve başka thread’e kendiliğinden geçmez. @Async için bağlamı kopyalayan ContextPropagatingTaskDecorator gerekir; ayrıntısı ThreadLocal dersinde.

Elle kurulmuş istemci. new RestTemplate(), RestClient.create() ya da HttpClient.newHttpClient() izlenmez ve başlık eklemez. İstemciyi Spring’in otomatik yapılandırdığı builder’dan üret.

Kapalı Kafka observation. Spring Kafka’da observation varsayılan olarak kapalıdır; template ve listener için ayrı ayrı açılır.

“Zipkin’de isteğim yok.” Spring Boot varsayılan olarak isteklerin yalnızca %10’unu dışarı gönderir. Bu samplingTrace'lerin yalnızca bir kısmını dışarı göndermek. Karar ilk serviste verilir ve traceparent'taki bayrakla aşağıya taşınır; trace ya bütün ya hiç kaydedilir.Sözlükte gör → kararı ilk serviste verilir ve başlıktaki bayrakla bütün servislere taşınır.

Kafam karıştı, daha basit anlat

Ağaç üç yerde kopar: iş başka bir thread’e geçince, çağrıyı elle kurulmuş bir istemci yapınca ve Kafka’da observation kapalıyken. Zipkin’de istek yoksa önce örnekleme oranına bak.

Hızlı kontrolOrta

Controller'ın log satırlarında traceId var, ama çağırdığı `@Async` metodun log satırlarında boş. Sebep ve çözüm nedir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Aşağıdaki örnek bir e-ticaret sitesinin sepet servisinden. Bağımlılıklar, örnekleme ve Kafka ayarı, thread’e bağlam taşıyan decorator ve izlenen RestClient tek yerde.

Derinleş · Sepet onayı: dört geçişte de bağlam taşımak 4 dosya · ~53 satır · ilk okumada atlayabilirsin
Proje dosyaları

pom.xml Micrometer Tracing'in OpenTelemetry köprüsü, Zipkin'e gönderen exporter ve Feign'i izlenir yapan modül.

pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-zipkin</artifactId>
</dependency>
<!-- Makes Spring Cloud OpenFeign clients create spans and send traceparent -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
</dependency>

src/main/resources/ application.yml Örnekleme oranı, Zipkin adresi ve Kafka'nın iki tarafında observation.

src/main/resources/application.yml
spring:
application:
name: checkout
kafka:
template:
observation-enabled: true # producer adds traceparent to record headers
listener:
observation-enabled: true # consumer continues the trace from those headers
management:
tracing:
sampling:
probability: 0.1 # Spring Boot's default; 1.0 while developing
zipkin:
tracing:
endpoint: http://zipkin:9411/api/v2/spans

src/main/java/com/shop/checkout/ TracingConfig.java @Async'e bağlam taşıyan decorator ve Spring'in builder'ından üretilen RestClient.

src/main/java/com/shop/checkout/TracingConfig.java
@Configuration
@EnableAsync
class TracingConfig {
// Spring Boot applies a TaskDecorator bean to its auto-configured executor.
// This one copies the caller's context (trace, MDC) onto the pool thread.
@Bean
TaskDecorator contextPropagatingTaskDecorator() {
return new ContextPropagatingTaskDecorator();
}
// Built from the auto-configured builder, so every call is observed and carries traceparent.
// RestClient.create() would skip that instrumentation.
@Bean
RestClient auditClient(RestClient.Builder builder) {
return builder.baseUrl("http://audit-service").build();
}
}

checkout.log Log satırı: traceId ve spanId kendiliğinden geliyor.

checkout.log
INFO 7 --- [checkout] [nio-8080-exec-3] [4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7] c.s.c.CheckoutController : sepet onayı başladı
INFO 7 --- [checkout] [ task-1] [4bf92f3577b34da6a3ce929d0e0e4736-5b1e2c7d9a0f3e44] c.s.c.AuditService : denetim kaydı yazıldı

Kendini sına

Önce hızlı bir ısınma: puan yok, kayıt yok. Sonra asıl sorular.

Şimşek turu1/5

Bir isteğin geçtiği her serviste traceId aynıdır.

Soru 1/4İleri

orders `order-created` olayını Kafka'ya yazıyor, shipping onu dakikalar sonra okuyor. Zipkin'de shipping hep ayrı bir trace olarak görünüyor. En olası sebep nedir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Aklında kalacak üç şey

  1. 1 traceId bütün yolculuk boyunca aynı kalır, spanId ise her adımda yenidir. Her span, kendisini başlatan span'in id'sini parent olarak taşır; ağaç bu bağlantılardan kurulur.
  2. 2 Kimliği servisten servise taşıyan şey traceparent başlığıdır. İzlenen Feign, Spring'in builder'ından çıkan RestClient ve observation'ı açık Kafka onu ekler; elle kurulmuş bir istemci eklemez.
  3. 3 Bağlam bir thread'e aittir. @Async, yeni bir thread ya da başka bir havuz bağlamı kopyalamazsa iş orada yeni ve sahipsiz bir trace başlatır.
Sonraki kapı Monoliti bir gecede yeniden yazmadan, müşteriler hiçbir şey fark etmeden parça parça taşıyabilir misin? Strangler Fig ve API Sürümleme — Monoliti Kimseyi Kırmadan Taşımak · 10 dk

4 kart sonraki derste seni bekliyor

0/5 kart bu dersten toplandı