一、简单介绍RPC RPC(RemoteProcedureCall)远程过程调用,它允许在一台服务器程序中调用另一台服务器上的子程序。RPC采用了代理模式从而屏蔽了网络之间的调用细节,使得调用远程函数就像调用本地程序一样简单。 RPC它包含了接口规范、传输协议、数据序列化反序列化规范。它可以基于HTTP或TCP协议之上来构建RPC协议,其各自都有优缺点。Google选择将GRPC定义在HTTP2通信协议之上。GRPC的优势由HTTP2和Protobuf继承而来。Dubbo国内最早的RPC框架、偏向服务治理、阿里巴巴开源支持java语言Motan微博内部使用的RPC框架、2016年开源仅支持java语言Tars腾讯内部使用的RPC框架、2017年开源仅支持C语言SpringCloud国外Pivotal公司于2014年开源、仅支持java语言gRPCGoogle于2015年开源的跨语言RPC框架、支持多种语言ThriftApache开源项目之一(原Facebook)、支持多种语言 二、什么是gRPC gRPC是由Google开发的一个高性能、开源、跨语言使用的RPC框架。主要基于HTTP2通信协议标准设计,基于Protobuf(ProtocolBuffers)序列化协议开发,采用IDL文件定义服务而设计,同时支持大多数主流的编程语言。 IDL接口描述语言(InternalDefineLanguage)使用的是protobuf,通过proto3工具生成指定语言的数据结构、服务端接口以及客户端接口的存根(Stub) Protobuf作为轻量级的结构化数据存储格式,可以用于结构化、数据序列化、即时通讯、数据存储等领域。它是与语言无关、平台无关、可扩展的序列化结构数据格式,其功能类似于json、xml。但是Protobuf比它们更小、更快、更简单,所以Protobuf更适合RPC作为数据交换格式。 Protobuf的核心内容:定义接口:接口路径和参数,以service标识定义消息:消息的结构体,以message标识 通过Protobuf提供的机制,服务端与客户端之间只需要关注接口方法名(service)和参数(message)即可通信,而不需关注繁琐的链路协议和字段解析,极大降低了服务端的设计开发成本。 通信协议使用的是HTTP2,其特点如下:HTTP2采用的是二进制格式的传输协议,而不是HTTP1。x的文本格式,支持多路复用,即通过一个连接发送多个并发的请求。而HTTP1。x虽然可以通过pipeline实现并发请求,但是多个请求之间的响应仍会被阻塞。支持服务端推送,即客户端发送一个请求,服务器可以对客户端发送多个响应。而不像HTTP1。x只能通过客户端发起请求,服务端才响应。支持头部压缩,HTTP2对传输的消息头进行了压缩传输,节省了头部在网络传输过程所占用的网络流量。 gRPC基于HTTP2对流传输提供了如下几种支持,在后面的案例也是基于这几种流式体验的。UnaryRP,一元RPCServersidestreamingRPC,服务端流式RPCClientsidestreamingRPC,客户端流式RPCBidirectionalstreamingRP,双向流式RPC gRPC的调用模型: 大致介绍一下流转的流程:客户端(gRPCStub)调用A方法,发起RPC调用对请求信息使用Protobuf进行对象序列化压缩(IDL)服务端(gRPCServer)接收到请求后,解码请求体,进行业务逻辑处理并返回对响应结果使用Protobuf进行对象序列化压缩(IDL)客户端接受到服务端响应,解码请求体。回调被调用的A方法,唤醒正在等待响应(阻塞)的客户端调用并返回响应结果 三、准备Protobuf环境 1)安装Protobuf的编译器protocbrewsearchprotobufbrewinstallprotobufprotocversio 2)安装go语言的protobuf插件 注意:从google。golang。org官网下载网上很多资源是旧版本的地址github上,目前已经被google收录管辖。其生成的。proto文件也跟之前的不同,甚至会报错。goinstallgoogle。golang。orggrpccmdprotocgengogrpcv1。2goinstallgoogle。golang。orgprotobufcmdprotocgengov1。28 插件的安装路径在gobin目录下 3)修改环境变量使得全局可用vi。zshrcexportGOPATHgoexportPATHPATH:GOPATHbinsource。zshrc 四、gRPC初体验 1)创建一个项目目录,其结构如下 首先在proto目录下创建一个文件且扩展名为。proto,我这边取名叫protolist。proto文件其次分别在server和client文件夹下创建相应名称的。go文件,我这边分别叫server。go和client。go 2)在protolist。proto文件中定义gRPC服务、方法请求、响应类型 首先指定proto的版本,默认是proto2版本,我们需要手动指定版本为proto3类型(可以了解一下proto2和proto3的区别)且需要写在第一行。syntaxproto3; 然后指定生成的go文件在哪个目录中,以及包的命名空间是什么,使用分号分割。(。)表示当前目录,其包名为(proto)可随意取,optiongopackage。; 上面也说过Protobuf的核心内容有两个:service和message,即定义服务接口和消息。 定义服务和服务的方法,gRPC支持四种服务:一元RPC、服务端流式RPC、客户端流式RPC、双向流式RPC。那就先体验双向流式PRC吧。serviceGreeter{双向流RPCrpcSayRoute(streamHelloRequest)returns(streamHelloReply){}} 上面的代码定义了一个名字为Greeter的服务,里面包含了一个双向流式的RPC方法。其意味着,双方可以使用读写流发送一系列消息。注意,这两个流独立运行,在客户端和服务端可以按照任意的顺序读取和写入。 定义消息类型,请求的消息类型以及请求响应的消息类型messageHelloRequest{stringname1;}messageHelloReply{stringmessage1;} 这段代码看着就很眼熟呀,有点像结构体。不过人家叫消息体,消息体内的类型支持多种,这里就不列举了,可以自行查阅。其中name1也不是为了赋值,它表示此变量在消息体内的位置。 最后在当前目录下执行如下命令:protocgoout。gooptpathssourcerelativegogrpcout。gogrpcoptpathssourcerelative。。protogoout。表示指定源文件地址gooptpathssourcerelative:paths表示生成的go代码的位置sourcerelative表示按proto源文件的目录层级去创建Go代码的目录层级,如果目录已存在则不用创建 此时,会在当前目录下生成以下go文件。 protolist。pb。go包含用于填充、序列化、检索请求、响应消息类型的所有协议及缓冲区代码protolistgrpc。pb。go客户端使用服务中定义的方法调用的接口类型(或存根)服务端要实现的接口类型,也使用服务中定义的方法 3)服务端实现,注册服务 在server目录下的server。go文件中注册一个结构体为GreeterServers的服务。并监听一个端口号,在服务端运行着funcmain(){server:grpc。NewServer()proto。RegisterGreeterServer(server,GreeterServers{})lis,:net。Listen(tcp,:8090)server。Serve(lis)} 在protolistgrpc。pb。go文件中有一个GreeterServer的接口,里面包含了两个方法,我们在server。go文件中就需要按照协议接口来实现所有的方法。GreeterServeristheserverAPIforGreeterservice。AllimplementationsmustembedUnimplementedGreeterServerforforwardcompatibilitytypeGreeterServerinterface{SayRoute(GreeterSayRouteServer)errormustEmbedUnimplementedGreeterServer()} 在server。go中声明一个GreeterServers结构体中里面继承了一个结构体。typeGreeterServersstruct{proto。UnimplementedGreeterServer} 这里直接继承proto(proto就是生成的grpc。pb。go文件的引用)下面的结构体即可,mustEmbedUnimplementedGreeterServer这个方法在协议中已经被这个UnimplementedGreeterServer结构体实现了。 下面实现SayRoute方法func(sGreeterServers)SayRoute(streamproto。GreeterSayRouteServer)error{for{stream。Send(proto。HelloReply{Message:hahahaha})resp,err:stream。Recv()iferrio。EOF{returnnil}iferr!nil{returnerr}log。Printf(resp:v,resp)}} 其中Send和Recv都是GreeterSayRouteServer接口中的方法,用于发送消息和接收消息。 此时运行gorunserver。go文件,让TCP端口号为8090服务跑起来。 4)客户端实现 客户端连接上服务端的端口:8090,然后调用服务端的SayRoute()方法,在通过Send()方法发送消息,使用Recv()方法接收消息。整个流程就像调用本地方法一样。funcmain(){conn,:grpc。Dial(:8090,grpc。WithInsecure())deferconn。Close()client:proto。NewGreeterClient(conn)SayRoute(client,proto。HelloRequest{Name:cici})}funcSayRoute(clientproto。GreeterClient,rproto。HelloRequest)error{stream,:client。SayRoute(context。Background())forn:0;n6;n{stream。Send(r)resp,err:stream。Recv()iferrio。EOF{break}iferr!nil{returnerr}log。Printf(resperr:v,resp)}stream。CloseSend()returnnil} 下面运行一下,如下是输出内容: 其实,双向流式RPC就是指两端可以同时使用读写流发送消息。这两个流可以独立运行,可以根据业务需求完善流的交互顺序。 总结这次gRPC案例初体验,我们简单的了解到什么是RPC以及gRPC的设计理念,什么是Protobuf以及它的核心内容是什么。最后实践了双向流的简单demo。线下在学习的过程中对安装Protobuf插件的确走了一点弯路,好在最后走出来了。然后呢特别感谢《Go语言编程之旅》的作者让我这个小儿有幸在gRPC的世界里稍微扑腾了一下。 五、参考来源 《Go语言编程之旅》 https:blog。csdn。netweixin42905141articledetails125272803 https:developers。google。comprotocolbuffersdocsreferencegogenerated?hlzhcn https:blog。csdn。neti19970916articledetails125733894