gRPC微服务内部通讯的高效实现
上一篇文章我们有介绍.NetCore下服务向外请求管理的最佳实践,本篇继续介绍微服务之间的通信方式gRPC。无论是Http请求协议还是gRPC请求协议都可以用于外部或内部微服务直接的通讯方式。只是通常我们向外提供服务基于Http更方便和易实现,而内部通讯基于gRPC更高效。
什么是gRPC?
gRPC是一个远程过程调用框架,它实现了让我们调用远程方法就像是在调用本地方法一样。它是由Google公司发起并开源,所以开头g代表的就是Google,RPC代表远程过程调用。
gRPC有哪些特点
提供所有主流语言的实现,提供了各种语言的包,打破语言隔阂
基于HTTP2,开放协议,受到广泛的支持,易于实现和集成
默认使用Protocol Buffers序列化,相比较于Restful JSON效率好很多
工具链成熟,代码生成便捷,开箱即用
支持双向流式的请求和响应,对批量处理,低时延场景友好
.NET生态对gRPC的支持情况
提供基于HttpClient的原生框架实现
提供原生的ASP.NET Core集成库
提供完整的代码生成工具
Visual Studio和Visual Studio Code提供Proto文件智能提示
.NetCore实现gRPC
接下来,我们尝试基于.NetCore实现gRPC服务通信实践。
要实现gRPC通信,我们需要引用一些核心的依赖包,如下:
服务端核心包:
客户端核心包:
在gRPC服务中会有一个.proto文件,.proto文件的作用主要是定义包名、库名,定义服务,定义服务的输入、输出的类型。基于这些定义,通过Grpc.Tools生成我们的服务端代码和客户端代码。这一点和WebService基于WSDL生成服务代理代码有点类似。
gRPC异常处理需要用到的包:
Grpc.Core.RpcException Grpc异常处理包
Grpc.Core.Interceptors.Interceptor Grpc拦截器包,可以实现通过注入拦截器实现异常处理
gRPC与HTTPS证书
gRPC使用的HTTP2协议,默认HTTP2协议使用了HTTPS加密协议,所以发布的时候需要HTTPS证书,不过gRPC同时也提供了不使用证书的方法。
接下来,我们开始创建工程,来实现一个Grpc的demo。dotnet3.1提供了grpc模板类型。
一、创建Grpc服务项目
首先我们直接使用命令来创建一个grpc服务项目
dotnet new grpc -n Grpc.Service
这样我们的服务项目就创建好了。完成后我们先来看一下生成的csproj文件都有些什么。
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.24.0" />
</ItemGroup>
</Project>
上面就是一个全新Grpc服务项目文件的全部内容,我们可以看到还是熟悉的SDK:Microsoft.NET.Sdk.Web,这和我们创建的普通的webapi项目是完全一样的。不同的部分在于grpc类型的项目默认帮我们干了三件事:
引用了Grpc.AspNetCore包
默认生成了一个proto文件,Protos\greet.proto
同时为proto文件生成了grpc服务代码在Services目录下,我们只要填充该服务,完成实际的业务处理代码编码就可以了
greet.proto文件就是项目模板默认生成的grpc服务,我们来修改一下服务名称为material,变成我们期望的服务名称。
二、编写proto文件
我们再打开默认生成的proto文件看下
syntax = "proto3";
option csharp_namespace = "Grpc.Service";
package material;
// The greeting service definition.
service material {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply);
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings.
message HelloReply {
string message = 1;
}
默认生成的proto文件定义了一个服务方法SayHello,输入是HelloRequest,输出是HelloReply。所以proto文件其实就是定义一个服务,最终对应到实现这个服务的具体类。在这里我要修改下服务的定义和实现,变成我期望的。修改后如下:
syntax = "proto3";
option csharp_namespace = "Grpc.Service";
package material;
// The greeting service definition.
service material {
// Sends a greeting
rpc Add (MaterialModel) returns (MaterialReturn);
rpc Reduce (MaterialModel) returns (MaterialReturn);
}
// 物料模型
message MaterialModel {
string sn = 1 ;
string keypart_no = 2 ;
int32 quantity = 3 ;
}
// 物料处理消息回复模型
message MaterialReturn {
bool ret = 1 ;
int32 status = 2 ;
string message = 3 ;
}
同时更新csproj引用的proto文件名称和Service类的代码,编译生成。这样就完成了Grpc服务代码了。
注意:同时强调一下,上面的proto文件中我们有看到定义的模型类的属性,如string sn = 1这样子的,这里的每个属性需要定义索引,这也是protobuf和json的差别,protobuf是按照属性的顺序是序列化的。
三、添加服务注册
最后,要将我们的Grpc服务暴露出去,别忘了要在Startup类中注册并添加Map哦。
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ……
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<MaterialService>();
});
}
四、客户端调用
接下来,我们创建客户端项目:dotnet new webapi -n Grpc.Client
这次我们创建普通的webapi项目,然后手动添加引用包,来实现grpc服务的引用和调用。通过以下命令来引入包
dotnet tool install dotnet-grpc -g
dotnet grpc add-file ../Grpc.Service/Protos/material.proto
上面第一条命令,安装全局的grpc tool工具。第二条命令引用我们刚刚实现的Grpc服务的proto文件。
然后在Startup中注册GrpcClient,代码如下:
public void ConfigureServices(IServiceCollection services)
{
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); //不使用Https证书
services.AddGrpcClient<Grpc.Service.material.materialClient>(options =>
{
options.Address = new Uri("http://localhost:8002");
})
.ConfigurePrimaryHttpMessageHandler(provider =>
{
var handler = new SocketsHttpHandler();
handler.SslOptions.RemoteCertificateValidationCallback = (a, b, c, d) => true; //移除证书验证
return handler;
});
// ……
}
接着我在启动的时候,请求Grpc服务,并执行Add方法,代码如下:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ……
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
materialClient service = context.RequestServices.GetService<materialClient>();
MaterialReturn r=null;
try
{
r = service.Add(new MaterialModel { Sn="123123",KeypartNo = "abc",Quantity=10 });
}
catch (Exception ex)
{
r=new MaterialReturn{Message=ex.Message,Ret=false,Status=500};
}
await context.Response.WriteAsync($"Ret={r.Ret},Status={r.Status},Message={r.Message}",Encoding.UTF8);
});
});
}
分别运行Grpc.Service和Grpc.Client,这里我设置的服务端启动端口8002,客户端5000,浏览器输入http://localhost:5000,返回结果如下
Grpc服务调用成功。
关于Grpc的实例讲解就到这里,感兴趣的同学可以到Github上下载demo源码练习。
回首向来萧瑟处,归去,
也无风雨也无晴。