gRPC在 ASP.NET Core 中应用学习(二)

前言:

 上一篇文章中简单的对gRPC进行了简单了解,并实现了gRPC在ASP.NET Core中服务实现、客户端调用;那么本篇继续对gRPC的4中服务方法定义、其他使用注意点进一步了解学习

一、gRPC的4类服务方法

  • 简单 RPC(一元方法):客户端向服务器发送单个请求并获得单个响应,就像普通的函数调用一样。

   示例:

rpc UnaryCall(ExampleRequest) returns (ExampleResponse) {}
  • 服务器端流式 RPC: 客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流,直到里面没有任何消息。通过在 响应 类型前插入 stream 关键字,可以指定一个服务器端的流方法

   示例:

rpc StreamingFromServer(ExampleRequest) returns (stream ExampleResponse) {}
  • 客户端流式 RPC: 

   示例:

rpc StreamingFromClient(stream ExampleRequest) returns (ExampleResponse) {}
  • 双向流式 RPC:双方使用读写流去发送一个消息序列。两个流独立操作,因此客户端和服务器可以以任意喜欢的顺序读写:

   比如, 服务器可以在写入响应前等待接收所有的客户端消息,或者可以交替的读取和写入消息,或者其他读写的组合。 每个流中的消息顺序被预留。你可以通过在请求和响应前加 stream 关键字去制定方法的类型。

   示例:

 rpc StreamingBothWays(stream ExampleRequest) returns (stream ExampleResponse) {}

  完整protos如下: 

syntax = "proto3";
option csharp_namespace = "GrpcServiceDemo";
//可添加版本号区分v1/v2 package serviceTypeDemo;
//服务类型Demo:用于展现gRpc四种服务方法 service ServiceTypeDemo { // Unary:一元方法 rpc UnaryCall (ExampleRequest) returns (ExampleResponse); // Server streaming:服务流 rpc StreamingFromServer (ExampleRequest) returns (stream ExampleResponse); // Client streaming:客户端流 rpc StreamingFromClient (stream ExampleRequest) returns (ExampleResponse); // Bi-directional streaming:双向流 rpc StreamingBothWays (stream ExampleRequest) returns (stream ExampleResponse); } //请求对象 message ExampleRequest { int32 pageIndex = 1; int32 pageSize = 2; bool isDescending = 3; } //响应对象 message ExampleResponse{ object result = 1; int32 total = 2; }

  服务实现:实现相对简单,主要展示读取流和写入流  

public class ServerTypeService : ServiceTypeDemo.ServiceTypeDemoBase
{
    public override Task<ExampleResponse> UnaryCall(ExampleRequest request, ServerCallContext context)
    {
        ExampleResponse resp = GetResp();
        return Task.FromResult(resp);
    }
    private static ExampleResponse GetResp()
    {
        var resp = new ExampleResponse();
        resp.Total = new Random().Next(1, 20);
        resp.Result.Add("abc");
        resp.Result.Add("efg");
        return resp;
    }
    public async override Task StreamingFromServer(ExampleRequest request, IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
    {
        var responses = new List<ExampleResponse>();
        responses.Add(GetResp());
        responses.Add(GetResp());
        responses.Add(GetResp());
        foreach (var response in responses)
        {
            //写入流中
            await responseStream.WriteAsync(response);
        }
    }
    public async override Task<ExampleResponse> StreamingFromClient(IAsyncStreamReader<ExampleRequest> requestStream, ServerCallContext context)
    {
        int pointCount = 0;
        //读取传入流
        while (await requestStream.MoveNext())
        {
            var point = requestStream.Current;
            pointCount++;
        }
        var info = GetResp();
        info.Total = pointCount;
        return info;
    }
    public async override Task StreamingBothWays(IAsyncStreamReader<ExampleRequest> requestStream, IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)
    {
        //读取内容
        while (await requestStream.MoveNext())
        {
            var note = requestStream.Current;
            var responses = new List<ExampleResponse>();
            responses.Add(GetResp());
            responses.Add(GetResp());
            //循环写入
            foreach (var prevNote in responses){
                await responseStream.WriteAsync(prevNote);
            }
        }
    }
}

二、gRPC其他注意点  

