İçeriğe geç

Retry, Backoff ve Jitter — Herkes Aynı Anda Tekrar Denerse

Orta 9 dk Sık karşılaşılır

Önce şunu oku: Backpressure ve Yük Atma — Kuyruk Dolarsa Ne Olur?

30 saniyede özet

Tekrar denemek kısa hataları kurtarır, ama herkes aynı anda tekrar denerse düzelmek üzere olan servisi yeniden düşürür. Exponential backoff aralıkları açar, jitter ise denemeleri zamana yayar.

Sevdiğin fırın on dakikalığına kapanmış, kapıda “birazdan döneceğiz” yazıyor. Sen ve yirmi kişi daha tam on dakika sonra dönüyorsunuz. Kapı açıldığı an herkes aynı anda içeri girmeye çalışıyor ve kimse kıpırdayamıyor.

  1. Bayt: Bakiye servisi üç saniyeliğine düştü. Uygulama hata alınca hemen tekrar deniyor, sorun yok!

  2. Sen: Servis üç saniye sonra döndü ama hâlâ cevap veremiyor.

  3. Bayt: Ayakta ama boğuluyor... herkes aynı anda kapıda.

  4. Bayt: O zaman sorun düşmesi değil, nasıl geri döndüğümüz. Tekrar denemelere bakalım.

Tekrar denemek neden iki ucu keskin?

Ağ bir anlığına koptuysa ya da servis yeniden başlıyorsa, birkaç saniye sonra tekrar denemek isteği kurtarır. Kullanıcı hiçbir şey fark etmez.

Sorun, binlerce istemcinin aynı kesintiyi aynı anda yaşamasıdır. Hepsi hemen tekrar denerse, servis ayağa kalktığı an normal yükünün katlarıyla karşılaşır. Buna thundering herdÇok sayıda istemcinin aynı anda aynı kaynağa yüklenmesi; örneğin bir kesintiden sonra herkesin aynı anda tekrar denemesi.Sözlükte gör → denir.

Kafam karıştı, daha basit anlat

Tekrar denemek tek istemci için iyidir. Herkes aynı anda yaparsa servisi yeniden düşürür.

Hızlı kontrolBaşlangıç

Tekrar denemek (retry) hangi durumda gerçekten işe yarar?

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

Thundering herd ne demektir?

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

Beklemeyi büyüt, sonra dağıt

120 istemci aynı anda tekrar deniyor. Servis saniyede 40 çağrı kaldırabiliyor ve fazlası onu yavaşlatıyor. O saniyede kaç istemci cevap alır? Cevabı göster

40’tan da az. Kaldıramadığı her çağrı da iş tüketir; servis boğulur ve normalde yapabileceğinden azını bitirir. Simülatördeki modelde yalnızca 13 istemci cevap alıyor.

Aynı kalabalık, farklı geliş anları.
Adım adım oku
  1. Dükkân kısa süre kapalı; herkes geri dönecek.
  2. Kapı açılınca herkes aynı anda gelir ve kapı tıkanır.
  3. Herkes biraz farklı zamanda gelirse kapı akar.
  4. Tekrar denemeleri zamana yay.

İlk çare exponential backoffBaşarısız her denemeden sonra bekleme süresini katlayarak artırmak. Süreye rastgele bir pay (jitter) eklenir ki istemciler aynı anda geri dönmesin.Sözlükte gör →: her başarısız denemeden sonra beklemeyi ikiye katla. 1, 2, 4, 8 saniye. Servis toparlanmak için zaman kazanır.

Ama herkes aynı takvimle beklerse, tekrar denemeler yine aynı saniyelere yığılır. jitterTekrar deneme beklemesine eklenen rastgelelik. Aynı anda hata alan istemcilerin farklı anlarda dönmesini sağlar.Sözlükte gör → her istemcinin beklemesini rastgele biraz farklılaştırır; aynı kalabalık zamana yayılır.

Kafam karıştı, daha basit anlat

Backoff aralıkları açar. Jitter herkesin farklı bir anda gelmesini sağlar.

