
通过ASP.NET Core开发gRPC服务实现高性能远程过程调用
你好,我是源码库的技术博主。在微服务架构大行其道的今天,服务间的通信效率直接决定了系统的整体性能。你是否曾为REST API的JSON序列化开销和HTTP/1.1的队头阻塞感到头疼?今天,我想和你分享一个我最近在项目中深度实践的技术方案:使用ASP.NET Core构建gRPC服务。它基于HTTP/2和ProtoBuf,能带来显著的性能提升和强类型接口体验。下面,我将带你从零开始,一步步搭建一个完整的gRPC服务与客户端,并分享一些我踩过的“坑”和最佳实践。
一、 项目准备与环境搭建
首先,我们需要一个清晰的目标。我们将创建一个简单的“订单查询”服务。服务端用ASP.NET Core实现,客户端是一个控制台应用。确保你的开发环境满足以下条件:
- .NET 8 SDK 或更高版本(本文基于.NET 8)。
- Visual Studio 2022、Rider或VS Code。
打开你的终端或IDE,我们开始创建解决方案和项目。我习惯先建一个解决方案目录,让结构更清晰。
mkdir GrpcOrderDemo
cd GrpcOrderDemo
dotnet new sln -n GrpcOrderDemo
接下来,创建服务端项目。gRPC模板已经集成在ASP.NET Core Web API模板中。
dotnet new webapi -n OrderService.Grpc --use-controllers false
dotnet sln add OrderService.Grpc
然后,创建客户端项目。我们用一个控制台程序来模拟消费者。
dotnet new console -n OrderClient
dotnet sln add OrderClient
现在,为两个项目添加必要的NuGet包。这是关键一步,版本一致性很重要,我建议在项目文件中统一指定版本。
在 OrderService.Grpc.csproj 中,确保包含:
在 OrderClient.csproj 中,添加:
踩坑提示:Grpc.Tools 包必须设置 PrivateAssets="All",这样它只在编译时用于生成代码,不会作为依赖传递到输出程序集中。
二、 定义服务契约:编写.proto文件
gRPC的核心是协议缓冲区(Protocol Buffers)语言定义的服务契约。这就像一份双方都必须遵守的强类型API合同。在解决方案根目录下创建一个 Protos 文件夹,然后新建 order.proto 文件。
syntax = "proto3";
option csharp_namespace = "GrpcOrderDemo";
package order;
// 定义服务
service OrderService {
rpc GetOrder (GetOrderRequest) returns (OrderReply);
rpc GetOrderStream (GetOrderRequest) returns (stream OrderItem); // 服务端流示例
}
// 请求消息
message GetOrderRequest {
string order_id = 1;
}
// 响应消息
message OrderReply {
string order_id = 1;
string customer_name = 2;
double total_amount = 3;
repeated OrderItem items = 4; // repeated 表示列表
OrderStatus status = 5;
}
// 子消息
message OrderItem {
string product_id = 1;
string product_name = 2;
int32 quantity = 3;
double unit_price = 4;
}
// 枚举
enum OrderStatus {
UNKNOWN = 0;
CREATED = 1;
PAID = 2;
SHIPPED = 3;
DELIVERED = 4;
}
这个文件定义了一个包含两个方法的服务:一个普通RPC调用,一个服务端流式调用。注意字段后面的数字是字段编号,一旦定义,在生产环境中就绝不能修改,这是ProtoBuf的兼容性规则。
接下来,我们需要让两个项目都能使用这个契约。编辑项目文件,将.proto文件包含进来。
在 OrderService.Grpc.csproj 中添加:
在 OrderClient.csproj 中添加:
GrpcServices 属性告诉工具生成服务器端桩代码、客户端桩代码,或者两者都生成(Both)。现在编译一下项目,你会在 obj/Debug/net8.0 目录下看到生成的C#文件。这些文件包含了强类型的请求、响应类以及服务基类和客户端存根。
三、 实现gRPC服务端
服务端实现很简单,继承自生成的服务基类并重写方法。在服务端项目 OrderService.Grpc 中,创建 Services 文件夹,并添加 OrderServiceImpl.cs。
using Grpc.Core;
using GrpcOrderDemo;
namespace OrderService.Grpc.Services;
public class OrderServiceImpl : OrderService.OrderServiceBase
{
private readonly ILogger _logger;
// 模拟一个内存数据源
private static readonly Dictionary _orderData = new()
{
["ORD-001"] = new OrderReply
{
OrderId = "ORD-001",
CustomerName = "张三",
TotalAmount = 299.98,
Status = OrderStatus.Paid,
Items =
{
new OrderItem { ProductId = "P-100", ProductName = "《ASP.NET Core实战》", Quantity = 1, UnitPrice = 89.99 },
new OrderItem { ProductId = "P-101", ProductName = "《gRPC指南》", Quantity = 1, UnitPrice = 109.99 }
}
}
};
public OrderServiceImpl(ILogger logger)
{
_logger = logger;
}
public override Task GetOrder(GetOrderRequest request, ServerCallContext context)
{
_logger.LogInformation("收到订单查询请求,ID: {OrderId}", request.OrderId);
if (_orderData.TryGetValue(request.OrderId, out var order))
{
return Task.FromResult(order);
}
// gRPC错误处理:抛出RpcException
throw new RpcException(new Status(StatusCode.NotFound, $"订单 {request.OrderId} 不存在"));
}
public override async Task GetOrderStream(GetOrderRequest request, IServerStreamWriter responseStream, ServerCallContext context)
{
_logger.LogInformation("开始流式返回订单 {OrderId} 的商品项", request.OrderId);
if (!_orderData.TryGetValue(request.OrderId, out var order))
{
throw new RpcException(new Status(StatusCode.NotFound, $"订单 {request.OrderId} 不存在"));
}
foreach (var item in order.Items)
{
// 模拟一些处理延迟
await Task.Delay(500);
// 检查客户端是否已取消请求(例如关闭了连接)
if (context.CancellationToken.IsCancellationRequested)
{
_logger.LogInformation("客户端取消了流式请求。");
break;
}
await responseStream.WriteAsync(item);
_logger.LogInformation("已流式发送商品: {ProductName}", item.ProductName);
}
_logger.LogInformation("订单商品流式发送完毕。");
}
}
接下来,在 Program.cs 中注册gRPC服务并映射端点。这是ASP.NET Core的常规操作。
using OrderService.Grpc.Services;
var builder = WebApplication.CreateBuilder(args);
// 添加gRPC服务
builder.Services.AddGrpc();
var app = builder.Build();
// 配置HTTP请求管道
app.MapGrpcService();
// 添加一个健康检查端点,便于容器编排
app.MapGet("/", () => "gRPC服务运行中。通信需要通过gRPC客户端。");
app.Run();
最后,修改 appsettings.json 中的 Kestrel 配置,确保它支持HTTP/2(gRPC的传输基础)。gRPC over HTTP/2是默认支持的,但明确配置是个好习惯。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http2"
}
},
"AllowedHosts": "*"
}
现在,运行服务端项目,它将在 https://localhost:5001 和 http://localhost:5000 上监听。注意控制台输出,gRPC服务现在已准备就绪。
四、 实现gRPC客户端
客户端实现同样直观。我们使用生成的强类型客户端。打开 OrderClient 项目的 Program.cs。
using Grpc.Core;
using Grpc.Net.Client;
using GrpcOrderDemo;
// 创建gRPC通道。通道是长期存活的,应该复用。
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
// 创建客户端实例
var client = new OrderService.OrderServiceClient(channel);
try
{
Console.WriteLine("=== 开始普通RPC调用 ===");
var request = new GetOrderRequest { OrderId = "ORD-001" };
var reply = await client.GetOrderAsync(request);
Console.WriteLine($"收到订单回复:");
Console.WriteLine($" 订单号: {reply.OrderId}");
Console.WriteLine($" 客户: {reply.CustomerName}");
Console.WriteLine($" 总金额: {reply.TotalAmount:C}");
Console.WriteLine($" 状态: {reply.Status}");
Console.WriteLine("n=== 开始服务端流式调用 ===");
var streamCall = client.GetOrderStream(request);
// 异步遍历响应流
await foreach (var item in streamCall.ResponseStream.ReadAllAsync())
{
Console.WriteLine($" 商品: {item.ProductName}, 数量: {item.Quantity}, 单价: {item.UnitPrice:C}");
}
Console.WriteLine("流式调用结束。");
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.NotFound)
{
Console.WriteLine($"错误: {ex.Status.Detail}");
}
catch (RpcException ex)
{
Console.WriteLine($"gRPC错误: {ex.Status.Code} - {ex.Status.Detail}");
}
实战经验:GrpcChannel 的创建开销较大,在真实应用中(如ASP.NET Core客户端),你应该通过依赖注入将其注册为单例(AddSingleton)并进行复用。另外,注意处理 RpcException,这是gRPC标准的错误传递方式。
五、 测试、部署与进阶思考
现在,先运行服务端,再运行客户端。你应该能在客户端控制台看到订单的详细信息,以及商品项一条条“流”出来的效果。这演示了gRPC流式处理的能力,非常适合大数据集或实时通知场景。
关于部署,在Docker或Kubernetes中部署gRPC服务与部署普通ASP.NET Core应用无异。但需要注意:
- 负载均衡:gRPC基于HTTP/2长连接,传统的L4负载均衡可能导致连接不均衡。建议使用L7负载均衡器(如Envoy, Linkerd, 或支持HTTP/2的Nginx)或客户端负载均衡。
- 健康检查:使用
Grpc.AspNetCore.HealthChecks包提供标准的gRPC健康检查协议。 - 安全性:生产环境务必使用TLS。在Kestrel配置中启用HTTPS并强制使用HTTP/2。
最后,gRPC并非银弹。它的强类型和性能优势在内部微服务间通信中非常突出,但对于需要暴露给外部浏览器或移动端的情况,gRPC-Web是一个补充方案。同时,像服务网格(Service Mesh)这样的基础设施正在让gRPC这类高性能RPC变得更加易用和强大。
希望这篇教程能帮你顺利开启ASP.NET Core gRPC之旅。如果在实践中遇到问题,欢迎在源码库社区交流讨论。记住,理解底层协议(HTTP/2, ProtoBuf)和工具链的运作方式,是解决复杂问题的关键。祝你编码愉快!

评论(0)