 1、截止时间和取消处理

    截止时间功能让 gRPC 客户端可以指定等待调用完成的时间。 超过截止时间时,将取消调用。

   截止时间配置:

  • 在进行调用时,使用 CallOptions.Deadline 配置截止时间。
  • 没有截止时间默认值。 gRPC 调用没有时间限制,除非指定了截止时间。
  • 截止时间指的是超过截止时间的 UTC 时间。 例如,DateTime.UtcNow.AddSeconds(5) 是从现在起 5 秒的截止时间。
  • 如果使用的是过去或当前的时间,则调用将立即超过截止时间。
  • 截止时间随 gRPC 调用发送到服务,并由客户端和服务独立跟踪。 gRPC 调用可能在一台计算机上完成,但当响应返回给客户端时,已超过了截止时间。

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

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

   使用方式如下:

var defaultMethodConfig = new MethodConfig
{
    Names = { MethodName.Default },
    RetryPolicy = new RetryPolicy
    {
        //最大重试5次
        MaxAttempts = 5,
        //重试尝试之间的初始退避延迟。
        InitialBackoff = TimeSpan.FromSeconds(1),
        //最大退避会限制指数退避增长的上限。
        MaxBackoff = TimeSpan.FromSeconds(5),
        //每次重试尝试后,退避将乘以该值,并将在乘数大于 1 的情况下以指数方式增加。
        BackoffMultiplier = 1.5,
        //状态代码的集合,在此集合中重试
        RetryableStatusCodes = { StatusCode.Unavailable }
    }
};
//初始化Grpc通道:参数为gRPC服务地址
using var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions()
{
    ThrowOperationCanceledOnCancellation = true,
    //设置重试:v2.36版本添加
    ServiceConfig = new ServiceConfig() { MethodConfigs = { defaultMethodConfig } }
});

string token = string.Empty;
var client = new Greeter.GreeterClient(channel);
//设置超时5秒;headers:可传递头信息:如认证串
Metadata metadata = new Metadata();
metadata.Add("Authorization", $"Bearer {token}");
var reply = client.SayHello(new HelloRequest { Name = "GreeterClient" },
    headers: metadata,
    deadline: DateTime.UtcNow.AddSeconds(5));

 2、gRPC-Web应用

   gRPC-Web 允许浏览器 JavaScript 和 Blazor 应用调用 gRPC 服务。 无法从基于浏览器的应用中调用 HTTP/2 gRPC 服务。 可将托管于 ASP.NET Core 中的 gRPC 服务配置为随 HTTP/2 gRPC 一起支持 gRPC-Web。

  有两种方式可将 gRPC-Web 添加到 ASP.NET Core 应用中:

  • 在 ASP.NET Core 中同时支持 gRPC-Web 和 gRPC HTTP/2。 此选项会使用 Grpc.AspNetCore.Web 包提供的中间件。
  • 使用 Envoy 代理的 gRPC-Web 支持将 gRPC-Web 转换为 gRPC HTTP/2。 转换后的调用随后会转发给 ASP.NET Core 应用。

  a)添加Grpc.AspNetCore.Web 包引用

  b)修改Startup.cs文件如下:

public void ConfigureServices(IServiceCollection services){
    services.AddGrpc();
}
public void Configure(IApplicationBuilder app){
    app.UseRouting();
    app.UseGrpcWeb();//启用gRPCWeb
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGrpcService<GreeterService>().EnableGrpcWeb();
    });
}

    c)Cors跨域设置:

public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc();
    services.AddCors(o => o.AddPolicy("AllowAll", builder =>
    {
        builder.AllowAnyOrigin()
               .AllowAnyMethod()
               .AllowAnyHeader()
               .WithExposedHeaders("Grpc-Status", "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding");
    }));
}
public void Configure(IApplicationBuilder app)
{
    app.UseRouting();
    app.UseGrpcWeb();
    app.UseCors();
    app.UseEndpoints(endpoints =>{
        endpoints.MapGrpcService<GreeterService>().EnableGrpcWeb()
                                                  .RequireCors("AllowAll");
    });
} 

 3、gRPC进程间调用:需要.NET 5;

  服务端设置:   

public static readonly string SocketPath = Path.Combine(Path.GetTempPath(), "socket.tmp");

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
            webBuilder.ConfigureKestrel(options =>
            {
                if (File.Exists(SocketPath))
                {
                    File.Delete(SocketPath);
                }
                options.ListenUnixSocket(SocketPath);
            });
        });

  客户端设置:  

public class UnixDomainSocketConnectionFactory
{
    private readonly EndPoint _endPoint;

    public UnixDomainSocketConnectionFactory(EndPoint endPoint)
    {
        _endPoint = endPoint;
    }

    public async ValueTask<Stream> ConnectAsync(SocketsHttpConnectionContext _,
        CancellationToken cancellationToken = default)
    {
        var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);

        try
        {
            await socket.ConnectAsync(_endPoint, cancellationToken).ConfigureAwait(false);
            return new NetworkStream(socket, true);
        }
        catch
        {
            socket.Dispose();
            throw;
        }
    }
}

    在创建通道连接时:

public static readonly string SocketPath = Path.Combine(Path.GetTempPath(), "socket.tmp");

public static GrpcChannel CreateChannel()
{
    var udsEndPoint = new UnixDomainSocketEndPoint(SocketPath);
    var connectionFactory = new UnixDomainSocketConnectionFactory(udsEndPoint);
    var socketsHttpHandler = new SocketsHttpHandler
    {
        ConnectCallback = connectionFactory.ConnectAsync
    };

    return GrpcChannel.ForAddress("http://localhost", new GrpcChannelOptions
    {
        HttpHandler = socketsHttpHandler
    });
}

 4、相关配置项:

  服务端:

  gRPC 服务在 Startup.cs 中使用 AddGrpc 进行配置。

选项默认值描述
MaxSendMessageSize null 可以从服务器发送的最大消息大小(以字节为单位)。 尝试发送超过配置的最大消息大小的消息会导致异常。 设置为 null时,消息的大小不受限制。
MaxReceiveMessageSize 4 MB 可以由服务器接收的最大消息大小(以字节为单位)。 如果服务器收到的消息超过此限制,则会引发异常。 增大此值可使服务器接收更大的消息,但可能会对内存消耗产生负面影响。 设置为 null时,消息的大小不受限制。
EnableDetailedErrors false 如果为 true,则当服务方法中引发异常时,会将详细异常消息返回到客户端。 默认值为 false 将 EnableDetailedErrors 设置为 true 可能会泄漏敏感信息。
CompressionProviders gzip 用于压缩和解压缩消息的压缩提供程序的集合。 可以创建自定义压缩提供程序并将其添加到集合中。 默认已配置提供程序支持 gzip 压缩。
ResponseCompressionAlgorithm null 压缩算法用于压缩从服务器发送的消息。 该算法必须与 CompressionProviders 中的压缩提供程序匹配。 若要使算法可压缩响应,客户端必须通过在 grpc-accept-encoding 标头中进行发送来指示它支持算法。
ResponseCompressionLevel null 用于压缩从服务器发送的消息的压缩级别。
Interceptors None 随每个 gRPC 调用一起运行的侦听器的集合。 侦听器按注册顺序运行。 全局配置的侦听器在为单个服务配置的侦听器之前运行。 有关 gRPC 侦听器的详细信息,请参阅 gRPC 侦听器与中间件
IgnoreUnknownServices false 如果为 true,则对未知服务和方法的调用不会返回 UNIMPLEMENTED 状态,并且请求会传递到 ASP.NET Core 中的下一个注册中间件。

  客户端:

   gRPC 客户端配置在 GrpcChannelOptions 中进行设置。 下表描述了用于配置 gRPC 通道的选项      

