(RPC通讯示意图)
为什么突然说到gRPC呢,其实以前就想说一说这个东西,也想尝试使用一下,一直没有机会,一直看我公众号的小伙伴肯定都知道,这几天一直在录制一个《eShopOnContainer微服务架构》系列,现在已经是8期了,里边涵盖了使用ASP.NETCore开发微服务的常用的基本的知识技能,具体的你可以看我的视频就行,B站也同步更新。
既然要说到了微服务,那肯定就离不开服务间调用,自然而然的就联系到了常用的一个框架——gRPC了,那今天就简单的说一说这个框架,也算是一个刚入门的,比较简单,后边我也会持续跟进讲解。
划
重
点
gRPC是什么?
用官网的一句话就是:A high-performance, open-source universal RPC framework。
要说gRPC,那就先说下什么的RPC框架,所谓RPC(remote procedure call 远程过程调用)框架实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从server/client模型。使用的时候客户端调用server端提供的接口就像是调用本地的函数一样。
而gRPC就是一个由Google开源的,跨语言的,高性能的远程过程调用(RPC)框架。gRPC使客户端和服务端应用程序可以透明地进行通信,并简化了连接系统的构建。它使用HTTP/2作为通信协议,使用 Protocol Buffers 作为序列化协议。
可能第一次看到这种通信框架比较陌生,介绍的也比较官方和抽象,这里说一下另一个常用的服务间通讯的方案,你可能就明白了,那就是RESTFul风格的API,想必每个人都用过RestfulAPI吧,这里就先简单说下RestfulAPI,如果这个还不是很理解的话,建议死记硬背。
1、REST,即Representational State Transfer的缩写。直接翻译的意思是"表现层状态转化"。
2、它是一种互联网应用程序的API设计理念:URL定位资源,用HTTP动词(GET,POST,DELETE,PUT,DETC)描述操作,比如只需要知道/api/blog,你就知道了他的常见的CURD多种操作。
3、简单来说就是url地址中只包含名词表示资源,使用http动词表示动作进行操作资源,将软件和网络这两个领域一定程度上结合起来。
4、之所以灵活,是因为他很少参与业务逻辑,只定义资源操作。
看完了RestfulAPI,你应该就能明白gRPC是干什么的了吧。
那两者有什么区别呢,平时在前后端分离或者移动端需要后端api的场景下,经常使用Restful丰富的API,既然大家已经习惯并熟悉了Restful,为何还用gRPC呢?
PS:下边的内容我基本是摘抄于官网和网络,文末有参考连接,今天主要是介绍下如何操作代码,文字讲解不是重点。
为什么要使用gRPC?
问题:既然是server/client模型,那么我们直接用restful api不是也可以满足吗,为什么还需要RPC呢?
我这里简单说明下优缺点和比较,说说到底使用gRPC有什么好处。
gRPC 和 Restful API
gRPC和Restful API都提供了一套通信机制,用于server/client模型通信,而且它们都使用http作为底层的传输协议(严格地说, gRPC使用的http2.0,而Restful api则不一定)。
不过gRPC还是有些特有的优势,如下:
1、gRPC可以通过protobuf来定义接口,从而可以有更加严格的接口约束条件。
2、通过protobuf可以将数据序列化为二进制编码,这会大幅减少需要传输的数据量,从而大幅提高性能。
3、gRPC可以方便地支持流式通信.
场景与好处????
1、需要对接口进行严格约束的情况。
比如我们提供了一个公共的服务,很多人,甚至公司外部的人也可以访问这个服务,这时对于接口我们希望有更加严格的约束,我们不希望客户端给我们传递任意的数据,尤其是考虑到安全性的因素,我们通常需要对接口进行更加严格的约束。这时gRPC就可以通过protobuf来提供严格的接口约束。
2、对于性能有更高要求的轻量级微服务。
有时我们的服务需要传递大量的数据,而又不希望影响到我们的性能,这个时候也可以考虑gRPC服务,因为通过protobuf我们可以将数据压缩编码转化为二进制格式,通常传递的数据量要小得多,而且通过http2我们可以实现异步的请求,从而大大提高了通信效率。
同时,更适应于网络受限的环境,使用 Protocol Buffers二进制序列化消息,该序列化始终小于等效的JSON消息,对网络带宽需求比JSON小。
3、需要对接多种语言的微服务的情况。
比如我们公司的项目,有JAVA组,有Python组,或者.NETCore组别,每个组当然负责各自独立的子服务部分,那就需用用到不同语言之间的服务调用问题,不希望出现兼容性问题。这个时候就用到了gRPC了,它协定优先 API 开发,默认使用协议缓冲区,允许与语言无关的实现。可用于多种语言的工具,以生成强类型服务器和客户端。gRPC工具支持所有流行的开发语言,使gRPC成为多语言开发环境的理想选择。
4、需要处理流式处理请求或响应的点对点实时服务
gRPC用更小的网络带宽,又支持客户端、服务器和双向流式处理调用,更好的帮助处理流式请求。
(理论上通过http2.0就可以使用streaming模式, 但是通常web服务的Restful api似乎很少这么用,通常的流式数据应用如视频流,一般都会使用专门的协议如HLS,RTMP等,这些就不是我们通常web服务了,而是有专门的服务器应用。)
也并不是十全十美的????
任何开发工具或者项目框架都不是十全十美的,就算是K8s、微服务或者DDD这么火热的技术也并不是无脑就上的,gRPC框架也有一定的弊端,或者至少是某些场景下是不适合的:
1、浏览器可访问的API。
浏览器不完全支持gRPC。虽然gRPC-Web可以提供浏览器支持,但是它有局限性,引入了服务器代理
2、广播实时通信
gRPC支持通过流进行实时通信,但不存在向已注册连接广播消息的概念
3、进程间通信
进程必须承载HTTP/2才能接受传入的gRPC调用,对于Windows,进程间通信管道是一种更快速的方法。
如何.NETCore上使用gRPC?
关于如何在ASP.NETCore上使用gRPC,这里有两种方法,第一是直接创建gRPC模板项目,第二个就是在在ASP.NETCore项目上创建gRPC服务。其实这两个原理和操作流程都是差不多的,我这里都说一下吧。
通过模板创建gRPC服务
打开VS2019(版本至少16.3+),新建项目,搜索"gRPC",就能看到一个选项,
点击下一步,填写好项目名称和项目地址以后,点击创建,
然后可以看到NetCore版本是3.1,然后不勾选Docker,点击创建。
等待新建好项目,就可以看到默认的文件是这样的,其实和我们创建ASP.NETCore项目是很相似的,如果说真的不一样,就是依赖包和多了一个Protos的文件夹,那下边我们来一一看看都是怎么作用的:
1、依赖包
Grpc.AspNetCore是gRPC结合ASP.NETCore封装的一个类库,其中很重要的是下边的两个依赖包,第一个就是Protobuf,第二个就是Tools,从名字上应该都能大概猜出来是干啥的,肯定有一个是解析protobuf文件的,一个是工具包,负责一些操作的。
2、Protos文件夹
在文章的开头我们已经说过了,gRPC很重要的一点,就是在请求和相应的的时候需要用到一个.proto的文件,用来定义服务和提供参数已经响应的参数。
默认的内容是这样的:
// 语法结构,使用pb3
syntax = "proto3";
// 定义命名空间,一般是项目名或者解决方案名
option csharp_namespace = "GrpcService1";
// 定义服务的包
package greet;
// 定义具体的服务
service Greeter {
// 定义某一个方法API,格式是:rpc 方法名(请求参数对象名) returns(返回参数对象名)
rpc SayHello (HelloRequest) returns (HelloReply);
}
// 定义请求的对象名
message HelloRequest {
// 有一个属性字段是name
string name = 1;
}
// 定义返回的对象名
message HelloReply {
// 有一个返回的字段是message
string message = 1;
}
可以看到虽然是扩展名是.proto的文件,但是语法结构很像一个.cs文件,语法上也类似,当然只不过是类似,具体的意思我已经在上边注释了,你看看就能明白。
你可能会好奇,那我定义好了这一个文件,怎么来使用呢,别着急,分成两步,咱们先说第二步定义具体服务,第一步先卖个关子。
3、GreeterService服务
上边我们定义好了proto文件,下边就需要针对这个配置,设计服务了,因为proto仅仅是定义了服务,还没有具体的内容,那很简单,就直接看代码吧。
///
/// 根据.proto定义具体的服务
/// GreeterService可以任意定义
/// Greeter.GreeterBase 根据.proto文件中定义的规则来
///
public class GreeterService : Greeter.GreeterBase
{
// 和ASP.NETCore一样,可以使用依赖注入和服务
private readonly ILogger _logger;
public GreeterService(ILogger logger)
{
_logger = logger;
}
///
/// 重写 设计对应的多个接口
/// 一般都是异步处理
///
///
///
///
public override Task SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Message = "Hello " + request.Name
});
}
}
我也同样的把注释都写上了,其实内容都是很简单的,我们都已经用了ASPNETCore这么久了,肯定都一样都能看明白,有两个问题肯定你会问:
第一、定义服务我明白,但是继承的父类Greeter.GreeterBase是如何处理的呢?
这个就是我第二步说完.proto文件的时候卖的那个关子,我们定义好了.proto文件后,系统会自动给我们创建生成服务、客户端和消息(表示传递的数据)的C# Class,但是需要一个操作:
右键项目,编辑项目文件,可以看到有一个配置:
这个是官方默认模板已经创建好的,是一个Server类型的服务,Include对应的文件,然后项目就能自动给我们创建好了父类,你可以从obj文件夹看到:
这个时候,就可以继承了。
第二、如何重写对应的方法呢?
很简单,直接针对当前的类型,alt+enter,在智能提示里,找到重写,就可以看到要重写的接口了:
4、appsettings.json
注意这里别之前不一样的地方,就是定义了一个节点:
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http2"
}
}
在上边我们说过,gRPC 需要 HTTP/2。 适用于 ASP.NET Core 的 gRPC 验证 HttpRequest.Protocol 为 HTTP/2。
Kestrel 在大多数新式操作系统上支持 HTTP/2。默认情况下,Kestrel 终结点配置为支持 HTTP/1.1 和 HTTP/2 连接。
5、Startup.cs
其他的文件内容都类似,我就不多说了,我们都知道,要用一个服务,就需要注册这个服务,那就肯定需要是Startup里,
// 注册grpc服务
services.AddGrpc();
// 在结点路由里配置指定的服务
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService();
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
});
});
项目启动,
可以看到,只有https安全协议的,因为上边已经说过了使用gRPC必须是http/2的,同样也是需要https安全协议的。
到这里就没有问题了,说完了系统默认模板创建的方案,那现在我们不用这个方案,尝试一下,如果已经创建好了一个NetCore的API项目,比如我的Blog.Core,如何在这个基础上,创建gRPC服务呢?
基于ASP.NETCore项目创建
因为上边我们已经讲完了对应的内容和注意事项,为了篇幅不罗嗦,我就直接创建,看看是否真的可以:
还是在当然解决方案,创建一个netcore的api项目,然后添加三个nuget包:
接着添加helloworld.proto文件,配置.csproj项目配置,包含当前的.proto文件,创建HelloWorldservice.cs服务类,继承刚刚创建好的父类Hello.HelloBase,最后,注册服务,配置中间件,相应的操作可以看下边的视频:
现在我们已经定义了好了server端的服务,那如何发起调用呢,需要一个client客户端。
如何发起调用?
1、创建一个netcore的控制台
还是在该解决方案中,添加一个控制台项目
然后添加三个依赖包:
all
runtime; build; native; contentfiles; analyzers; buildtransitive
注意,这里我们并没有添加其他的项目引用!
2、把Hello.proto拷贝到控制台
这个很简单,只需要直接把文件夹和文件直接拖动过去就行了。
然后配置下.csproj文件,修改下gprc的服务类型,一定是client:
3、发起调用
我这里就直接写代码了
class Program
{
static async System.Threading.Tasks.Task Main(string[] args)
{
// 创建通道
var channel = GrpcChannel.ForAddress("https://localhost:5001");
// 发起客户端调用
var client = new Hello.HelloClient(channel);
// api请求,传递参数
var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });
Console.WriteLine("Greeting: " + response.Message);
}
}
大概意思就是这样的,应该能够看懂的。
运行我们的gRPC服务,也就是运行core的webapi程序,然后运行客户端控制台:
看到没有,我们并没有在控制台去引用我们的gRPC服务端的代码,只需要一个.proto文件,就能够像调用方法一样,去调用其他服务端项目的服务,这就是很大的直观上的好处。
当然好处还有很多的,比如什么是流式,如何实现服务间调用,如何网关配置等等等等,咱们下次再见吧。