Policy di ripetizione nelle librerie client C++

In questa pagina viene descritto il modello di ripetizione dei tentativi utilizzato dalle librerie client C++.

Le librerie client emettono RPC (chiamate di procedura remota) per tuo conto. Queste RPC possono non riuscire a causa di errori temporanei. I riavvii dei server, i bilanciatori del carico che chiudono le connessioni sovraccariche o inattive e l'applicazione dei limiti di frequenza sono solo alcuni esempi di errori temporanei.

Le librerie potrebbero restituire questi errori all'applicazione. Tuttavia, molti di questi errori sono facili da gestire nella libreria, il che semplifica il codice dell'applicazione.

Errori e operazioni non irreversibili

Solo gli errori temporanei sono non irreversibili. Ad esempio, kUnavailable indica che il client non è riuscito a connettersi o ha perso la connessione a un servizio mentre era in corso una richiesta. Si tratta quasi sempre di una condizione temporanea, anche se il ripristino potrebbe richiedere molto tempo. Questi errori sono sempre non irreversibili (supponendo che l'operazione stessa sia sicura da riprovare). Al contrario, gli errori kPermissionDenied richiedono un intervento aggiuntivo (di solito da parte di una persona) per essere risolti. Questi errori non sono considerati "temporanei" o almeno non temporanei nelle tempistiche considerate dai loop di ripetizione dei tentativi nella libreria client.

Allo stesso modo, alcune operazioni non sono sicure da riprovare, indipendentemente dalla natura dell'errore. Sono incluse tutte le operazioni che apportano modifiche incrementali. Ad esempio, non è sicuro riprovare un'operazione per rimuovere "l'ultima versione di X" dove potrebbero esistere più versioni di una risorsa denominata "X". Questo perché il chiamante probabilmente intendeva rimuovere una singola versione e riprovare una richiesta di questo tipo può comportare la rimozione di tutte le versioni.

Configurare i loop di ripetizione dei tentativi

Le librerie client accettano tre parametri di configurazione diversi per controllare i loop di ripetizione dei tentativi:

  • *IdempotencyPolicy determina se una determinata richiesta è idempotente. Vengono riprovate solo queste richieste.
  • *RetryPolicy determina (a) se un errore deve essere considerato un errore temporaneo e (b) per quanto tempo (o quante volte) la libreria client riprova una richiesta.
  • *BackoffPolicy determina per quanto tempo la libreria client attende prima di riemettere la richiesta.

Norma di idempotenza predefinita

In generale, un'operazione è idempotente se la chiamata riuscita della funzione più volte lascia il sistema nello stesso stato della chiamata riuscita della funzione una volta. Solo le operazioni idempotenti sono sicure da riprovare. Esempi di operazioni idempotenti includono, a titolo esemplificativo, tutte le operazioni di sola lettura e le operazioni che possono avere esito positivo una sola volta.

Per impostazione predefinita, la libreria client tratta come idempotenti solo le RPC implementate tramite i verbi GET o PUT. In alcuni servizi, anche alcune richieste POST sono idempotenti. Puoi sempre sostituire la norma di idempotenza predefinita per adattarla meglio alle tue esigenze.

Alcune operazioni sono idempotenti solo se includono precondizioni. Ad esempio, "rimuovi l'ultima versione se l'ultima versione è Y" è idempotente, in quanto può avere esito positivo una sola volta.

Di tanto in tanto, le librerie client ricevono miglioramenti per trattare più operazioni come idempotenti. Consideriamo questi miglioramenti come correzioni di bug e, pertanto, non sono modifiche che causano interruzioni, anche se modificano il comportamento della libreria client.

Tieni presente che, anche se potrebbe essere sicuro riprovare un'operazione, ciò non significa che l'operazione produca lo stesso risultato al secondo tentativo rispetto al primo tentativo riuscito. Ad esempio, la creazione di una risorsa identificata in modo univoco potrebbe essere sicura da riprovare, in quanto il secondo e i tentativi successivi non riescono e lasciano il sistema nello stesso stato. Tuttavia, il client potrebbe ricevere un errore "esiste già" nei tentativi di ripetizione.

Norma di ripetizione dei tentativi predefinita

Seguendo le linee guida descritte in aip/194, la maggior parte delle librerie client C++ riprova solo gli errori gRPC UNAVAILABLE. Questi vengono mappati a StatusCode::kUnavailable. La norma predefinita prevede di riprovare le richieste per 30 minuti.

Tieni presente che gli errori kUnavailable non indicano che il server non è riuscito a ricevere la richiesta. Questo codice di errore viene utilizzato quando la richiesta non può essere inviata, ma anche se la richiesta viene inviata correttamente, ricevuta dal servizio e la connessione viene persa prima che la risposta venga ricevuta dal client. Inoltre, se potessi determinare se la richiesta è stata ricevuta correttamente, potresti risolvere il problema dei due generali, un risultato di impossibilità ben noto nei sistemi distribuiti.

Pertanto, non è sicuro riprovare tutte le operazioni che non riescono con kUnavailable. Anche l'idempotenza dell'operazione è importante.

Norma di backoff predefinita

Per impostazione predefinita, la maggior parte delle librerie utilizza una strategia di backoff esponenziale troncata, con jitter. Il backoff iniziale è di 1 secondo, il backoff massimo è di 5 minuti e il backoff raddoppia dopo ogni tentativo.

Modificare le norme di ripetizione dei tentativi e di backoff predefinite

Ogni libreria definisce una struttura *Option per configurare queste norme. Puoi fornire queste opzioni quando crei la classe *Client o anche in ogni richiesta.

Ad esempio, di seguito viene illustrato come modificare le norme di ripetizione dei tentativi e di backoff per un client Cloud Pub/Sub:

namespace pubsub = ::google::cloud::pubsub;
using ::google::cloud::future;
using ::google::cloud::Options;
using ::google::cloud::StatusOr;
[](std::string project_id, std::string topic_id) {
  auto topic = pubsub::Topic(std::move(project_id), std::move(topic_id));
  // By default a publisher will retry for 60 seconds, with an initial backoff
  // of 100ms, a maximum backoff of 60 seconds, and the backoff will grow by
  // 30% after each attempt. This changes those defaults.
  auto publisher = pubsub::Publisher(pubsub::MakePublisherConnection(
      std::move(topic),
      Options{}
          .set<pubsub::RetryPolicyOption>(
              pubsub::LimitedTimeRetryPolicy(
                  /*maximum_duration=*/std::chrono::minutes(10))
                  .clone())
          .set<pubsub::BackoffPolicyOption>(
              pubsub::ExponentialBackoffPolicy(
                  /*initial_delay=*/std::chrono::milliseconds(200),
                  /*maximum_delay=*/std::chrono::seconds(45),
                  /*scaling=*/2.0)
                  .clone())));

  std::vector<future<bool>> done;
  for (char const* data : {"1", "2", "3", "go!"}) {
    done.push_back(
        publisher.Publish(pubsub::MessageBuilder().SetData(data).Build())
            .then([](future<StatusOr<std::string>> f) {
              return f.get().ok();
            }));
  }
  publisher.Flush();
  int count = 0;
  for (auto& f : done) {
    if (f.get()) ++count;
  }
  std::cout << count << " messages sent successfully\n";
}

Consulta la documentazione di ogni libreria per trovare i nomi e gli esempi specifici per quella libreria.

Passaggi successivi