Patroon voor opnieuw proberen

Azure

U kunt een toepassing de mogelijkheid bieden tijdelijke fouten af te handelen wanneer de toepassing probeert verbinding te maken met een service of netwerkresource door een mislukte bewerking transparant opnieuw te proberen. Hiermee kunt u de stabiliteit van de toepassing verbeteren.

Context en probleem

Een toepassing die communiceert met elementen die worden uitgevoerd in de cloud, moet gevoelig zijn voor de tijdelijke fouten die in deze omgeving kunnen optreden. Fouten zijn onder meer tijdelijk verlies van de netwerkverbinding met onderdelen en services, het tijdelijk niet beschikbaar zijn van een service of time-outs die zich voordoen wanneer een service bezet is.

Doorgaans corrigeren deze fouten zichzelf en als de actie die een fout heeft geactiveerd na een geschikte vertraging wordt herhaald, zal deze waarschijnlijk lukken. Een databaseservice die een groot aantal gelijktijdige aanvragen verwerkt, kan bijvoorbeeld een beperkingsstrategie implementeren die tijdelijk verdere aanvragen weigert totdat de workload is versoepeld. Een toepassing die probeert toegang te krijgen tot de database, kan mogelijk geen verbinding maken, maar als het na een vertraging opnieuw wordt geprobeerd, kan het wel lukken.

Oplossing

In de cloud zijn tijdelijke fouten niet ongebruikelijk en een toepassing moet zo worden ontworpen dat dergelijke fouten elegant en transparant worden afgehandeld. Dit verkleint het effect dat fouten kunnen hebben op de zakelijke taken die de toepassing uitvoert.

Als een toepassing een fout detecteert wanneer wordt geprobeerd een aanvraag naar een externe service te verzenden, kan de fout worden afgehandeld met behulp van de volgende strategieën:

  • Annuleren. Als de fout aangeeft dat het probleem niet tijdelijk is of als het onwaarschijnlijk is dat de bewerking slaagt wanneer deze wordt herhaald, moet de toepassing de bewerking annuleren en een uitzondering rapporteren. De kans dat een verificatiefout die wordt veroorzaakt door ongeldige referenties, de volgende keer slaagt, is klein, hoe vaak dit ook wordt geprobeerd.

  • Opnieuw proberen. Als de specifieke gerapporteerde fout ongebruikelijk of zeldzaam is, is deze mogelijk veroorzaakt door ongebruikelijke omstandigheden, zoals een netwerkpakket dat beschadigd is geraakt tijdens de overdracht. In dit geval kan de toepassing de mislukte aanvraag direct opnieuw proberen te verzenden omdat het onwaarschijnlijk is dat dezelfde fout zich opnieuw voordoet en de aanvraag waarschijnlijk lukt.

  • Opnieuw proberen na vertraging. Als de fout wordt veroorzaakt door een van de meer gebruikelijke verbindingsfouten of fouten omdat de service of het netwerk bezet is, heeft het netwerk of de service mogelijk een korte periode nodig terwijl de verbindingsproblemen worden opgelost of de werkachterstand wordt ingelopen. De toepassing moet wachten gedurende een geschikte periode voordat de aanvraag opnieuw wordt geprobeerd.

Voor de meer gangbare tijdelijke fouten moet de periode tussen nieuwe pogingen zo worden gekozen dat aanvragen van meerdere exemplaren van de toepassing zo gelijkmatig mogelijk worden verdeeld. Dit vermindert de kans dat een bezette service overbelast blijft. Als een service voortdurend wordt belast door nieuwe pogingen van veel exemplaren van een toepassing, duurt het langer voordat de service herstelt.

Als de aanvraag nog steeds mislukt, kan de toepassing wachten en een nieuwe poging doen. Zo nodig kan dit proces worden herhaald met toenemende vertragingen tussen nieuwe pogingen totdat een maximum aantal aanvragen is geprobeerd. De vertraging kan incrementeel of exponentieel worden vergroot, afhankelijk van het type fout en de kans dat deze in de loop der tijd wordt gecorrigeerd.

In het volgende diagram wordt een bewerking in een gehoste service aangeroepen met behulp van dit patroon. Als de aanvraag mislukt na een vooraf gedefinieerd aantal pogingen, behandelt de toepassing de fout als een uitzondering en wordt deze dienovereenkomstig verwerkt.

Afbeelding 1: een bewerking in een gehoste service aanroepen met het patroon voor opnieuw proberen

