Políticas de reintento en las bibliotecas cliente de C++

En esta página, se describe el modelo de reintento que usan las bibliotecas cliente de C++.

Las bibliotecas cliente emiten RPC (llamadas de procedimiento remoto) en tu nombre. Estas RPC pueden fallar debido a errores transitorios. Los servidores se reinician, los balanceadores de cargas cierran las conexiones sobrecargadas o inactivas, y los límites de frecuencia pueden entrar en vigencia, y estos son solo algunos ejemplos de fallas transitorias.

Las bibliotecas podrían mostrar estos errores a la aplicación. Sin embargo, muchos de estos errores son fáciles de controlar en la biblioteca, lo que simplifica el código de la aplicación.

Errores y operaciones que se pueden reintentar

Solo se pueden reintentar los errores transitorios. Por ejemplo, kUnavailable indica que el cliente no pudo conectarse o perdió su conexión a un servicio mientras una solicitud estaba en curso. Esta es casi siempre una condición transitoria, aunque puede tardar mucho en recuperarse. Estos errores siempre se pueden reintentar (siempre que la operación en sí sea segura para reintentar). En el contrato, los errores kPermissionDenied requieren una intervención adicional (por lo general, por una persona) para resolverse. Esos errores no se consideran "transitorios" o, al menos, no transitorios en las escalas de tiempo que consideran los bucles de reintento en la biblioteca cliente.

Del mismo modo, no es seguro reintentar algunas operaciones, independientemente de la naturaleza del error. Esto incluye cualquier operación que realice cambios incrementales. Por ejemplo, no es seguro reintentar una operación para quitar "la versión más reciente de X" cuando puede haber varias versiones de un recurso llamado "X". Esto se debe a que el llamador probablemente tenía la intención de quitar una sola versión, y reintentar esa solicitud puede provocar la eliminación de todas las versiones.

Configura bucles de reintento

Las bibliotecas cliente aceptan tres parámetros de configuración diferentes para controlar los bucles de reintento:

  • El *IdempotencyPolicy determina si una solicitud en particular es idempotente. Solo se reintentan esas solicitudes.
  • El *RetryPolicy determina (a) si un error debe considerarse una falla transitoria y (b) cuánto tiempo (o cuántas veces) la biblioteca cliente reintenta una solicitud.
  • El *BackoffPolicy determina cuánto tiempo espera la biblioteca cliente antes de volver a emitir la solicitud.

Política de idempotencia predeterminada

En general, una operación es idempotente si llamar a la función varias veces de forma correcta deja el sistema en el mismo estado que llamar a la función una vez de forma correcta. Solo es seguro reintentar las operaciones idempotentes. Entre los ejemplos de operaciones idempotentes, se incluyen, entre otros, todas las operaciones de solo lectura y las operaciones que solo pueden tener éxito una vez.

De forma predeterminada, la biblioteca cliente solo trata como idempotentes las RPC que se implementan a través de los verbos GET o PUT. Esto puede ser demasiado conservador, ya que en algunos servicios incluso algunas solicitudes POST son idempotentes. Siempre puedes anular la política de idempotencia predeterminada para que se adapte mejor a tus necesidades.

Algunas operaciones solo son idempotentes si incluyen condiciones previas. Por ejemplo, "quitar la versión más reciente si la versión más reciente es Y" es idempotente, ya que solo puede tener éxito una vez.

De vez en cuando, las bibliotecas cliente reciben mejoras para tratar más operaciones como idempotentes. Consideramos que estas mejoras son correcciones de errores y, por lo tanto, no son incompatibles, incluso si cambian el comportamiento de la biblioteca cliente.

Ten en cuenta que, si bien puede ser seguro reintentar una operación, esto no significa que la operación produzca el mismo resultado en el segundo intento que en el primer intento exitoso. Por ejemplo, puede ser seguro reintentar la creación de un recurso identificado de forma única, ya que el segundo intento y los sucesivos fallan y dejan el sistema en el mismo estado. Sin embargo, el cliente puede recibir un error de "ya existe" en los intentos de reintento.

Política de reintento predeterminada

Siguiendo los lineamientos que se describen en aip/194, la mayoría de las bibliotecas cliente de C++ solo reintentan los errores de gRPC UNAVAILABLE. Estos se asignan a StatusCode::kUnavailable. La política predeterminada es reintentar las solicitudes durante 30 minutos.

Ten en cuenta que los errores kUnavailable no indican que el servidor no recibió la solicitud. Este código de error se usa cuando no se puede enviar la solicitud, pero también se usa si la solicitud se envía correctamente, el servicio la recibe y se pierde la conexión antes de que el cliente reciba la respuesta. Además, si pudieras determinar si la solicitud se recibió correctamente, podrías resolver el problema de los dos generales, un resultado de imposibilidad conocido en los sistemas distribuidos.

Por lo tanto, no es seguro reintentar todas las operaciones que fallan con kUnavailable. La idempotencia de la operación también importa.

Política de retirada predeterminada

De forma predeterminada, la mayoría de las bibliotecas usan una estrategia de retirada exponencial truncada, con fluctuación. La retirada inicial es de 1 segundo, la retirada máxima es de 5 minutos y la retirada se duplica después de cada reintento.

Cambia las políticas de reintento y retirada predeterminadas

Cada biblioteca define una estructura *Option para configurar estas políticas. Puedes proporcionar estas opciones cuando creas la clase *Client o incluso en cada solicitud.

Por ejemplo, se muestra cómo cambiar las políticas de reintento y retirada para un cliente de 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 documentación de cada biblioteca para encontrar los nombres y ejemplos específicos de esa biblioteca.

Próximos pasos