7 Retry Muster, die jeder Backend-Entwickler kennen sollte
Wenhao Wang
Dev Intern · Leapcell

Vorwort
In Geschäftssystemen ist das Scheitern die Norm. Ob aufgrund von Netzwerkschwankungen, Serviceüberlastung oder instabilen Drittanbieterschnittstellen, ein System muss "selbstheilende" Fähigkeiten besitzen, um gelegentliche Anomalien zu bewältigen.
Retry-Mechanismen sind eine der Kernkomponenten der Selbstheilungsfähigkeit eines Systems.
Retries sind jedoch ein zweischneidiges Schwert: Bei guter Konzeption verbessern sie die Erfolgsraten und die Benutzererfahrung; bei schlechter Konzeption können sie zu Request-Stürmen, kaskadierenden Ausfällen und sogar zur Verstärkung von Problemen bis hin zu Vorfällen führen.
Dieser Artikel behandelt 7 häufig verwendete Retry-Strategien.
1. Brute-Force-Schleife
Problemszenario
Eine Benutzeroberfläche zum Versenden von SMS zur Benutzerregistrierung ruft wiederholt eine SMS-API eines Drittanbieters in einer While-Schleife auf.
Codebeispiel:
public void sendSms(String phone) { int retry = 0; while (retry < 5) { // Brute-Force-Schleife try { smsClient.send(phone); break; } catch (Exception e) { retry++; Thread.sleep(1000); // Feste Verzögerung von 1 Sekunde } } }
Vorfall
Der SMS-Server war überlastet, was dazu führte, dass sich jede Anfrage um 3 Sekunden verzögerte.
Dieser Brute-Force-Code löste innerhalb von 0,5 Sekunden Zehntausende von Wiederholungsversuchen aus, überlastete die SMS-Plattform und löste einen Circuit Breaker aus, der sogar normale Anfragen ablehnte.
Lehren:
- Keine Anpassung des Verzögerungsintervalls: Feste Verzögerung verursachte Retry-Bursts
- Ignorierte Ausnahmetypen: Wiederholungsversuche auch bei nicht-transienten Fehlern (z. B. ungültige Parameter)
- Korrektur: Einführung zufälliger Verzögerungen und Herausfiltern von nicht wiederholbaren Ausnahmen
2. Spring Retry
Anwendungsfall
Spring Retry eignet sich für kleine bis mittelgroße Projekte und ermöglicht die schnelle Implementierung grundlegender Retries und Circuit Breaking (z. B. Order-Status-Abfrage-APIs) durch Annotationen.
Die Verwendung der @Retryable
-Annotation implementiert die Retry-Logik.
Konfigurationsbeispiel
@Retryable( value = {TimeoutException.class}, // Retry nur bei Timeout maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2) // 1s → 2s → 4s ) public boolean queryOrderStatus(String orderId) { return httpClient.get("/order/" + orderId); } @Recover // Fallback-Methode public boolean fallback() { return false; }
Vorteile
- Deklarative Annotationen: sauberer Code, entkoppelt von der Geschäftslogik
- Exponentielles Backoff: Erhöht automatisch die Retry-Intervalle
- Circuit-Breaker-Integration: Kombiniert mit
@CircuitBreaker
, um den Fehler-Traffic schnell zu blockieren
3. Resilience4j
Fortgeschrittenes Szenario
Für komplexere Systeme, die benutzerdefinierte Backoff-Algorithmen, Circuit-Breaker-Strategien und mehrschichtigen Schutz benötigen (z. B. Core-Payment-APIs), wird Resilience4j empfohlen.
Kerncode:
// 1. Retry-Konfiguration: Exponentielles Backoff + Jitter RetryConfig retryConfig = RetryConfig.custom() .maxAttempts(3) .intervalFunction(IntervalFunction.ofExponentialRandomBackoff( 1000L, // anfängliche Verzögerung von 1 Sekunde 2.0, // exponentieller Multiplikator 0.3 // Jitter-Faktor )) .retryOnException(e -> e instanceof TimeoutException) .build(); // 2. Circuit-Breaker-Konfiguration: Auslösen, wenn die Fehlerrate > 50 % ist CircuitBreakerConfig cbConfig = CircuitBreakerConfig.custom() .slidingWindow(10, 10, CircuitBreakerConfig.SlidingWindowType.COUNT_BASED) .failureRateThreshold(50) .build(); // Kombinierte Verwendung Retry retry = Retry.of("payment", retryConfig); CircuitBreaker cb = CircuitBreaker.of("payment", cbConfig); // Geschäftslogik ausführen Supplier<Boolean> supplier = () -> paymentService.pay(); Supplier<Boolean> decorated = Decorators.ofSupplier(supplier) .withRetry(retry) .withCircuitBreaker(cb) .decorate();
Ergebnis
Nach der Bereitstellung dieser Lösung sank die Timeout-Rate der Payment-API eines Unternehmens um 60 %, und die Häufigkeit der Circuit-Breaker-Auslösungen sank um fast 90 %.
4. MQ-Queue
Anwendungsfall
Asynchrone Szenarien mit hoher Parallelität und Verzögerungstoleranz (z. B. Logistikstatus-Synchronisierung).
Implementierungsprinzip
- Wenn die erste Anfrage fehlschlägt, wird die Nachricht an eine Delay-Queue gesendet.
- Die Queue wiederholt den Nachrichtenverbrauch nach einer voreingestellten Verzögerung (z. B. 5s, 30s, 1min).
- Wenn die maximale Anzahl an Wiederholungsversuchen erreicht ist, wird die Nachricht zur manuellen Bearbeitung in eine Dead-Letter-Queue verschoben.
RocketMQ-Code-Snippet:
// Producer sendet verzögerte Nachricht Message<String> message = new Message(); message.setBody("Bestelldaten"); message.setDelayTimeLevel(3); // RocketMQ Level 3 = 10s Verzögerung rocketMQTemplate.send(message); // Consumer wiederholt @RocketMQMessageListener(topic = "DELAY_TOPIC") public class DelayConsumer { @Override public void handleMessage(Message message) { try { syncLogistics(message); } catch (Exception e) { // Retry mit erhöhtem Delay Level resendWithDelay(message, retryCount + 1); } } }
RocketMQ wiederholt fehlgeschlagene Consumer-Operationen automatisch.
5. Geplante Aufgabe
Anwendungsfall
Für Aufgaben, die keine Echtzeitreaktion erfordern und die Batch-Verarbeitung zulassen (z. B. Dateiimport), können geplante Jobs verwendet werden.
Beispiel mit Quartz:
@Scheduled(cron = "0 0/5 * * * ?") // alle 5 Minuten ausführen public void retryFailedTasks() { List<FailedTask> list = failedTaskDao.listUnprocessed(5); // fehlgeschlagene Aufgaben abfragen list.forEach(task -> { try { retryTask(task); task.markSuccess(); } catch (Exception e) { task.incrRetryCount(); } failedTaskDao.update(task); }); }
6. Zwei-Phasen-Commit
Anwendungsfall
Für Szenarien, die eine strikte Datenkonsistenz erfordern (z. B. Geldtransfers), kann der Zwei-Phasen-Commit-Mechanismus verwendet werden.
Wichtige Implementierung
- Phase Eins: Erfassen der Transaktion in der Datenbank (Status auf "pending" setzen).
- Phase Zwei: Aufrufen der Remote-Schnittstelle und Aktualisieren des Transaktionsstatus basierend auf dem Ergebnis.
- Kompensationsaufgabe: Periodisches Scannen und Wiederholen von "pending"-Transaktionen, bei denen ein Timeout aufgetreten ist.
Beispielcode:
@Transactional public void transfer(TransferRequest req) { // 1. Erfassen der Transaktion transferRecordDao.create(req, PENDING); // 2. Aufrufen der Bank-API boolean success = bankClient.transfer(req); // 3. Aktualisieren des Transaktionsstatus transferRecordDao.updateStatus(req.getId(), success ? SUCCESS : FAILED); // 4. Asynchrones Wiederholen, falls fehlgeschlagen if (!success) { mqTemplate.send("TRANSFER_RETRY_QUEUE", req); } }
7. Verteilte Sperre
Anwendungsfall
In Szenarien, die mehrere Service-Instanzen oder Multi-Threaded-Umgebungen umfassen, in denen Idempotenz von entscheidender Bedeutung ist (z. B. Flash-Sales), kann eine verteilte Sperre verwendet werden.
Beispiel mit Redis + Lua für verteilte Sperren:
public boolean retryWithLock(String key, int maxRetry) { String lockKey = "api_retry_lock:" + key; for (int i = 0; i < maxRetry; i++) { // Versuch, eine verteilte Sperre zu erlangen if (redis.setnx(lockKey, "1", 30, TimeUnit.SECONDS)) { try { return callApi(); } finally { redis.delete(lockKey); } } Thread.sleep(1000 * (i + 1)); // Vor dem Wiederholungsversuch warten } return false; }
Zusammenfassung
Retry-Mechanismen sind wie Feuerlöscher in einem Rechenzentrum – man hofft, sie nie benutzen zu müssen, aber wenn eine Katastrophe eintritt, sind sie möglicherweise die letzte Verteidigungslinie.
Welche Lösung sollten wir bei der Arbeit wählen?
Folgen Sie nicht einfach den neuesten technischen Trends. Wählen Sie basierend auf dem Gleichgewicht zwischen Angriff und Verteidigung, das Ihr Unternehmen benötigt.
Der Schlüssel zur Systemstabilität liegt darin, Retries immer mit Respekt zu behandeln.
Wir sind Leapcell, Ihre erste Wahl für das Hosten von Backend-Projekten.
Leapcell ist die Serverless-Plattform der nächsten Generation für Webhosting, asynchrone Aufgaben und Redis:
Multi-Language-Unterstützung
- Entwickeln Sie mit Node.js, Python, Go oder Rust.
Unbegrenzte Projekte kostenlos bereitstellen
- Zahlen Sie nur für die tatsächliche Nutzung – keine Anfragen, keine Gebühren.
Unschlagbare Kosteneffizienz
- Pay-as-you-go ohne Leerlaufgebühren.
- Beispiel: 25 US-Dollar unterstützen 6,94 Millionen Anfragen bei einer durchschnittlichen Antwortzeit von 60 ms.
Optimierte Entwicklererfahrung
- Intuitive Benutzeroberfläche für mühelose Einrichtung.
- Vollautomatische CI/CD-Pipelines und GitOps-Integration.
- Echtzeitmetriken und -protokollierung für verwertbare Erkenntnisse.
Mühelose Skalierbarkeit und hohe Leistung
- Automatische Skalierung zur mühelosen Bewältigung hoher Parallelität.
- Kein betrieblicher Aufwand – konzentrieren Sie sich einfach auf den Aufbau.
Erfahren Sie mehr in der Dokumentation!
Folgen Sie uns auf X: @LeapcellHQ