Hızlı kontrolOrta

Exponential backoff ne yapar?

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

Exponential backoff varken neden jitter da gerekir?

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

Kendin gör

Herkes aynı anda tekrar denerse

Tohum 112821
  1. 0
  2. 4
  3. 8
  4. 12
  5. 16
  6. 20
  • ✕ servis kapalı
  • ⚠ kapasite aşıldı
  • ✓ başarılı
  • - - - kapasite: 40/sn

Oynat ya da adımla: her adım dört saniye.

Hız
Adım 0

Şu an ne oldu?

Hemen tekrar dene (her saniye) · en fazla 4 deneme

Servis ilk 3 saniye kapalı, sonra saniyede 40 çağrı kaldırıyor. 120 istemci bekliyor.

Görevler0/3

  • Servis ayağa kalktığında istemcilerin yarısından fazlasını kaybetaçık

    İpucu

    Varsayılan ayarlar yeter.

  • Backoff kullanıp yine de servise 100 denemelik bir dalga gönderaçık

    İpucu

    Herkes aynı takvimle beklesin.

  • Hiç vazgeçen olmadan bütün istemcileri cevaplataçık

    İpucu

    Beklemeleri istemciden istemciye farklılaştır.

Olay günlüğü (0)

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

  1. Varsayılanla oynat. Herkes her saniye tekrar deniyor: 120 istemciden yalnızca 13’ü cevap aldı.
  2. “Exponential backoff” seç. Aralar açıldı, ama denemeler yine 120’lik dalgalar halinde geldi.
  3. “Sabırlı istemci”yi aç. Daha çok deneme hakkı var, ama dalgalar sürdüğü için yarısı bile cevap alamadı.
  4. “Exponential backoff + jitter” seç. Denemeler dağıldı ve 120 istemcinin hepsi cevap aldı.
Hızlı kontrolOrta

Servis ayağa kalktığı saniyede kapasitesinin üç katı tekrar deneme alıyor. Ne olur?

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

Deneme hakkını 4'ten 8'e çıkarmak ama jitter eklememek ne sağlar?

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

Tuzaklar

Her şeyi tekrar denemek. 400 ya da 404 bir dakika sonra da aynı cevabı verir. Yalnızca geçici hataları (zaman aşımı, 503, 429) tekrar dene ve 429’da Retry-After’a uy.

Para çeken bir isteği körü körüne tekrarlamak. İlk istek aslında işlenmiş, yalnızca cevabı kaybolmuş olabilir. Ödeme gibi işlemlerde tekrar denemeden önce idempotency key gönder.

Katlanan tekrarlar. Üç katmanın her biri üç kez denerse, en alttaki servis tek bir tıklama için 27 çağrı görür. Tekrar denemeyi tek bir katmanda yap.

Kafam karıştı, daha basit anlat

Yalnızca geçici hatayı tekrar dene, ödemede idempotency key kullan, tekrarı tek katmanda tut.

Hızlı kontrolOrta

Bir ödeme isteği zaman aşımına uğradı. Tekrar denemeden önce ne gerekir?

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

Aşağıdaki örnek bir mobil bankanın bakiye istemcisinden: yalnızca geçici hatalarda, jitter’lı backoff ile ve sınırlı sayıda tekrar deneme.

Derinleş · Bakiye istemcisi: jitter'lı backoff, yalnızca geçici hatalar, sınırlı deneme 4 dosya · ~76 satır · ilk okumada atlayabilirsin
Proje dosyaları

src/main/java/com/bank/balance/ RetryPolicy.java Politika: deneme sayısı ve bekleme tek yerde; tam jitter ile her istemci farklı bekler.

src/main/java/com/bank/balance/RetryPolicy.java
record RetryPolicy(int maxAttempts, Duration base, Duration cap) {
static RetryPolicy standard() {
return new RetryPolicy(4, Duration.ofMillis(200), Duration.ofSeconds(5));
}
/** Full jitter: a random wait between zero and the exponential ceiling. */
Duration delayBefore(int attempt, RandomGenerator random) {
long ceiling = Math.min(cap.toMillis(), base.toMillis() << (attempt - 1));
return Duration.ofMillis(random.nextLong(ceiling + 1));
}
}