De toepassing moet alle pogingen om toegang te krijgen tot een externe service verpakken in code die een beleid voor opnieuw proberen implementeert dat overeenkomt met een van de hierboven genoemde strategieën. Voor aanvragen die naar verschillende services worden verzonden, kunnen verschillende soorten beleid gelden. Sommige leveranciers bieden bibliotheken die beleid voor opnieuw proberen implementeren, waarbij de toepassing het maximum aantal nieuwe pogingen, de tijd tussen pogingen en andere parameters kan opgeven.

Een toepassing moet de details van fouten en mislukte bewerkingen vastleggen in een logboek. Deze informatie is nuttig voor operators. Dat gezegd hebbende, is het raadzaam om te voorkomen dat operators over overstromingen waarschuwingen krijgen over bewerkingen waarbij nieuwe pogingen zijn geslaagd, het beste om vroege fouten te registreren als informatieve vermeldingen en alleen de fout van de laatste pogingspogingen als een werkelijke fout. Hier volgt een voorbeeld van hoe dit logboekregistratiemodel eruit zou zien.

Als een service regelmatig niet beschikbaar of bezet is, komt dit vaak doordat de service geen resources meer heeft. U kunt de frequentie van deze fouten verminderen door de service uit te breiden. Als een databaseservice bijvoorbeeld voortdurend wordt overbelast, kan het nuttig zijn de database te partitioneren en de belasting over meerdere servers te verdelen.

Microsoft Entity Framework biedt voorzieningen voor het opnieuw proberen van databasebewerkingen. Ook de meeste Azure-services en client-SDK's bevatten een mechanisme voor opnieuw proberen. Zie Richtlijnen voor opnieuw proberen voor specifieke services voor meer informatie.

Problemen en overwegingen

U moet de volgende punten overwegen wanneer u besluit hoe u dit patroon wilt implementeren.

Het beleid voor opnieuw proberen moet zijn afgestemd op de zakelijke vereisten van de toepassing en de aard van de fout. Voor bepaalde niet-kritieke bewerkingen is het beter dat ze snel mislukken dan dat ze meerdere keren opnieuw worden geprobeerd, hetgeen nadelig van invloed is op de doorvoer van de toepassing. In een interactieve webtoepassing die toegang heeft tot een externe service, is het bijvoorbeeld beter om te mislukken na een kleiner aantal nieuwe pogingen met slechts een korte vertraging tussen nieuwe pogingen en een geschikt bericht weer te geven aan de gebruiker (bijvoorbeeld 'probeer het later opnieuw'). Voor een batchtoepassing is het mogelijk beter het aantal nieuwe pogingen te verhogen met een exponentieel toenemende vertraging tussen de pogingen.

Bij een agressief beleid voor opnieuw proberen met een minimale vertraging tussen pogingen en een groot aantal nieuwe pogingen kunnen de prestaties van een bezette service die wordt uitgevoerd op (bijna) maximale capaciteit verder afnemen. Dit beleid voor opnieuw proberen kan ook van invloed zijn op de reactietijd van de toepassing als deze voortdurend probeert een mislukte bewerking uit te voeren.

Als een aanvraag na een groot aantal pogingen nog steeds mislukt, is het beter voor de toepassing om te voorkomen dat verdere aanvragen naar dezelfde resource worden verzonden en eenvoudig direct een fout te melden. Als de periode is verlopen, kan de toepassing als proef een of meer aanvragen toestaan om te kijken of ze slagen. Zie Circuitonderbrekerpatroon voor meer informatie over deze strategie.

Overweeg of de bewerking idempotent is. Als dit het geval is, is het inherent veilig om het opnieuw te proberen. Zo niet, dan kunnen nieuwe pogingen ertoe leiden dat de bewerking meer dan eenmaal wordt uitgevoerd met onbedoelde neveneffecten. Een service kan bijvoorbeeld de aanvraag ontvangen en deze met succes verwerken, waarna het verzenden van een antwoord mislukt. Op dat punt kan de logica voor opnieuw proberen de aanvraag mogelijk opnieuw verzenden, omdat ervan wordt uitgegaan dat de eerste aanvraag niet is ontvangen.

Een aanvraag naar een service kan mislukken door allerlei oorzaken die verschillende uitzonderingen genereren, afhankelijk van de aard van de fout. Sommige uitzonderingen wijzen op een fout die snel kan worden opgelost, terwijl anderen aangeven dat de fout langduriger is. Het beleid voor opnieuw proberen kan het beste de tijd tussen pogingen aanpassen op basis van het type uitzondering.

