gRPC是一个现代的、跨平台的、高性能的 RPC 框架。gRPC for .NET 构建在 ASP.NET Core 之上,是我们推荐的在 .NET 中构建 RPC 服务的方法。
.NET 6 进一步提高了 gRPC 已经非常出色的性能,并添加了一系列新功能,使 gRPC 在现代云原生应用程序中比以往任何时候都更好。在这篇文章中,我将描述这些新功能, 以及我们如何通过第一个支持端到端 HTTP/3 的 gRPC 实现引领行业。
gRPC 客户端负载均衡
客户端负载均衡功能允许 gRPC 客户端以最佳方式在可用服务器之间分配负载, 这样就不需要使用专门的负载均衡代理服务器, 这有几个好处:
•性能改进, 无代理可以减少网络延迟, 因为 RPC 直接发送到 gRPC 服务器, 无需中转。
•节省服务器资源,负载平衡代理必须解析然后重新发送每个 HTTP 请求, 本身也会占用 CPU 和内存, 所以移除代理可以节省服务器资源。
•更简单的程序架构, gRPC 负载均衡代理需要安装, 配置才能正常工作, 而使用客户端负载均衡, 客户端直接发送到服务端, 程序的架构也很简单。
如果要使用客户端负载均衡, 需要在创建 channel 的时候进行配置, 另外使用负载均衡时要考虑两个组件
•resolver 解析器, 它可以从创建的 channel 中返回服务地址, 并且支持从外部源获取地址, 其实这就是我们熟悉的服务发现。
•load balancer 负载均衡器, 当调用 gRPC 的时候, 它会根据配置的负载均衡的策略, 返回响应的服务地址, 并创建连接。
下面的代码中, 给 GrpcChannel 配置了 DNS 服务发现和轮询的负载均衡策略。
var channel = GrpcChannel.ForAddress( "dns:///my-example-host", new GrpcChannelOptions { Credentials = ChannelCredentials.Insecure, ServiceConfig = new ServiceConfig { LoadBalancingConfigs = { new RoundRobinConfig() } } });var client = new Greet.GreeterClient(channel);var response = await client.SayHelloAsync(new HelloRequest { Name = "world" });
有关更多信息,请参考 gRPC 客户端负载平衡[1]。
瞬时故障的处理和重试
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){ // 这里记录错误并重试}
在您的程序中, 你可能需要在很多地方写这样的处理代码, 幸运的是,.NET gRPC 客户端现在内置了对自动重试的支持, 只需要在 channel 上统一配置即可, 并且支持几种不同的重试策略。
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 } }});
有关更多信息,请参阅使用 gRPC 重试进行瞬态故障处理[2]。
Protobuf 性能
gRPC for .NET 使用 Google.Protobuf 库作为消息的默认序列化程序。Protobuf 是一种高效的二进制序列化格式。Google.Protobuf 旨在提高性能,使用代码生成而不是反射来序列化 .NET 对象。在.NET 5,我们和 Protobuf 团队合作并支持了内存API的序列化, 包括 Span<T>, ReadOnlySequence<T>, IBufferWriter<T>
, 在.NET 6, 序列化的性能得到进一步的优化和提升。
protocolbuffers/protobuf#8147[3] 支持了矢量化字符串的序列化。SIMD 指令允许并行处理多个字符,从而在序列化某些字符串值时显著提高性能。
private string _value = new string(' ', 10080);private byte[] _outputBuffer = new byte[10080];[Benchmark]public void WriteString(){ var span = new Span<byte>(_outputBuffer); WriteContext.Initialize(ref span, out WriteContext ctx); ctx.WriteString(_value); ctx.Flush();}
Method | Google.Protobuf | Mean | Ratio | Allocated |
WriteString | 3.14 | 8.838 us | 1.00 | 0 B |
WriteString | 3.18 | 2.919 ns | 0.33 | 0 B |
protocolbuffers/protobuf#7645[4] 添加了一个用于创建 ByteString
实例的新 API, UnsafeByteOperations.UnsafeWrapByteString
, 如果您知道底层数据不会发生改变, 那么可以使用它创建, 这样如果应用程序处理大字节数据时并且您想降低垃圾收集的频率,这将非常有用。
var data = await File.ReadAllBytesAsync(@"c:large_file.json");// Safe but slow.var copied = ByteString.CopyFrom(data);// Unsafe but fast. Useful if you know data won't change.var wrapped = UnsafeByteOperations.UnsafeWrap(data);
gRPC 下载速度
gRPC 用户反映有时下载速度会变慢, 特别时较大的文件, 我们的调查发现,当内容大于初始的接收窗口大小时,并且客户端和服务器之间存在高延迟, 会导致网络阻塞和整体吞吐量降低。
这已在 dotnet/runtime#54755[5] 中修复。HttpClient 现在动态缩放接收缓冲区窗口。建立 HTTP/2 连接后,客户端将向服务器发送 ping 以测量延迟。如果存在高延迟,客户端会自动增加接收缓冲区窗口,从而实现快速、连续的下载。
private GrpcChannel _channel = GrpcChannel.ForAddress(...);private DownloadClient _client = new DownloadClient(_channel);[Benchmark]public Task GrpcLargeDownload() => _client.DownloadLargeMessageAsync(new EmptyMessage());
Method | Runtime | Mean | Ratio |
GrpcLargeDownload | .NET 5.0 | 6.33 s | 1.00 |
GrpcLargeDownload | .NET 6.0 | 1.65 s | 0.26 |
HTTP/3 支持
.NET 上的 gRPC 现在支持 HTTP/3, 其中在 .NET 6 的 ASP.NET Core 和 HttpClient, 有关更多信息,请参阅 .NET 6 中的 HTTP/3 支持[6]。
.NET 是第一个支持端到端 HTTP/3 的 gRPC 实现,我们已经为其他平台提交了 gRFC[7],以便将来支持 HTTP/3。带有 HTTP/3 的 gRPC 是开发人员社区高度要求的功能[8],很高兴看到 .NET 在该领域处于领先地位。
总结
性能是 .NET 和 gRPC 的一个重要特性,而 .NET 6 比以往任何时候都快。客户端负载均衡和 HTTP/3 等以性能为导向的新功能意味着更低的延迟、更高的吞吐量和更少的服务器。这是一个节省资金、减少能耗和构建更环保的云原生应用程序的机会。
要试用新功能并开始在 .NET 中使用 gRPC,最好的起点是在 ASP.NET Core教程中 创建 gRPC 客户端和服务器[9]。
我们期待听到有关使用 gRPC 和 .NET 构建的应用程序以及您未来在dotnet和grpc 存储库中的贡献!
作者: James Newton-King
原文: https://devblogs.microsoft.com/dotnet/grpc-in-dotnet-6/
相关链接
[1]
gRPC 客户端负载平衡: https://docs.microsoft.com/zh-cn/aspnet/core/grpc/loadbalancing?view=aspnetcore-6.0[2]
gRPC 重试进行瞬态故障处理: https://docs.microsoft.com/zh-cn/aspnet/core/grpc/retries?view=aspnetcore-6.0[3]
protocolbuffers/protobuf#8147: https://github.com/protocolbuffers/protobuf/pull/8147[4]
protocolbuffers/protobuf#7645: https://github.com/protocolbuffers/protobuf/pull/7645[5]
dotnet/runtime#54755: https://github.com/protocolbuffers/protobuf/pull/7645[6]
.NET 6 中的 HTTP/3 支持: https://devblogs.microsoft.com/dotnet/http-3-support-in-dotnet-6/[7]
gRFC: https://github.com/grpc/proposal/pull/256[8]
开发人员社区高度要求的功能: https://github.com/grpc/grpc/issues/19126[9]
创建 gRPC 客户端和服务器: https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-6.0&tabs=visual-studio