.Net Core gRPC 实战

Posted Stacking

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了.Net Core gRPC 实战相关的知识,希望对你有一定的参考价值。

概述

gRPC 客户端必须使用与服务相同的连接级别安全性。  如调用服务时通道和服务的连接级别安全性不一致,gRPC 客户端就会抛出错误。

gRPC 配置使用HTTP

gRPC 客户端传输层安全性 (TLS) 是在创建 gRPC 通道时服务器地址以https开头配置的。若要配置为http协议做如下修改

AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
GrpcChannel.ForAddress("http://localhost:5000")

关于通道和客户端的说明

  • 创建通道成本高昂。 重用 gRPC 调用的通道可提高性能。
  • gRPC 客户端是使用通道创建的。 gRPC 客户端是轻型对象,无需缓存或重用。

 

配置截止时间

建议配置 gRPC 调用的截止时间,因为它限制调用时间的上限,阻止异常运行的服务持续运行并耗尽服务器资源。 截止时间对于构建可靠应用非常有效。

进行调用时,使用 CallOptions.Deadline 配置截止时间。

如果超过了截止时间,客户端和服务将有不同的行为:

  • 客户端将立即中止基础的 HTTP 请求并引发 DeadlineExceeded 错误。 客户端可以选择捕获错误并向用户显示超时消息。
  • 服务器将中止正在执行的 HTTP 请求,并引发 ServerCallContext.CancellationToken。 尽管中止了 HTTP 请求,gRPC 调用仍将继续运行直到方法完成。 将取消令牌传递给异步方法,使其随调用一同被取消。 例如,向异步数据库查询和 HTTP 请求传递取消令牌。 传递取消令牌让取消的调用可以在服务器上快速完成,并为其他调用释放资源。

客户端代码示例:

try
{
    var response = await client.SayHelloAsync(
        new HelloRequest { Name = "World" },
        deadline: DateTime.UtcNow.AddSeconds(5));
    
    // Greeting: Hello World
    Console.WriteLine("Greeting: " + response.Message);
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.DeadlineExceeded)
{
    Console.WriteLine("Greeting timeout.");
}

服务器代码示例:

var response = await client.GetUserAsync(
        new UserRequest { Id = request.Id },
        deadline: context.Deadline);

 

注册 gRPC 客户端

在 Startup类的ConfigureServices方法中,使用 AddGrpcClient 扩展方法指定 gRPC客户端类和服务地址。

services.AddGrpcClient<Greeter.GreeterClient>(o =>
{
    o.Address = new Uri("https://localhost:5001");
});

ASP.NET Core MVC 控制器和 gRPC 服务等通过构造函数等方式自动注入。

配置 HttpHandler

 .ConfigurePrimaryHttpMessageHandler(() => new GrpcWebHandler(GrpcWebMode.GrpcWebText, new HttpClientHandler()));

 

配置通道和拦截器

通道

通道(Channel)是.Net Core 3.X引入的类型,Channel是线程安全的,Channel的预期用例是多线程场景,可以实现多线程之间通信。类似Golang的chan类型。

通道相关文章:https://webmote.blog.csdn.net/article/details/115361367

gRPC配置通道示例:

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigureChannel(o =>
    {
        var credentials = CallCredentials.FromInterceptor((context, metadata) =>
        {
            if (!string.IsNullOrEmpty(_token))
            {
                metadata.Add("Authorization", $"Bearer {_token}");
            }
            return Task.CompletedTask;
        });

        o.Credentials = ChannelCredentials.Create(new SslCredentials(), credentials);
    });

 

拦截器

具有面向切面的思想,可以在调用服务的时候进行一些统一处理, 很适合在这里处理验证、日志等流程。

Interceptor类是gRPC服务拦截器的基类,是一个抽象类

 

 

 

各个方法作用如下:

方法名称

作用

BlockingUnaryCall

拦截阻塞调用

AsyncUnaryCall

拦截异步调用

AsyncServerStreamingCall

拦截异步服务端流调用

AsyncClientStreamingCall

拦截异步客户端流调用

AsyncDuplexStreamingCall

拦截异步双向流调用

UnaryServerHandler

用于拦截和传入普通调用服务器端处理程序

ClientStreamingServerHandler

用于拦截客户端流调用的服务器端处理程序

ServerStreamingServerHandler

用于拦截服务端流调用的服务器端处理程序

DuplexStreamingServerHandler

用于拦截双向流调用的服务器端处理程序

在实际使用中,可以根据自己的需要来使用对应的拦截方法。

本文示例为创建一个客户端拦截器ClientLoggerInterceptor,该类继承Interceptor。按需实现方法,这里我客户端调用的是SayHelloAsync方法则实现对应的AsyncUnaryCall方法。

 

 注册拦截器:

运行效果图:

 

 

服务器端拦截器同理,继承Interceptor类实现对应方法。

服务器端注入方式:

services.AddGrpc(options =>
    {
        options.Interceptors.Add<ServerLoggerInterceptor>();
    });

调用取消

可以使用 EnableCallContextPropagation() 对 gRPC 服务中工厂所创建的 gRPC 客户端进行配置,以自动将截止时间和取消令牌传播到子调用。

手动传播截止时间可能会很繁琐。 截止时间需要传递给每个调用,很容易不小心错过。 gRPC 客户端工厂提供自动解决方案。 指定 EnableCallContextPropagation

  • 自动将截止时间和取消令牌传播到子调用。
  • 这是确保复杂的嵌套 gRPC 场景始终传播截止时间和取消的一种极佳方式。
services
    .AddGrpcClient<User.UserServiceClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .EnableCallContextPropagation();

 如果客户端在 gRPC 调用的上下文之外使用,EnableCallContextPropagation 将引发错误。 此错误旨在提醒你没有要传播的调用上下文。 如果要在调用上下文之外使用客户端,请使用 SuppressContextNotFoundErrors 在配置客户端时禁止显示该错误:

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .EnableCallContextPropagation(o => o.SuppressContextNotFoundErrors = true);

 

配置 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("http://localhost:5000",
                new GrpcChannelOptions
                {
                    LoggerFactory = loggerFactory,
                    ServiceConfig = new ServiceConfig
                    {
                        MethodConfigs = { defaultMethodConfig }
                    }
                });

 

重试策略可以按方法配置,而方法可以使用 Names 属性进行匹配。 此方法配置有 MethodName.Default,因此它将应用于此通道调用的所有 gRPC 方法。

gRPC 重试选项

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

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

配置 gRPC hedging 策略

Hedging 是一种备选重试策略。 Hedged gRPC 调用可以在服务器上执行多次,并获取第一个成功的结果。 

重要的是,务必仅针对可安全执行多次且不会造成负面影响的方法启用 hedging。且hedging 策略不能与重试策略结合使用。

Hedging 具有以下优缺点:

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

Hedging 策略的配置类似于重试策略:

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

            var channel = GrpcChannel.ForAddress("http://localhost:5000", new GrpcChannelOptions
            {
                LoggerFactory = loggerFactory,
                ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } }
            });

 

gRPC hedging 选项

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

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

 

Github

本文示例代码:https://github.com/MayueCif/GrpcDemo

 

以上是关于.Net Core gRPC 实战的主要内容,如果未能解决你的问题,请参考以下文章

GRPC与.NET Core

Asp.net core 通过grpc调用python

WPF .NET Core 中的 gRPC 错误

如何在 ASP.NET Core 中为 gRPC 服务添加全局异常处理 ?

[.Net Core] - 在 .NET Core 中创建 gRPC 服务端和客户端

.NET Core Love gRPC