Houd er rekening mee hoe het opnieuw proberen van een bewerking die deel uitmaakt van een transactie van invloed is op de algehele consistentie van de transactie. Stem het beleid voor opnieuw proberen af op transactionele bewerkingen om de kans van slagen te maximaliseren en de noodzaak om alle stappen van de transactie ongedaan te maken te verminderen.

Zorg ervoor dat alle code voor nieuwe pogingen volledig is getest op basis van allerlei foutomstandigheden. Controleer of dit geen grote nadelige invloed op de prestaties of de betrouwbaarheid van de toepassing heeft, overmatige belasting van services en resources veroorzaakt of racevoorwaarden of knelpunten creëert.

Implementeer logica voor opnieuw proberen alleen als u de volledige context van een bewerking begrijpt. Als een taak die een beleid voor opnieuw proberen bevat, bijvoorbeeld een andere taak aanroept die ook een beleid voor opnieuw proberen bevat, kan deze extra laag van nieuwe pogingen lange vertragingen aan de verwerking toevoegen. Het is mogelijk beter de taak op het lagere niveau zo te configureren dat deze snel mislukt en de reden voor de fout terug te melden aan de taak die de lagere taak heeft aangeroepen. Deze taak op het hogere niveau kan vervolgens de fout afhandelen op basis van het eigen beleid.

Het is belangrijk om alle verbindingsfouten die tot een nieuwe poging leiden vast te leggen in een logboek, zodat onderliggende problemen met de toepassing, services of resources kunnen worden geïdentificeerd.

Onderzoek de fouten waarvan de kans het grootst is dat ze optreden voor een service of resource om te achterhalen of ze waarschijnlijk langdurig of terminaal zijn. Als dat het geval, is het beter om de fout af te handelen als een uitzondering. De toepassing kan de uitzondering rapporteren of vastleggen en vervolgens proberen door te gaan door een alternatieve service aan te roepen (indien beschikbaar) of door verminderde functionaliteit te bieden. Zie Circuitonderbrekerpatroon voor meer informatie over het detecteren en afhandelen van langdurige fouten.

Wanneer dit patroon gebruiken

Gebruik dit patroon wanneer een toepassing tijdelijke fouten kan ondervinden tijdens interactie met een externe service of toegang tot een externe resource. Deze fouten zijn naar verwachting van korte duur en als een aanvraag die eerder is mislukt, wordt herhaald, kan de volgende poging mogelijk wel lukken.

Dit patroon is mogelijk niet geschikt:

  • Wanneer een fout waarschijnlijk langdurig is, omdat dit de reactiesnelheid van een toepassing kan beïnvloeden. De toepassing kan tijd en resources verspillen doordat wordt geprobeerd een aanvraag te herhalen die waarschijnlijk zal mislukken.
  • Voor het afhandelen van problemen die niet worden veroorzaakt door tijdelijke fouten, zoals interne uitzonderingen veroorzaakt door fouten in de bedrijfslogica van een toepassing.
  • Als alternatief voor het oplossen van schaalbaarheidsproblemen in een systeem. Als een toepassing vaak fouten ondervindt omdat de service of resource bezet is, is dit vaak een teken dat de desbetreffende service of resource moet worden uitgebreid.

Workloadontwerp

Een architect moet evalueren hoe het patroon Voor opnieuw proberen kan worden gebruikt in het ontwerp van hun workload om de doelstellingen en principes te verhelpen die worden behandeld in de pijlers van het Azure Well-Architected Framework. Voorbeeld:

Pijler Hoe dit patroon ondersteuning biedt voor pijlerdoelen
Beslissingen over betrouwbaarheidsontwerp helpen uw workload bestand te worden tegen storingen en ervoor te zorgen dat deze herstelt naar een volledig functionerende status nadat er een fout is opgetreden. Tijdelijke fouten in een gedistribueerd systeem beperken is een kerntechniek voor het verbeteren van de tolerantie van een workload.

- RE:07 Zelfbehoud
- RE:07 Tijdelijke fouten

Net als bij elke ontwerpbeslissing moet u rekening houden met eventuele compromissen ten opzichte van de doelstellingen van de andere pijlers die met dit patroon kunnen worden geïntroduceerd.

Opmerking

In dit voorbeeld in C# ziet u een implementatie van het patroon voor opnieuw proberen. De methode OperationWithBasicRetryAsync, hieronder weergegeven, roept een externe service asynchroon aan via de methode TransientOperationAsync. De details van de methode TransientOperationAsync zijn specifiek voor de service en zijn weggelaten uit de voorbeeldcode.

