vlambda博客
学习文章列表

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.AspNetCore

客户端核心包:

Google.Protobuf                          序列化协议包
Grpc.Net.Client                            客户端包
Grpc.Net.ClientFactory               与HttpClient集成的包
Grpc.Tools                                    提供命令行工具包,基于Protocol文件生成服务代码


        在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类型的项目默认帮我们干了三件事:

  1. 引用了Grpc.AspNetCore包

  2. 默认生成了一个proto文件,Protos\greet.proto

  3. 同时为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 -gdotnet 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源码练习。





回首向来萧瑟处,归去,

也无风雨也无晴。