Dağıtık Tracing — Bir İsteği Beş Servis Boyunca İzlemek
Ö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.
Adım adım oku
- Paket depodan bir takip numarasıyla çıkar.
- Her aktarma merkezi aynı numarayı yeni etikete yazar.
- Bir merkez eski numarayı yazmak yerine yeni bir numara basar.
- Takip ekranında yolculuk yarıda kesilir, başka yerde de nereden geldiği bilinmeyen bir paket belirir.
-
Bayt: Sepet onayı patladı. BFF'in log'unda hata var ama sebebi hangi serviste?
-
Sen: Beş servisin log'unu açıp saate göre mi eşleştireceğiz?
-
Bayt: Aynı saniyede yüzlerce istek var. Saat yetmez, isteğin bir kimliği olmalı.
-
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.
Bir istek BFF, orders ve pricing servislerinden geçti. Üç servisin kayıtlarında da aynı olan hangisidir?
BFF, orders servisini Feign ile çağırıyor ve bağlam taşınıyor. orders'ın gelen istek span'inin parent'ı hangi span'dir?
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
// BFF@GetMapping("/checkout")CheckoutDto checkout(@RequestParam String cartId) { log.info("sepet onayı başladı"); Order order = orders.create(cartId); // Feign return new CheckoutDto(order);} // orders@PostMapping("/orders")Order create(@RequestBody String cartId) { Order order = repository.save(Order.from(cartId)); kafka.send("order-created", order.id(), order); return order;}Debug
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)
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.
`traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01` başlığında üçüncü alan (`00f067aa0ba902b7`) neyi söyler?
Spring Boot 3 servisinde Micrometer Tracing açık. `log.info("sipariş kaydedildi")` satırında traceId'yi log'a kim yazar?
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// BFForders.create(cart); // Feign: traceparent eklenirhttp.send(req("/prices"), ofString()); // elle HttpClient: başlık yok // ordersexecutor.setTaskDecorator(new ContextPropagatingTaskDecorator());audit.record(order); // @Async, başka threadkafkaTemplate.setObservationEnabled(true); // header: traceparent- ölçek: 0–500 ms
Ş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.
- 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ü.
- pricing’i Feign yap,
@Asyncişi çıplak executor’a ver. HTTP geçişleri tamam ama tek bir thread değişimi yeni bir yetim trace üretiyor. - Kafka’yı “yalnızca listener” yap. Producer başlık eklemiyor; shipping yeni bir trace ile başlıyor.
- 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.
BFF, pricing servisini `HttpClient.newHttpClient()` ile elle kurulmuş bir istemciyle çağırıyor. Zipkin'de ne görürsün?
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.
Controller'ın log satırlarında traceId var, ama çağırdığı `@Async` metodun log satırlarında boş. Sebep ve çözüm nedir?
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
Kendini sına
Önce hızlı bir ısınma: puan yok, kayıt yok. Sonra asıl sorular.
Bir isteğin geçtiği her serviste traceId aynıdır.
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?
Aklında kalacak üç şey
- 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 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 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.
4 kart sonraki derste seni bekliyor