private int retryCount = 3;
private readonly TimeSpan delay = TimeSpan.FromSeconds(5);

public async Task OperationWithBasicRetryAsync()
{
  int currentRetry = 0;

  for (;;)
  {
    try
    {
      // Call external service.
      await TransientOperationAsync();

      // Return or break.
      break;
    }
    catch (Exception ex)
    {
      Trace.TraceError("Operation Exception");

      currentRetry++;

      // Check if the exception thrown was a transient exception
      // based on the logic in the error detection strategy.
      // Determine whether to retry the operation, as well as how
      // long to wait, based on the retry strategy.
      if (currentRetry > this.retryCount || !IsTransient(ex))
      {
        // If this isn't a transient error or we shouldn't retry,
        // rethrow the exception.
        throw;
      }
    }

    // Wait to retry the operation.
    // Consider calculating an exponential delay here and
    // using a strategy best suited for the operation and fault.
    await Task.Delay(delay);
  }
}

// Async method that wraps a call to a remote service (details not shown).
private async Task TransientOperationAsync()
{
  ...
}

De instructie die deze methode aanroept, maakt deel uit van een try/catch-blok ingepakt in een for-lus. De for-lus wordt afgesloten als de aanroep van de methode TransientOperationAsync slaagt zonder dat een uitzondering wordt gegenereerd. Als de methode TransientOperationAsync mislukt, onderzoekt het catch-blok de oorzaak van het probleem. Als het een tijdelijke fout lijkt te zijn, wacht de code gedurende een korte vertraging voordat de bewerking opnieuw wordt geprobeerd.

De for-lus houdt ook bij hoe vaak de bewerking is geprobeerd en als de code driemaal mislukt, wordt ervan uitgegaan dat de uitzondering langduriger is. Als de uitzondering niet tijdelijk is of als deze langdurig is, genereert de catch-handler een uitzondering. Deze uitzondering verlaat de for-lus en moet worden opgevangen door de code die de methode OperationWithBasicRetryAsync aanroept.

De methode IsTransient, hieronder weergegeven, controleert op een specifieke set uitzonderingen die relevant zijn voor de omgeving waarin de code wordt uitgevoerd. De definitie van een tijdelijke uitzondering varieert, afhankelijk van de resources waartoe toegang wordt verkregen en de omgeving waarin de bewerking wordt uitgevoerd.

private bool IsTransient(Exception ex)
{
  // Determine if the exception is transient.
  // In some cases this is as simple as checking the exception type, in other
  // cases it might be necessary to inspect other properties of the exception.
  if (ex is OperationTransientException)
    return true;

  var webException = ex as WebException;
  if (webException != null)
  {
    // If the web exception contains one of the following status values
    // it might be transient.
    return new[] {WebExceptionStatus.ConnectionClosed,
                  WebExceptionStatus.Timeout,
                  WebExceptionStatus.RequestCanceled }.
            Contains(webException.Status);
  }

  // Additional exception checking logic goes here.
  return false;
}

Volgende stappen

  • Voordat u aangepaste logica voor opnieuw proberen schrijft, kunt u overwegen een algemeen framework te gebruiken, zoals Polly voor .NET of Resilience4j voor Java.

  • Wanneer u opdrachten verwerkt die bedrijfsgegevens wijzigen, moet u er rekening mee houden dat nieuwe pogingen ertoe kunnen leiden dat de actie tweemaal wordt uitgevoerd. Dit kan problematisch zijn als deze actie iets is als het opladen van de creditcard van een klant. Het gebruik van het Idempotentiepatroon dat in dit blogbericht wordt beschreven, kan helpen bij het omgaan met deze situaties.

  • Betrouwbaar web-app-patroon laat zien hoe u het patroon voor opnieuw proberen kunt toepassen op webtoepassingen die in de cloud worden samengevoegd.

  • Voor de meeste Azure-services bevatten de client-SDK's ingebouwde logica voor opnieuw proberen. Zie Richtlijnen voor opnieuw proberen voor Azure-services voor meer informatie.

  • Circuitonderbrekerpatroon. Als een fout naar verwachting langduriger is, is het mogelijk beter om een circuitonderbrekerpatroon te implementeren. Het combineren van de patronen voor opnieuw proberen en circuitonderbreker biedt een uitgebreide benadering voor het afhandelen van fouten.