Abilitare i nuovi tentativi per le funzioni basate su eventi (1ª generazione.)
Questo documento descrive come attivare i nuovi tentativi per le funzioni basate su eventi. I nuovi tentativi automatici non sono disponibili per le funzioni HTTP.
Semantica del nuovo tentativo
Cloud Run Functions fornisce l'esecuzione almeno una volta di una funzione basata su eventi per ogni evento emesso da un'origine eventi. Per impostazione predefinita, se una chiamata di funzione termina con un errore, la funzione non viene richiamata di nuovo e l'evento viene eliminato. Quando abiliti i tentativi su una funzione basata su eventi, Cloud Run Functions riprova a richiamare una funzione non riuscita finché non viene completata correttamente o la finestra di tentativi scade.
Questo periodo di nuovi tentativi scade dopo 7 giorni. Cloud Run Functions riprova le funzioni basate su eventi appena create utilizzando una strategia di backoff esponenziale, con un backoff crescente compreso tra 10 e 600 secondi. Queste norme vengono applicate alle nuove funzioni la prima volta che le esegui il deployment. Non viene applicata retroattivamente alle funzioni esistenti che sono state implementate per la prima volta prima dell'entrata in vigore delle modifiche descritte in queste note di rilascio, anche se le implementi di nuovo.
Quando i tentativi non sono attivati per una funzione, ovvero l'impostazione predefinita, la funzione
segnala sempre che è stata eseguita correttamente e nei log potrebbero
essere visualizzati codici di risposta 200 OK
. Ciò si verifica anche se la funzione ha riscontrato un errore. Per
rendere chiaro quando la funzione rileva un errore, assicurati di
segnalare gli errori
in modo appropriato.
Perché le funzioni basate su eventi non vengono completate
In rare occasioni, una funzione potrebbe terminare prematuramente a causa di un errore interno e, per impostazione predefinita, il tentativo di esecuzione della funzione potrebbe essere ripetuto o meno automaticamente.
In genere, una funzione basata su eventi potrebbe non essere completata correttamente a causa di errori generati nel codice della funzione stesso. I motivi per cui ciò potrebbe accadere includono:
- La funzione contiene un bug e il runtime genera un'eccezione.
- La funzione non riesce a raggiungere un endpoint di servizio o si verifica un timeout durante il tentativo di farlo.
- La funzione genera intenzionalmente un'eccezione (ad esempio, quando la convalida di un parametro non va a buon fine).
- Una funzione Node.js restituisce una promessa rifiutata o passa un valore non
null
a un callback.
In uno qualsiasi di questi casi, l'esecuzione della funzione viene interrotta per impostazione predefinita e l'evento viene eliminato. Per riprovare a eseguire la funzione quando si verifica un errore, puoi modificare il criterio di ripetizione predefinito impostando la proprietà "retry on failure" (riprova in caso di errore). In questo modo, l'evento viene ritentato ripetutamente finché la funzione non viene completata correttamente o finché non scade il timeout dei tentativi.
Attivare o disattivare i tentativi
Per attivare o disattivare i tentativi, puoi utilizzare lo strumento a riga di comando gcloud
o la console Google Cloud . Per impostazione predefinita, i tentativi vengono disattivati.
Configurare i tentativi dalla riga di comando dello strumento gcloud
Per attivare i tentativi utilizzando lo strumento a riga di comando gcloud
, includi il flag --retry
quando esegui il deployment della funzione:
gcloud functions deploy FUNCTION_NAME --retry FLAGS...
Per disattivare i tentativi, esegui nuovamente il deployment della funzione senza il flag --retry
:
gcloud functions deploy FUNCTION_NAME FLAGS...
Configura i tentativi dalla console
Se stai creando una nuova funzione:
- Nella schermata Crea funzione, in Attivatore, scegli il tipo di evento da utilizzare come attivatore per la tua funzione.
- Seleziona la casella di controllo Riprova in caso di errore per attivare i tentativi.
Se stai aggiornando una funzione esistente:
- Nella pagina Panoramica delle funzioni Cloud Run, fai clic sul nome della funzione che stai aggiornando per aprire la schermata Dettagli funzione, quindi scegli Modifica dalla barra dei menu per visualizzare il riquadro Trigger.
- Seleziona o deseleziona la casella di controllo Riprova in caso di errore per attivare o disattivare i tentativi.
Best practice
Questa sezione descrive le best practice per l'utilizzo dei tentativi.
Utilizzare i tentativi per gestire gli errori temporanei
Poiché la funzione viene ritentata continuamente fino all'esecuzione riuscita, gli errori permanenti come i bug devono essere eliminati dal codice tramite test prima di attivare i nuovi tentativi. I tentativi vengono utilizzati al meglio per gestire errori intermittenti o temporanei che hanno un'alta probabilità di risoluzione al nuovo tentativo, ad esempio un endpoint di servizio instabile o un timeout.
Imposta una condizione di fine per evitare loop di tentativi ripetuti all'infinito
La best practice consiste nel proteggere la funzione da loop continui quando utilizzi i tentativi. Puoi farlo includendo una condizione di fine ben definita prima che la funzione inizi l'elaborazione. Tieni presente che questa tecnica funziona solo se la funzione viene avviata correttamente ed è in grado di valutare la condizione finale.
Un approccio semplice ma efficace è quello di eliminare gli eventi con timestamp precedenti a un determinato periodo di tempo. In questo modo si evitano esecuzioni eccessive quando gli errori sono persistenti o di durata superiore al previsto.
Ad esempio, questo snippet di codice elimina tutti gli eventi più vecchi di 10 secondi:
Node.js
Python
Go
Java
C#
using CloudNative.CloudEvents; using Google.Cloud.Functions.Framework; using Google.Events.Protobuf.Cloud.PubSub.V1; using Microsoft.Extensions.Logging; using System; using System.Threading; using System.Threading.Tasks; namespace TimeBoundedRetries; public class Function : ICloudEventFunction<MessagePublishedData> { private static readonly TimeSpan MaxEventAge = TimeSpan.FromSeconds(10); private readonly ILogger _logger; // Note: for additional testability, use an injectable clock abstraction. public Function(ILogger<Function> logger) => _logger = logger; public Task HandleAsync(CloudEvent cloudEvent, MessagePublishedData data, CancellationToken cancellationToken) { string textData = data.Message.TextData; DateTimeOffset utcNow = DateTimeOffset.UtcNow; // Every PubSub CloudEvent will contain a timestamp. DateTimeOffset timestamp = cloudEvent.Time.Value; DateTimeOffset expiry = timestamp + MaxEventAge; // Ignore events that are too old. if (utcNow > expiry) { _logger.LogInformation("Dropping PubSub message '{text}'", textData); return Task.CompletedTask; } // Process events that are recent enough. // If this processing throws an exception, the message will be retried until either // processing succeeds or the event becomes too old and is dropped by the code above. _logger.LogInformation("Processing PubSub message '{text}'", textData); return Task.CompletedTask; } }
Ruby
PHP
/** * This function shows an example method for avoiding infinite retries in * Google Cloud Functions. By default, functions configured to automatically * retry execution on failure will be retried indefinitely - causing an * infinite loop. To avoid this, we stop retrying executions (by not throwing * exceptions) for any events that are older than a predefined threshold. */ use Google\CloudFunctions\CloudEvent; function avoidInfiniteRetries(CloudEvent $event): void { $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb'); $eventId = $event->getId(); // The maximum age of events to process. $maxAge = 10; // 10 seconds // The age of the event being processed. $eventAge = time() - strtotime($event->getTime()); // Ignore events that are too old if ($eventAge > $maxAge) { fwrite($log, 'Dropping event ' . $eventId . ' with age ' . $eventAge . ' seconds' . PHP_EOL); return; } // Do what the function is supposed to do fwrite($log, 'Processing event: ' . $eventId . ' with age ' . $eventAge . ' seconds' . PHP_EOL); // infinite_retries failed function executions $failed = true; if ($failed) { throw new Exception('Event ' . $eventId . ' failed; retrying...'); } }
Distinguere tra le funzioni che possono essere riprovate e gli errori irreversibili
Se i tentativi sono abilitati per la funzione, qualsiasi errore non gestito attiverà un nuovo tentativo. Assicurati che il codice acquisisca eventuali errori che non devono comportare un nuovo tentativo.
Node.js
Python
Go
Java
C#
using CloudNative.CloudEvents; using Google.Cloud.Functions.Framework; using Google.Events.Protobuf.Cloud.PubSub.V1; using Microsoft.Extensions.Logging; using System; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace Retry; public class Function : ICloudEventFunction<MessagePublishedData> { private readonly ILogger _logger; public Function(ILogger<Function> logger) => _logger = logger; public Task HandleAsync(CloudEvent cloudEvent, MessagePublishedData data, CancellationToken cancellationToken) { bool retry = false; string text = data.Message?.TextData; // Get the value of the "retry" JSON parameter, if one exists. if (!string.IsNullOrEmpty(text)) { JsonElement element = JsonSerializer.Deserialize<JsonElement>(data.Message.TextData); retry = element.TryGetProperty("retry", out var property) && property.ValueKind == JsonValueKind.True; } // Throwing an exception causes the execution to be retried. if (retry) { throw new InvalidOperationException("Retrying..."); } else { _logger.LogInformation("Not retrying..."); } return Task.CompletedTask; } }
Ruby
PHP
use Google\CloudFunctions\CloudEvent; function tipsRetry(CloudEvent $event): void { $cloudEventData = $event->getData(); $pubSubData = $cloudEventData['message']['data']; $json = json_decode(base64_decode($pubSubData), true); // Determine whether to retry the invocation based on a parameter $tryAgain = $json['some_parameter']; if ($tryAgain) { /** * Functions with automatic retries enabled should throw exceptions to * indicate intermittent failures that a retry might fix. In this * case, a thrown exception will cause the original function * invocation to be re-sent. */ throw new Exception('Intermittent failure occurred; retrying...'); } /** * If a function with retries enabled encounters a non-retriable * failure, it should return *without* throwing an exception. */ $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb'); fwrite($log, 'Not retrying' . PHP_EOL); }
Rendere idempotenti le funzioni basate su eventi ripetibili
Le funzioni basate su eventi che possono essere riprovate devono essere idempotenti. Ecco alcune linee guida generali per rendere idempotente una funzione:
- Molte API esterne (come Stripe) ti consentono di fornire una chiave di idempotenza come parametro. Se utilizzi un'API di questo tipo, devi utilizzare l'ID evento come chiave di idempotenza.
- L'idempotenza funziona bene con la consegna "at-least-once", perché consente di riprovare in sicurezza. Pertanto, una best practice generale per scrivere codice affidabile è combinare l'idempotenza con i tentativi.
- Assicurati che il codice sia idempotente internamente. Ad esempio:
- Assicurati che le mutazioni possano verificarsi più di una volta senza modificare il risultato.
- Esegui query sullo stato del database in una transazione prima di modificarlo.
- Assicurati che tutti gli effetti collaterali siano idempotenti.
- Imponi un controllo transazionale al di fuori della funzione, indipendentemente dal codice. Ad esempio, mantieni lo stato in un punto in cui viene registrato che un determinato ID evento è già stato elaborato.
- Gestisci le chiamate di funzione duplicate fuori banda. Ad esempio, esegui una pulizia separata dopo le chiamate di funzioni duplicate.
Configura il criterio per i tentativi ripetuti
A seconda delle esigenze della tua funzione, potresti voler configurare direttamente la policy di ripetizione dei tentativi. In questo modo potrai configurare qualsiasi combinazione dei seguenti elementi:
- Ridurre la finestra di nuovi tentativi da 7 giorni a un minimo di 10 minuti.
- Modifica il tempo di backoff minimo e massimo per la strategia di ripetizione di backoff esponenziale.
- Modifica la strategia di ripetizione per riprovare immediatamente.
- Configura un argomento messaggi non recapitabili.
- Imposta un numero massimo e minimo di tentativi di consegna.
Per configurare i criteri di ripetizione:
- Scrivi una funzione HTTP.
- Utilizza l'API Pub/Sub per creare un abbonamento Pub/Sub, specificando l'URL della funzione come target.
Per ulteriori informazioni sulla configurazione diretta di Pub/Sub, consulta la documentazione di Pub/Sub sulla gestione degli errori.
Passaggi successivi
- Deployment di Cloud Run Functions.
- Chiamata delle funzioni di trigger Pub/Sub.
- Chiamata delle funzioni di attivazione di Cloud Storage.
- Tutorial su Cloud Run Functions con Pub/Sub.
- Tutorial su Cloud Run Functions con Cloud Storage.