Kafka'dan gelen bir mesaj işlenemezse Spring onu birkaç kez dener, sonra log'a yazıp atlar: mesaj kaybolur. İşlenemeyen mesajları ayrı bir 'sahipsiz' kuyruğa (DLT) koymak, kaybı önlemenin yoludur.
Tek kasalı bir postanede adresi okunmayan bir mektup yüzünden bütün kuyruk bekliyor. Sonunda memur mektubu sessizce çöpe atıyor.
Bayt: Muhasebe bir günün cirosunda üç sipariş eksik buldu!
Sen: Mesajlar Kafka'da duruyor mu?
Bayt: Duruyor. Log'da da yalnızca üç tane Backoff exhausted satırı var.
Bayt: Okunmayan mektubu çöpe atan bir postane gibi. Hadi kuyruğa bakalım.
Muhasebe, bir günün cirosunda üç siparişin eksik olduğunu buldu. Kafka’da mesajlar duruyordu, DLT diye bir topic yoktu. Log’da yalnızca üç “Backoff exhausted” satırı vardı.
Hiçbir şey ayarlamazsan ne olur?
Listener exception fırlatınca kaydı Spring Kafka’nın DefaultErrorHandler’ı devralır. Varsayılan ayarla kaydı aralıksız dokuz kez daha dener.
Denemeler biterse kayıt log’a yazılır ve offset ilerletilir. Kaydın kendisi hiçbir yerde saklanmaz: mesaj kaybolmuştur.
Bunu önlemek için handler’a bir kurtarıcı verilir. DeadLetterPublishingRecoverer, kaydı hata bilgisiyle birlikte bir dead letter topicİşlenemeyen mesajların, hata bilgisiyle birlikte yazıldığı ayrı topic. Mesaj kaybolmaz; incelenip düzeltildikten sonra yeniden gönderilebilir.Sözlükte gör →’e yazar.
Kafam karıştı, daha basit anlat
Hiçbir şey ayarlamazsan Spring mesajı birkaç kez dener, olmazsa log’a yazıp bir sonrakine geçer. Mesajın kendisi hiçbir yerde saklanmaz, yani kaybolur.
✎ Hızlı kontrolBaşlangıç
Spring Kafka'da @KafkaListener bir exception fırlattı ve sen hiçbir hata yönetimi ayarlamadın. Varsayılan olarak ne olur?
Arada bir sipariş hiç işlenmiyor ve DLT'de de yok. Log'da yalnızca bir 'Backoff exhausted' satırı var. Hatalı satır hangisi?
Tekrar denerken kuyruk bekler
🤔 #3 başarısız oldu ve DefaultErrorHandler onu yeniden deniyor. Aynı partition'daki #4, #5 ve #6 ne yapar? Cevabı göster
Bekler. Handler başarısız offset’e geri döner ve aynı kaydı yeniden teslim eder. Bir partition sırayla işlendiği için arkadakiler o kayıt bitene kadar gelmez.
Tahminin tuttu mu?
Okunmayan mektup: kuyruk bekler, sonra ya çöpe ya rafa.Adım adım oku
#3 işlenemiyor ve DefaultErrorHandler onu art arda yeniden deniyor.
Aynı partition'daki #4, #5 ve #6 bu sırada bekler; tek kasalı postane gibi.
Varsayılan ayarda denemeler bitince kayıt log'a yazılıp atlanır: mesaj Kafka'da durur, ama uygulama için kaybolmuştur.
DeadLetterPublishingRecoverer ile kayıt sahipsiz rafına, yani DLT topic'ine yazılır ve kuyruk ilerler.
Bloklayan retry sırayı korur, bedeli lag’dir. @RetryableTopic ise başarısız kaydı gecikmeli retry topic’lerine alır ve partition akmaya devam eder. Bu sefer de o kayıt, arkasındakilerden sonra işlenir.
Kafam karıştı, daha basit anlat
Mesajlar tek sıra hâlinde gelir. Öndeki mesaj yeniden denenirken arkadakiler bekler. Onu kenara ayırırsan sıra akar, ama o mesaj arkadakilerden sonra işlenir.
✎ Hızlı kontrolOrta
DefaultErrorHandler bir kaydı yeniden denerken aynı partition'daki sonraki kayıtlara ne olur?
@RetryableTopic ile bloklamayan retry'a geçtin. Ne kazanır, ne kaybedersin?
Kendin gör
Bozuk mesaj partition'ı durdurur mu?
Tohum 186651
#1
○ sırada
#2
○ sırada
#3
○ sırada
#4
○ sırada
#5
○ sırada
#6
○ sırada
İşlenme sırası: —
Oynat ya da adımla.
Hız
Adım 0
Şu an ne oldu?
DefaultErrorHandler, varsayılan (9 yeniden deneme, sonra atla)
Altı sipariş aynı partition'da. Üçüncüsü işlenemiyor: arkadakiler bekleyecek mi, mesaj nereye gidecek?
Varsayılanla oynat. Poison pill on kez denendi, sonra kayboldu.
Handler’ı “DLT” yap. Kayıt kurtuldu, ama boşa iki deneme yapıldı.
“Yeniden denenmez”i aç. İlk hatada DLT’ye gitti.
Hatayı “Geçici” yap ve “yeniden denenmez”i kapat. Üçüncü denemede geçti, sıra korundu.
Handler’ı “@RetryableTopic” yap. Partition aktı, sıra bozuldu.
✎ Hızlı kontrolOrta
Her hatayı nasıl ele alınması gerektiğine göre ayır.
Asla düzelmeyecek mesaj
poison pillKaç kez denenirse denensin işlenemeyecek bir mesaj: bozuk format, geçersiz veri. Yeniden denemek yalnızca arkasındaki mesajları bekletir.Sözlükte gör →, kaç kez denenirse denensin işlenemeyecek bir kayıttır: çözülemeyen bayt, geçersiz veri, tanınmayan olay tipi. Onu yeniden denemek yalnızca arkadaki kayıtları bekletir.
addNotRetryableExceptions(...) ile bu hataları sınıflandır; ilk hatada kurtarıcıya giderler. Geçici hatalar ise (503, deadlock) back-off ile yeniden denenir.
Kafam karıştı, daha basit anlat
Bozuk bir mesajı kaç kez denersen dene işlenmez. Onu hemen ayrı bir kutuya koy ki arkadaki sağlam mesajlar beklemesin.
✎ Hızlı kontrolOrta
Kayıt geçersiz bir JSON içeriyor ve doğrulama her seferinde başarısız oluyor. En doğru yaklaşım hangisi?
Tuzaklar
Çözme hatası listener’a ulaşmaz. Deserializer’ı ErrorHandlingDeserializer ile sar; yoksa bozuk kayıt poll sırasında patlar ve handler onu göremez.
Tekrarlanan yan etki. Her deneme listener’ı baştan çalıştırır. Hatadan önce gönderilen e-posta her denemede yeniden gider; listener idempotent olmalı.
Uzun back-off. Bloklayan retry’ın toplam beklemesi max.poll.interval.ms’yi aşarsa consumer gruptan düşer ve rebalance başlar.
Aşağıdaki örnek bir bankanın kart harcamalarını muhasebe defterine işleyen servisinden ve dersin bütün kurallarını birlikte uyguluyor: çözme hataları handler’a ulaşıyor, kalıcı hatalar denenmeden DLT’ye gidiyor, geçici hatalar back-off ile tekrar deneniyor ve listener idempotent.
Derinleş · Kart harcamalarını deftere işleyen servis: uçtan uca 6 dosya · ~140 satır · ilk okumada atlayabilirsin
Listener önce bir e-posta gönderiyor, sonra veritabanına yazarken exception alıyor. Kayıt 3 kez deneniyor ve üçüncüsünde geçiyor. Kullanıcı ne yaşar?
Aklında kalacak üç şey
1 Ayar yapılmamış DefaultErrorHandler, denemeler bitince mesajı yalnızca log'a yazar. Kaybedilmemesi gereken mesajlar bir dead letter topic'e gitmelidir.
2 Bekleyerek tekrar denemek sırayı korur ama arkadaki mesajları durdurur; @RetryableTopic akışı sürdürür ama sırayı bozar. İkisi aynı anda olmaz.
3 Hiç düzelmeyecek hatalar tekrar denenmez. Onları denemek yalnızca arkadaki mesajları bekletir.