暂时性故障处理与 gRPC 重试

作者:James Newton-King

gRPC 重试是一项功能,允许 gRPC 客户端自动重试失败的调用。 本文介绍如何配置重试策略,以便在 .NET 中创建可复原的容错 gRPC 应用。

gRPC 重试需要 Grpc.Net.Client 2.36.0 或更高版本。

暂时性故障处理

暂时性故障可能会中断 gRPC 调用。 暂时性故障包括:

  • 暂时失去网络连接。
  • 服务暂时不可用。
  • 由于服务器负载导致超时。

gRPC 调用中断时,客户端会引发 RpcException 并提供有关错误的详细信息。 客户端应用必须捕获异常并选择如何处理错误。

var client = new Greeter.GreeterClient(channel);
try
{
    var response = await client.SayHelloAsync(
        new HelloRequest { Name = ".NET" });

    Console.WriteLine("From server: " + response.Message);
}
catch (RpcException ex)
{
    // Write logic to inspect the error and retry
    // if the error is from a transient fault.
}

在整个应用中复制重试逻辑是非常冗长的代码,容易出错。 幸运的是,.NET gRPC 客户端拥有自动重试的内置支持。

配置 gRPC 重试策略

重试策略在创建 gRPC 通道时配置一次:

var defaultMethodConfig = new MethodConfig
{
    Names = { MethodName.Default },
    RetryPolicy = new RetryPolicy
    {
        MaxAttempts = 5,
        InitialBackoff = TimeSpan.FromSeconds(1),
        MaxBackoff = TimeSpan.FromSeconds(5),
        BackoffMultiplier = 1.5,
        RetryableStatusCodes = { StatusCode.Unavailable }
    }
};

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions
{
    ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } }
});

上述代码:

  • 创建一个 MethodConfig。 重试策略可以按方法配置,而方法可以使用 Names 属性进行匹配。 此方法配置有 MethodName.Default,因此它将应用于此通道调用的所有 gRPC 方法。
  • 配置重试策略。 此策略将指示客户端自动重试状态代码为 Unavailable 的失败 gRPC 调用。
  • 通过设置 GrpcChannelOptions.ServiceConfig,将创建的通道配置为使用该重试策略。

随着该通道一起创建的 gRPC 客户端将自动重试失败的调用:

var client = new Greeter.GreeterClient(channel);
var response = await client.SayHelloAsync(
    new HelloRequest { Name = ".NET" });

Console.WriteLine("From server: " + response.Message);

重试何时有效

满足以下条件时,将重试调用:

  • 失败状态代码与 RetryableStatusCodes 中的值匹配。
  • 之前的尝试次数小于 MaxAttempts
  • 此调用未提交。
  • 尚未超过截止时间。

在以下两种情况下,将提交 gRPC 调用:

  • 客户端收到响应头。 调用 ServerCallContext.WriteResponseHeadersAsync 或将第一个消息写入服务器响应流时,服务器会发送响应头。
  • 客户端的传出消息(如果是流式处理则为消息)已超出客户端的最大缓冲区大小。 MaxRetryBufferSizeMaxRetryBufferPerCallSize在通道上配置

无论状态代码是什么或以前的尝试次数是多少,提交的调用都无法重试。

流式处理调用

流式处理调用可以与 gRPC 重试一起使用,但在将它们一起使用时,务必注意以下事项:

  • 服务器流式处理、双向流式处理: 在已收到第一个消息后,从服务器返回多个消息的流式处理 RPC 无法重试。 应用必须添加额外的逻辑才能手动重新建立服务器和双向流式传输调用。
  • 客户端流式处理、双向流式处理: 传出消息超出客户端的最大缓冲区大小时,向服务器发送多个消息的流式处理 RPC 无法重试。 可通过配置增加最大缓冲区大小。

有关详细信息,请参阅重试何时有效

重试退避延迟

重试尝试之间的退避延迟配置了 InitialBackoffMaxBackoffBackoffMultipliergRPC 重试选项部分中提供了有关每个选项的详细信息。

重试尝试之间的实际延迟是随机的。 介于 0 与当前退避之间的随机延迟确定何时进行下一次重试尝试。 假设即使配置了指数退避来增大尝试之间的当前退避,尝试之间的实际延迟也并不总是更大。 延迟是随机的,可防止多次调用的重试聚集在一起且可能导致服务器过载。

gRPC 重试选项

下表描述了用于配置 gRPC 重试策略的选项:

选项 描述
MaxAttempts 最大调用尝试次数,包括原始尝试。 此值受 GrpcChannelOptions.MaxRetryAttempts(默认值为 5)的限制。 必须为该选项提供值,且值必须大于 1。
InitialBackoff 重试尝试之间的初始退避延迟。 介于 0 与当前退避之间的随机延迟确定何时进行下一次重试尝试。 每次尝试后,当前退避将乘以 BackoffMultiplier。 必须为该选项提供值,且值必须大于 0。
MaxBackoff 最大退避会限制指数退避增长的上限。 必须为该选项提供值,且值必须大于 0。
BackoffMultiplier 每次重试尝试后,退避将乘以该值,并将在乘数大于 1 的情况下以指数方式增加。 必须为该选项提供值,且值必须大于 0。
RetryableStatusCodes 状态代码的集合。 具有匹配状态的失败 gRPC 调用将自动重试。 有关状态代码的更多信息,请参阅状态代码及其在 gRPC 中的用法。 至少需要提供一个可重试的状态代码。

Hedging

Hedging 是一种备选重试策略。 Hedging 允许在不等待响应的情况下,主动发送单个 gRPC 调用的多个副本。 Hedged gRPC 调用可以在服务器上执行多次,并使用第一个成功的结果。 重要的是,务必仅针对可安全执行多次且不会造成负面影响的方法启用 hedging。

与重试相比,Hedging 具有以下优缺点:

  • Hedging 的优点是,它可能会更快地返回成功的结果。 它允许同时进行多个 gRPC 调用,并在出现第一个成功的结果时完成。
  • Hedging 的一个缺点是它可能会造成浪费。 进行了多个调用并且这些调用全部成功。 而仅使用第一个结果,并放弃其余结果。

配置 gRPC hedging 策略

Hedging 策略的配置类似于重试策略。 需要注意的是,hedging 策略不能与重试策略结合使用。

var defaultMethodConfig = new MethodConfig
{
    Names = { MethodName.Default },
    HedgingPolicy = new HedgingPolicy
    {
        MaxAttempts = 5,
        NonFatalStatusCodes = { StatusCode.Unavailable }
    }
};

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions
{
    ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } }
});

gRPC hedging 选项

下表描述了用于配置 gRPC hedging 策略的选项:

选项 描述
MaxAttempts Hedging 策略将发送的调用数量上限。 MaxAttempts 表示所有尝试的总数,包括原始尝试。 此值受 GrpcChannelOptions.MaxRetryAttempts(默认值为 5)的限制。 必须为该选项提供值,且值必须大于 2。
HedgingDelay 第一次调用立即发送,而后续 hedging 调用按该值延迟发送。 如果延迟设置为零或 null,那么所有所有 hedged 调用都将立即发送。 HedgingDelay 为可选,默认值为零。 值必须为零或更大。
NonFatalStatusCodes 指示其他 hedge 调用仍可能会成功的状态代码集合。 如果服务器返回非致命状态代码,hedged 调用将继续。 否则,将取消未完成的请求,并将错误返回到应用。 有关状态代码的更多信息,请参阅状态代码及其在 gRPC 中的用法

其他资源