src/main/java/com/bank/balance/ Retryable.java Sınıflandırma: yalnızca zaman aşımı, 503 ve 429 tekrar denenir; 4xx hatalar hemen döner.

src/main/java/com/bank/balance/Retryable.java
final class Retryable {
private Retryable() {}
static boolean isTransient(Throwable error) {
if (error instanceof HttpStatusCodeException http) {
int status = http.getStatusCode().value();
return status == 429 || status == 502 || status == 503 || status == 504;
}
return error instanceof ResourceAccessException; // timeouts, refused connections
}
}

src/main/java/com/bank/balance/ BalanceClient.java İstemci: 429'da Retry-After'a uyar, deneme hakkı bitince hatayı yukarı iletir.

src/main/java/com/bank/balance/BalanceClient.java
@Component
class BalanceClient {
private final RestClient rest;
private final RetryPolicy policy = RetryPolicy.standard();
private final RandomGenerator random = RandomGenerator.getDefault();
BalanceClient(RestClient rest) {
this.rest = rest;
}
Balance balanceOf(String accountId) throws InterruptedException {
for (int attempt = 1; ; attempt++) {
try {
return rest.get().uri("/accounts/{id}/balance", accountId).retrieve().body(Balance.class);
} catch (RuntimeException error) {
if (!Retryable.isTransient(error) || attempt == policy.maxAttempts()) {
throw error; // permanent, or out of attempts: let the caller decide
}
Thread.sleep(waitBefore(error, attempt + 1));
}
}
}
private Duration waitBefore(RuntimeException error, int attempt) {
if (error instanceof HttpStatusCodeException http && http.getResponseHeaders() != null) {
String retryAfter = http.getResponseHeaders().getFirst("Retry-After");
if (retryAfter != null) {
return Duration.ofSeconds(Long.parseLong(retryAfter)); // the server knows best
}
}
return policy.delayBefore(attempt, random);
}
}

src/main/java/com/bank/payment/ TransferClient.java Ödeme: tekrar denemeler aynı idempotency key ile gider, para iki kez çekilmez.

src/main/java/com/bank/payment/TransferClient.java
@Component
class TransferClient {
private final RestClient rest;
TransferClient(RestClient rest) {
this.rest = rest;
}
/** The same key on every attempt: a retry can never move the money twice. */
TransferReceipt send(TransferRequest request, UUID idempotencyKey) {
return rest.post().uri("/transfers")
.header("Idempotency-Key", idempotencyKey.toString())
.body(request)
.retrieve()
.body(TransferReceipt.class);
}
}

Kendini sına

Şimşek turu1/4

Bir hata alındığında hemen tekrar denemek her zaman en güvenli seçimdir.

Soru 1/3İleri

Üç katmanlı bir zincirde her katman 3 kez deniyor. En alttaki servis tek bir kullanıcı tıklaması için en çok kaç çağrı görebilir?

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

Aklında kalacak üç şey

  1. 1 Hemen ve sürekli tekrar denemek, kısa bir kesintiyi uzun bir kesintiye çevirebilir. Servis ayağa kalktığı an bütün istemcilerin tekrar denemesiyle karşılaşır.
  2. 2 Exponential backoff aralıkları açar ama tek başına yetmez. Herkes aynı takvimle beklerse tekrar denemeler yine aynı saniyelere yığılır.
  3. 3 Jitter her istemcinin bekleme süresini biraz farklılaştırır. Aynı sayıda deneme zamana yayılır ve servis yükü kaldırabilir.
Sonraki kapı Milyonlarca kaydı kontrol etmen gerekiyor ama hepsini belleğe sığdıramıyorsun. Yalnızca birkaç bit yeter mi? Bloom Filter — "Kesinlikle Yok" mu, "Belki Var" mı? · 9 dk

4 kart sonraki derste seni bekliyor

0/4 kart bu dersten toplandı