选项默认值描述
HttpHandler 新实例 用于进行 gRPC 调用的 HttpMessageHandler 可以将客户端设置为配置自定义 HttpClientHandler,或将附加处理程序添加到 gRPC 调用的 HTTP 管道。 如果未指定 HttpMessageHandler,则会通过自动处置为通道创建新 HttpClientHandler 实例。
HttpClient null 用于进行 gRPC 调用的 HttpClient 此设置是 HttpHandler 的替代项。
DisposeHttpClient false 如果设置为 true 且指定了 HttpMessageHandler 或 HttpClient,则在处置 GrpcChannel 时,将分别处置 HttpHandler 或 HttpClient
LoggerFactory null 客户端用于记录有关 gRPC 调用的信息的 LoggerFactory 可以通过依赖项注入来解析或使用 LoggerFactory.Create 来创建 LoggerFactory 实例。 有关配置日志记录的示例,请参阅 .NET 上 gRPC 中的日志记录和诊断
MaxSendMessageSize null 可以从客户端发送的最大消息大小(以字节为单位)。 尝试发送超过配置的最大消息大小的消息会导致异常。 设置为 null时,消息的大小不受限制。
MaxReceiveMessageSize 4 MB 可以由客户端接收的最大消息大小(以字节为单位)。 如果客户端收到的消息超过此限制,则会引发异常。 增大此值可使客户端接收更大的消息,但可能会对内存消耗产生负面影响。 设置为 null时,消息的大小不受限制。
Credentials null 一个 ChannelCredentials 实例。 凭据用于将身份验证元数据添加到 gRPC 调用。
CompressionProviders gzip 用于压缩和解压缩消息的压缩提供程序的集合。 可以创建自定义压缩提供程序并将其添加到集合中。 默认已配置提供程序支持 gzip 压缩。
ThrowOperationCanceledOnCancellation false 如果设置为 true,则在取消调用或超过其截止时间时,客户端将引发 OperationCanceledException
MaxRetryAttempts 5 最大重试次数。 该值限制服务配置中指定的任何重试和 hedging 尝试值。单独设置该值不会启用重试。 重试在服务配置中启用,可以使用 ServiceConfig 来启用。 null 值会删除最大重试次数限制。 有关重试的更多详细信息,请参阅“暂时性故障处理与 gRPC 重试”。
MaxRetryBufferSize 16 MB 在重试或 hedging 调用时,可用于存储发送的消息的最大缓冲区大小(以字节为单位)。 如果超出了缓冲区限制,则不会再进行重试,并且仅保留一个 hedging 调用,其他 hedging 调用将会取消。 此限制将应用于通过通道进行的所有调用。 值 null 移除最大重试缓冲区大小限制。
MaxRetryBufferPerCallSize 1 MB 在重试或 hedging 调用时,可用于存储发送的消息的最大缓冲区大小(以字节为单位)。 如果超出了缓冲区限制,则不会再进行重试,并且仅保留一个 hedging 调用,其他 hedging 调用将会取消。 此限制将应用于一个调用。 值 null 移除每个调用的最大重试缓冲区大小限制。
ServiceConfig null gRPC 通道的服务配置。 服务配置可以用于配置 gRPC 重试

参考:

 官方说明文档:https://grpc.io/docs/what-is-grpc/ 

 微软:https://docs.microsoft.com/zh-cn/aspnet/core/grpc/?view=aspnetcore-3.0

 示例源码地址:https://github.com/cwsheng/GrpcDemo

  

posted @ 2021-03-28 16:08  chaney1992  阅读(335)  评论(0编辑  收藏  举报