Policy di ripetizione nelle librerie client C++

Questa pagina descrive il modello di nuovi 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, la chiusura delle connessioni sovraccariche o inattive da parte dei bilanciatori del carico e i limiti di frequenza possono entrare in vigore e questi 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 non irreversibili e operazioni non irreversibili

Solo gli errori temporanei possono essere riprovati. 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. Quasi sempre si tratta di una condizione transitoria, anche se il recupero potrebbe richiedere molto tempo. Questi errori sono sempre riproducibili (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 cicli di nuovi tentativi nella libreria client.

Allo stesso modo, non è sicuro riprovare alcune operazioni, 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" in cui potrebbero esistere più versioni di una risorsa denominata "X". Questo perché il chiamante probabilmente intendeva rimuovere una sola versione e il nuovo tentativo di richiesta può comportare la rimozione di tutte le versioni.

Configura i cicli di ripetizione dei tentativi

Le librerie client accettano tre diversi parametri di configurazione per controllare i cicli di nuovi tentativi:

  • *IdempotencyPolicy determina se una particolare 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 ritenta una richiesta.
  • *BackoffPolicy determina per quanto tempo la libreria client attende prima di emettere nuovamente la richiesta.

Norma di idempotenza predefinita

In generale, un'operazione è idempotente se la chiamata alla funzione più volte lascia il sistema nello stesso stato della chiamata alla funzione una sola volta. Solo le operazioni idempotenti possono essere riprovate in sicurezza. Esempi di operazioni idempotenti includono, a titolo esemplificativo, tutte le operazioni di sola lettura e le operazioni che possono essere eseguite correttamente una sola volta.

Per impostazione predefinita, la libreria client considera idempotenti solo le RPC implementate tramite i verbi GET o PUT. Questo potrebbe essere troppo conservativo, in alcuni servizi anche alcune richieste POST sono idempotenti. Puoi sempre ignorare la policy di idempotenza predefinita per adattarla meglio alle tue esigenze.

Alcune operazioni sono idempotenti solo se includono precondizioni. Ad esempio, "remove the latest version if the latest version is Y" è idempotente, in quanto può riuscire 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 cambiano 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, poiché il secondo e i tentativi successivi non vanno a buon fine e lasciano il sistema nello stesso stato. Tuttavia, il client potrebbe ricevere un errore "esiste già" nei tentativi di ripetizione.

Criteri di ripetizione predefiniti

Seguendo le linee guida descritte in aip/194, la maggior parte delle librerie client C++ ritenta solo gli errori gRPC UNAVAILABLE. Questi sono mappati a StatusCode::kUnavailable. La policy 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 non riuscite con kUnavailable. Anche l'idempotenza dell'operazione è importante.

Norme di backoff predefinite

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

Modificare i criteri predefiniti per i nuovi tentativi e il backoff

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

Ad esempio, questo mostra come modificare i criteri di ripetizione e 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