Ödeme API çağrınız sabah 2'de başarısız oldu. Bu bir ağ arızası mıydı, bir hız sınırı mıydı, yoksa çökmüş bir sunucu mu? Cevap, yeniden denemenin işlemi kurtarıp kurtarmayacağını veya müşteriyi iki kez ücretlendirip ücretlendirmeyeceğini belirler.
Yeniden denemeler, dağıtılmış sistemlerde en yaygın esneklik modelidir ve en sık yanlış yapılır. Bir HTTP çağrısının etrafına sarılmış bir döngü, savunma programlama gibi hissettirir. Yanlış yapıldığında, binlerce istemci aynı anda zor durumdaki bir sunucuya yük bindiği için 30 saniyelik bir kesintiyi 30 dakikaya dönüştürür. Doğru yapıldığında, yeniden denemeler geçici arızaları o kadar temiz bir şekilde emer ki kullanıcılarınız bunları asla fark etmez.
Bu kılavuz, üretim sistemlerinin güvendiği yeniden deneme mantığını kapsar: hangi durum kodlarını yeniden denemeli, tam jitter'lı üstel geri çekilme formülü, Retry-After başlıkları, idempotens anahtarları, yeniden deneme bütçeleri ve devre kesiciler. Ayrıca, Apidog sahte sunucularıyla 429'ları ve 503'leri simüle ederek istemcinizin doğru davrandığını nasıl kanıtlayacağınızı da göreceksiniz, çünkü başarısız bir sunucuya karşı hiç test etmediğiniz bir yeniden deneme deseni bir tahmindir, bir tasarım değil. Fintek API yeniden deneme mantığı geliştiren ekipler bunu pahalı yoldan öğrenir; sizin öğrenmenize gerek yok.
Neden basit yeniden denemeler kesintileri daha kötü hale getirir
Saniyede 1.000 istek işleyen bir hizmet düşünün. Beş saniye boyunca aksıyor. Her istemci hemen, her biri üç kez yeniden deniyor. 1.000 rps'lik talebiniz, zaten diz çökmüş bir sunucuya yönelik 4.000 rps'ye dönüşüyor. Tamamen çöküyor. Şimdi her istemci tekrar yeniden deniyor.
Bu geri bildirim döngüsünün bir adı var: yeniden deneme fırtınası. Sunucu geri geldiğinde oluşan senkronize yığılma, gürleyen sürüdür. Google'ın SRE kitabı, basamaklı hataları ele alma bölümünde bu deseni vurgular: geri çekilme olmaksızın yapılan yeniden denemeler, sistemin en az kaldırabileceği anda yükü artırır ve orijinal hata düzeltildikten çok sonra bile bir hizmeti kapalı tutabilir.
Çoğu yeniden deneme fırtınasına iki tasarım hatası neden olur:
- Denemeler arasında gecikme olmaması. Anında yeniden denemeler, en kötü olası pencerede yükü artırır.
- Sabit gecikmeler. Her istemci tam olarak bir saniye beklerse, hepsi aynı anda geri döner. Sunucu, düzgün bir yükseliş yerine senkronize trafik dalgaları alır.
Çözüm "asla yeniden deneme" değildir. Çözüm, seçici olarak, artan rastgele gecikmelerle ve yeniden denemelerinizin eklediği ekstra yükün kesin bir tavanı ile yeniden denemektir.
Bu arızaları yeniden dene, asla şunları deneme
Herhangi bir geri çekilme matematiğinden önce, istemcinizin bir karar tablosuna ihtiyacı vardır. Sunucunun geçersiz olarak zaten reddettiği bir isteği yeniden denemek kapasiteyi boşa harcar ve günlükleri kirletir. Geçici bir hatayı yeniden denemek asıl amaçtır.
Şunları yeniden dene:
| Sinyal | Anlamı |
|---|---|
| 429 Çok Fazla İstek | Bir hız sınırına ulaştınız. Geri çekilin ve daha yavaş gelin. |
| 502 Hatalı Ağ Geçidi | Yukarı akış bir atlama çöplük döndürdü. Genellikle geçici. |
| 503 Hizmet Kullanılamıyor | Sunucu aşırı yüklü veya yeniden başlatılıyor. |
| 504 Ağ Geçidi Zaman Aşımı | Yukarı akış bir bağımlılık çok yavaştı. |
| Bağlantı sıfırlamaları, DNS hataları, soket zaman aşımları | İstek hiçbir zaman ulaşmamış olabilir. |
Bir 504 ağ geçidi zaman aşımı özel dikkat gerektirir: ağ geçidi beklemeyi bırakmış olsa bile kaynak isteğinizi işlenmiş olabilir. Bu ayrım, idempotensi'ye geldiğimizde önem kazanır.
Şunları asla yeniden deneme:
| Sinyal | Anlamı |
|---|---|
| 400 Hatalı İstek | Yükünüz hatalı biçimlendirilmiş. Bir dahaki sefere de hatalı biçimlendirilmiş olacak. |
| 401 Yetkilendirilmemiş | Kimlik bilgileriniz yanlış veya süresi dolmuş. Tokeni yenileyin, döngü yapmayın. |
| 403 Yasak | İzniniz yok. Yeniden denemek onu sağlamaz. |
| 422 İşlenemeyen Varlık | Doğrulama başarısız oldu. Veriyi düzeltin, zamanlamayı değil. |
Kural: hata sunucunun durumu veya ağ ile ilgili olduğunda yeniden dene. Hata isteğinizle ilgili olduğunda hızla başarısız ol. 429 ikisinin arasında yer alır; yeniden denenebilir, ancak aynı zamanda genel istek hızınızın iyileştirilmesi gerektiğine dair bir sinyaldir; bu, herhangi bir yeniden deneme döngüsünün yukarısında çözülmesi gereken bir oran sınırlama sorunudur.
Üstel geri çekilme formülü ve jitter'ın neden önemli olduğu
Üstel geri çekilme, her yeniden denemenin bir öncekinden daha uzun beklemesi anlamına gelir, varsayılan olarak iki katına çıkar:
delay = base * 2^retry_count
500 ms'lik bir tabanla, bu 0.5s, 1s, 2s, 4s, 8s demektir. Gecikmelerin dakikalara dönüşmemesi için bir üst sınır (örneğin 30 saniye) ekleyin:
delay = min(cap, base * 2^retry_count)
Bu, "yüklenme" sorununu çözer ancak senkronizasyon sorununu çözmez. Eğer 5.000 istemci aynı anda başarısız olursa, basit üstel geri çekilme ile hepsi t=0.5s, sonra t=1s, sonra t=2s'de geri gelir. Hala dalgalar halinde. Hala bir sürü, sadece daha kibar bir sürü.
Jitter, gecikmeyi rastgele hale getirerek senkronizasyonu bozar. AWS Mimari Blogu, üstel geri çekilme ve jitter analizinde, çekişmeli bir kaynağa karşı yarışan istemcileri simüle ederek sayıları inceledi. Jitter'sız geri çekilme hala kümelenmiş çağrı zirveleri üretti. Sıfır ile üstel tavan arasında rastgele bir gecikme seçen tam jitter, hem en az toplam çağrıyı hem de en kısa tamamlama sürelerine yakın sonuçları üretti:
delay = random_between(0, min(cap, base * 2^retry_count))
Bu sonuç insanları şaşırtır. Sıfıra kadar tamamen rastgeleleştirme, düzenli bir ikiye katlama programına kıyasla özensiz gelebilir. Ancak istemcileri pencere boyunca düzgün bir şekilde dağıtmak, sunucu yükünü düz tutan şeydir. AWS analizi ayrıca "eşit jitter" (yarısı sabit, yarısı rastgele) ve "ilişkisiz jitter"ı da test etti; tam jitter ve ilişkisiz jitter öne çıktı ve tam jitter doğru bir şekilde yazılması en basit olanıdır. Aksini söyleyen ölçümleriniz olmadığı sürece bunu varsayılan yeniden deneme deseniniz olarak kullanın.
Sunucu size söylediğinde Retry-After'a uyun
Geri çekilme, istemcinizin ne kadar beklemesi gerektiğini tahmin etmesidir. Bazen sunucu bu tahmini ortadan kaldırır. 429 ve 503 yanıtları için tanımlanan Retry-After başlığı, ya saniye cinsinden bir sayı ya da bir HTTP tarihi taşır:
HTTP/1.1 429 Too Many Requests
Retry-After: 12
Bu başlık mevcut olduğunda, hesaplanan geri çekilmenizi geçersiz kılar. Sunucu, hız sınırlama penceresinin ne zaman sıfırlandığını veya bakımının ne zaman bittiğini bilir; sizin üstel programınız bilmez. Retry-After'ı göz ardı eden istemciler, sağlayıcıların kısıtlamadan doğrudan yasaklamaya geçmelerinin nedenlerinden biridir. Ayrıştırın, saygı gösterin ve kötü niyetli veya hatalı bir Retry-After: 86400'ün işçinizi bir gün boyunca takılı bırakmaması için yine de üst sınırınızı ve maksimum yeniden deneme sayınızı uygulayın.
Idempotens: POST'u yeniden denemek için ön koşul
İşte o önceki 504'teki tuzak. GET, PUT ve DELETE sözleşme gereği idempotenttir: onları iki kez göndermek sistemi aynı durumda bırakır. POST değildir. Eğer POST /v1/payments sunucu tarafından işlendikten sonra zaman aşımına uğrarsa, yeniden denemeniz ikinci bir ödeme oluşturur. Tebrikler, mükemmel çalışma süresine sahip bir çift ücretlendirme makinesi inşa ettiniz.
Çözüm bir idempotens anahtarıdır: her mantıksal işlemde başlık olarak gönderilen benzersiz bir istemci tarafından oluşturulmuş kimlik (genellikle bir UUID). Sunucu anahtarı ilk yanıtla birlikte saklar ve herhangi bir kopya için depolanan bu yanıtı tekrar oynatır. Stripe'ın idempotent istekleri tam olarak bu şekilde çalışır ve çoğu ödeme ve provizyon API'si bunu takip etmiştir.
Anahtarları çalıştıran iki kural:
- Aynı işlem, aynı anahtar. Bir mantıksal ödemenin her yeniden denemesi tek bir anahtarı yeniden kullanır. Yeni bir kullanıcı eylemi yeni bir anahtar alır.
- Anahtarı ilk göndermeden önce oluşturun, yeniden deneme döngüsünün içinde değil. Aksi takdirde her yeniden deneme yeni bir işlem gibi görünür ve koruma buharlaşır.
Çağırdığınız API idempotens anahtarlarını desteklemiyorsa, idempotent olmayan yazmaları otomatik olarak yeniden denemeyin. Hatayı yüzeye çıkarın ve bir insanın veya bir uzlaştırma işinin karar vermesine izin verin.
Yeniden deneme bütçeleri ve devre kesiciler: acil çıkış
Geri çekilme, yeniden denemelerin ne zaman olacağını şekillendirir. Kaç tane olacağını sınırlamaz. Uzun bir kesinti sırasında, iyi jitter'lanmış istemciler bile yeniden deneme yükünü biriktirir ve katmanlı yeniden denemeler çoğalır: eğer API ağ geçidiniz 3 kez ve hizmet istemciniz 3 kez yeniden denerse, tek bir kullanıcı tıklaması 9 isteğe dönüşebilir.
Hasarı sınırlayan iki mekanizma:
Yeniden deneme bütçeleri. "İstek başına 3 yeniden deneme" yerine, kayan bir pencere üzerinde ölçülen "yeniden denemeler en fazla %10 ekstra yük ekleyebilir" kuralını uygulayın. Bütçe harcandığında, hatalar hemen geri döner. Bu, aynı anda kaç isteğin başarısız olduğuna bakılmaksızın yeniden deneme amplifikasyonunu sınırlı tutar. Linkerd ve Envoy, bunu birinci sınıf bir yapılandırma olarak sunar.
Devre kesiciler. Her bir aşağı akış için hata oranını izleyin. Bir eşiği aştığında, kesici açılır: çağrılar ağı etkilemeden anında başarısız olur. Bir soğuma süresinden sonra, kesici tekrar kapanmadan önce bağımlılığın düzelip düzelmediğini test etmek için birkaç yoklama isteği gönderilir. Geri çekilme nazikçe yığılmayı yavaşlatırken, kesici onu iptal eder. Her ciddi yeniden deneme tasarımı ikisini eşleştirir, çünkü tek başına geri çekilme hala her isteği sonunda gönderir.
Python'da üretime hazır bir örnek
İşte tüm desen tek bir yerde: yeniden denenebilir durum filtreleme, tam jitter, Retry-After desteği, bir idempotens anahtarı ve katı bir yeniden deneme üst sınırı.
import random
import time
import uuid
import requests
RETRYABLE = {429, 502, 503, 504}
BASE = 0.5 # seconds
CAP = 30.0 # ceiling on any single delay
MAX_RETRIES = 5
def create_payment(payload):
idempotency_key = str(uuid.uuid4()) # one key per logical payment
headers = {"Idempotency-Key": idempotency_key}
for retry_count in range(MAX_RETRIES + 1):
try:
resp = requests.post(
"https://api.acmepay.com/v1/payments",
json=payload, headers=headers, timeout=10,
)
if resp.status_code < 400:
return resp.json()
if resp.status_code not in RETRYABLE:
resp.raise_for_status() # 400/401/403/422: fail fast
retry_after = resp.headers.get("Retry-After")
except (requests.ConnectionError, requests.Timeout):
retry_after = None # network fault: fall through to backoff
if retry_count == MAX_RETRIES:
raise RuntimeError("payment failed after all retries")
if retry_after and retry_after.isdigit():
delay = min(CAP, float(retry_after))
else:
delay = random.uniform(0, min(CAP, BASE * 2 ** retry_count))
time.sleep(delay)
Dikkat çekmeye değer: anahtar döngünün dışında, bir kez üretilir. Retry-After, hesaplanan geri çekilmeyi yener ancak yine de üst sınırı dikkate alır. Yeniden denenemeyen durumlar hemen yükseltilir. JavaScript tarafındaysanız, axios-retry kütüphanesi size retryCondition ve retryDelay kancalarıyla aynı şekli verir; karar tablosu aynı kalır.
Üretim ortamı sizin için yapmadan önce yeniden deneme davranışını nasıl test edersiniz
Çoğu ekip, hata dalını hiçbir zaman çalıştırmamış yeniden deneme kodu gönderir. Başarılı yol test edildi; 503 yolu gerçek bir kesinti sırasında ilk kez çalışır. İki Apidog özelliğiyle daha iyisini yapabilirsiniz.
- Sahte sunucularla hataları simüle edin. Apidog'un akıllı sahte sunucusu,
/v1/paymentsgibi bir uç nokta tanımlamanıza ve yanıtlarını betiklemenize olanak tanır. İlk iki çağrı için 503, üçüncü çağrı için 200 döndürmesini sağlayın veyaRetry-After: 5ile 429 döndürün ya da istemci zaman aşımınızı tetiklemek için 15 saniyelik bir gecikme ekleyin. İstemcinizi sahte URL'ye yönlendirin ve yeniden deneme döngüsünün her senaryoyu nasıl ele aldığını izleyin, üretim olayı gerekmez. - Test senaryolarıyla istemci davranışını doğrulayın. Apidog test senaryoları, iddialar ve zamanlama kontrolleriyle istekleri birbirine bağlar. Dengesiz sahte sunucunuza karşı tetiklenen ve çağrının sonunda başarılı olduğunu, toplam geçen sürenin beklenen geri çekilme zarfınızın içine düştüğünü ve tam olarak bir kaynak oluşturulduğunu (idempotens anahtarınızın işini yaptığını kanıtlayarak) doğrulayan bir senaryo oluşturun. Senaryoyu CI'ya bağlayın ve yeniden deneme mantığınız her kesintide değil, her taahhütte çalıştırılsın.
Bu, "yeniden denemeler ekledik" ile "istemcimizin hız sınırlı, yarı kapalı bir bağımlılıkta hayatta kaldığını doğruladık" arasındaki farktır. Apidog'u ücretsiz indirin ve yaklaşık on dakika içinde istemcinize karşı çalışan başarısız bir sahte sunucuya sahip olabilirsiniz.
SSS
429'u yeniden denemeli miyim?
Evet, ve sunucunun size genellikle nasıl yapılacağını söylediği tek durum budur. Retry-After başlığını okuyun ve en az o kadar bekleyin; başlık eksikse jitter ile üstel geri çekilmeye geri dönün. Ayrıca tekrarlanan 429'ları, normal bir işlem olarak değil, istemci tarafı kısıtlaması veya önbelleğe alma ile istek hızınızı düzeltmek için bir sinyal olarak kabul edin.
Tam jitter nedir?
Tam jitter, her yeniden deneme gecikmesini sıfır ile üstel tavan arasında rastgele, tekdüze bir şekilde seçer: random(0, min(cap, base * 2^n)). Birçok istemciden gelen senkronize yeniden deneme dalgalarını önler. AWS'nin simülasyonlarında, hem yapılan toplam çağrılarda hem de tamamlama süresinde basit geri çekilme ve eşit jitter'ı geride bıraktı, bu yüzden AWS SDK'larında varsayılan olarak kullanılır.
POST isteklerini yeniden denemek güvenli midir?
Yalnızca istek pratik olarak idempotent olduğunda, ki POST için bu, sunucunun tekrarlayanları ayıkladığı bir idempotens anahtarı göndermek anlamına gelir. Böyle bir anahtar olmadan, bir zaman aşımından sonra yapılan yeniden deneme bir ödemeyi, siparişi veya kaydı çoğaltabilir, çünkü sunucu başarısız olduğunu düşündüğünüz isteği işlemiş olabilir. Yazma API'lerini çağıran yapay zeka ajanları sürekli olarak bununla karşılaşır; ajan hata kurtarma modelleri burada ele alınanlarla aynıdır: anahtarlı yazmalar, sınırlı yeniden denemeler ve bir devre kesici.
Kaç kez yeniden denemeliyim?
Üç ila beş deneme neredeyse her geçici hatayı halleder; bunun ötesinde, başarı oranları sabitlenirken yük ve gecikme artmaya devam eder. Tam bir kesintinin yükünüzü artırmaması için istek başına üst sınırı global bir yeniden deneme bütçesiyle (örneğin, yeniden denemeler %10 ekstra trafik ekleyebilir) eşleştirin. Eğer bir bağımlılık son yeniden denemenizden sonra da kapalı kalıyorsa, bu yeniden deneme alanı değil, devre kesici alanıdır.
