揭开gRPC神秘面纱

一、什么是RPC

RPC(Remote Procedure Call):
远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的思想。
RPC 是一种技术思想而非一种规范或协议,常见 RPC 技术和框架有:
应用级的服务框架:阿里的 Dubbo/Dubbox、Google gRPC、Spring Boot/Spring Cloud。
远程通信协议:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。
通信框架:MINA 和 Netty。
目前流行的开源 RPC 框架还是比较多的,有阿里巴巴的 Dubbo、Facebook 的 Thrift、Google 的 gRPC、Twitter 的 Finagle 等。

揭开gRPC神秘面纱_第1张图片
RPC 的核心功能是指实现一个 RPC 最重要的功能模块,就是上图中的”RPC 协议”部分:
一个 RPC 的核心功能主要有 5 个部分组成,分别是:

  • 客户端
  • 客户端 Stub
  • 网络传输模块
  • 服务端 Stub
  • 服务端
    揭开gRPC神秘面纱_第2张图片

下面分别介绍核心 RPC 框架的重要组成:

  • 客户端(Client):服务调用方。
  • 客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端。
  • 服务端存根(Server Stub):接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。
  • 服务端(Server):服务的真正提供者。
  • Network Service:底层传输,可以是 TCP 或 HTTP。

百度百科上查找的资料
什么是代理和存根

打个比方,你到自动取款机上去取款;你就是客户,取款机就是你的代理;你不会在乎钱具体放在那里,你只想看到足够或更多的钱从出口出来(这就是com的透明性)。你同银行之间的操作完全是取款机代理实现。 你的取款请求通过取款机,传到另一头,银行的服务器,他也没有必要知道你在哪儿取钱,他所关心的是你的身份,和你取款多少。当他确认你的权限,就进行相应的操作,返回操作结果给取款机,取款机根据服务器返回结果,从保险柜里取出相应数量的钱给你。你取出卡后,操作完成。 取款机不是直接同服务器连接的,他们之间还有一个“存根”,取款机与存根通信,服务器与存根通信。从某种意义上说存根就是服务器的代理。

按照我自己通俗的理解,客户端与stub存根进行数据交互等价与直接与服务端通讯,对于接口调用相当于本地调用。

无非就是客户端将要调用服务端的接口和参数传递给client stub,client stub将上述信息序列化为能在网络上传输的数据,这一点应用完全没必要清楚细节。传输到server stub再将数据反序列化成实际调用接口的信息。server stub根据传输过来的信息调用服务端提供的服务接口,再将返回值通过stub传输给客户端。
简单归纳与普通的C/S通讯,仅仅多了一个中间件stub。

揭开gRPC神秘面纱_第3张图片

RPC 的核心功能主要由 5 个模块组成,如果想要自己实现一个 RPC,最简单的方式要实现三个技术点,分别是:

  • 服务寻址
  • 数据流的序列化和反序列化
  • 网络传输

在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。
这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。
只有二进制数据才能在网络中传输,序列化和反序列化的定义是:

  • 将对象转换成二进制流的过程叫做序列化
  • 将二进制流转换成对象的过程叫做反序列化

这个过程叫序列化和反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。

通俗的理解:
现在我们都会在淘宝上买桌子,桌子这种很不规则不东西,该怎么从一个城市运输到另一个城市,这时候一般都会把它拆掉成板子,再装到箱子里面,就可以快递寄出去了,这个过程就类似我们的序列化的过程(把数据转化为可以存储或者传输的形式)。当买家收到货后,就需要自己把这些板子组装成桌子的样子,这个过程就像反序列 的过程(转化成当初的数据对象)。

详细内容参考该文章。
花了一个星期,我终于把RPC框架整明白了! - 51CTO.COM

二、什么是gRPC

在gRPC中,客户端应用程序可以直接在其他计算机上的服务器应用程序上调用方法,就好像它是本地对象一样,从而使您更轻松地创建分布式应用程序和服务。与许多RPC系统一样,gRPC围绕定义服​​务的思想,指定可通过其参数和返回类型远程调用的方法。在服务器端,服务器实现此接口并运行gRPC服务器以处理客户端调用。在客户端,客户端具有一个存根(在某些语言中仅称为客户端),提供与服务器相同的方法。
存根通俗理解为“开发票一式两份”,作为服务端方法的镜像或者说映射。简单说客户端执行存根的方法,等价于调用服务端的方法。

揭开gRPC神秘面纱_第4张图片
从Google内部的服务器到您自己的台式机,gRPC客户端和服务器可以在各种环境中运行并相互通信,并且可以使用gRPC支持的任何语言编写。因此,例如,您可以使用Go,Python或Ruby中的客户端轻松地用Java创建gRPC服务器。此外,最新的Google API的接口将具有gRPC版本,可让您轻松地在应用程序中构建Google功能。基于HTTP2.0的传输协议,高效多语言支持。

(简单的理解就是远程客户端可以直接调用服务端的接口。)

三、什么是Protobuf

1.概要

protobuf(Google Protocol Buffers)是Google提供一个具有高效的协议数据交换格式工具库。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
grpc作为rpc的一种,肯定有服务间的网络调用,调用就一定会有数据的传输,而这个数据的传输就用的是protobuf去序列化和反序列化的,一个请求的内容在调用方序列化通过网络传送到服务方,服务方就能用protobuf反序列化出来。
优势:

  • 平台无关,语言无关,可扩展;
  • 提供了友好的动态库,使用简单;
  • 解析速度快,比对应的XML快约20-100倍;
  • 序列化数据非常简洁、紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10。

揭开gRPC神秘面纱_第5张图片
干货|了解Google远程过程调用(gRPC)技术

2.举例说明

helloworld.proto

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

// The greeting service definition.
service Greeter {
  // 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;
}

说明:
(1)syntax = “proto3”
表示使用的protobuf编译器版本为v3

(2)option中的几个选项说明
选项不对message的定义产生任何的效果,只会在一些特定的场景中起到作用,下面是一部分例子,完整的选项列表可以前往google/protobuf/descriptor.proto查看
一些选项是文件级别的,意味着它可以作用于最外范围,不包含在任何消息内部、enum或服务定义中。一些选项是消息级别的,意味着它可以用在消息定义的内部。当然有些选项可以作用在域、enum类型、enum值、服务类型及服务方法中。到目前为止,并没有一种有效的选项能作用于所有的类型。

如下就是一些常用的选择:
java_multiple_files
option java_multiple_files = true; 该选项为true时,生成的Java类将是包级别的,否则会在一个包装类中。

java_package :
这个选项表明生成java类所在的包。如果在.proto文件中没有明确的声明java_package,就采用默认的包名。当然了,默认方式产生的 java包名并不是最好的方式,按照应用名称倒序方式进行排序的。如果不需要产生java代码,则该选项将不起任何作用。如:
option java_package = “com.example.foo”;

java_outer_classname ):
该选项表明想要生成Java类的名称。如果在.proto文件中没有明确的java_outer_classname定义,生成的class名称将会根据.proto文件的名称采用驼峰式的命名方式进行生成。如(foo_bar.proto生成的java类名为FooBar.java),如果不生成java代码,则该选项不起任何作用。如:
option java_outer_classname = “Ponycopter”;

objc_class_prefix:
设置Objective-C类的前缀,添加到所有Objective-C从此.proto文件产生的类和枚举类型。没有默认值,所使用的前缀应该是苹果推荐的3-5个大写字符,注意2个字节的前缀是苹果所保留的。
其他字段详细参考博客 。

(3)package helloworld
声明了一个包名,用来防止不同的消息类型命名冲突,类似于 namespace命名空间。

(4)定义service
定义一个SayHello的rpc方法,helloworld定义的是一个简单的RPC,客户端使用存根发送请求到服务器并等待响应返回,就像平常的函数调用一样。

rpc SayHello (HelloRequest) returns (HelloReply) {}

gRPC 允许你定义四类服务方法:
单项 RPC,即客户端发送一个请求给服务端,从服务端获取一个应答,就像一次普通的函数调用。

rpc SayHello(HelloRequest) returns (HelloResponse){
}

服务端流式 RPC,即客户端发送一个请求给服务端,可获取一个数据流用来读取一系列消息。客户端从返回的数据流里一直读取直到没有更多消息为止。

rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
}

客户端流式 RPC,即客户端用提供的一个数据流写入并发送一系列消息给服务端。一旦客户端完成消息写入,就等待服务端读取这些消息并返回应答。

rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}

双向流式 RPC,即两边都可以分别通过一个读写数据流来发送一系列消息。这两个数据流操作是相互独立的,所以客户端和服务端能按其希望的任意顺序读写,例如:服务端可以在写应答前等待所有的客户端消息,或者它可以先读一个消息再写一个消息,或者是读写相结合的其他方式。每个数据流里消息的顺序会被保持。

rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
}

深入理解流,什么是流?

grpc将根据proto双方协商好的数据类型,序列化为二进制进行传输。之后再反序列化恢复数据原样。

(5)定义消息类型
指定字段类型
HelloRequest 消息包含一个string类型。
分配标示号
在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。

注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。

切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。

最小的标识号可以从1开始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999]( (从FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber))的标识号, Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警。同样你也不能使用早期保留的标识号。

默认值
当一个消息被解析的时候,如果被编码的信息不包含一个特定的singular元素,被解析的对象锁对应的域被设置位一个默认值,对于不同类型指定如下:
对于strings,默认是一个空string
对于bytes,默认是一个空的bytes
对于bools,默认是false
对于数值类型,默认是0
对于枚举,默认是第一个定义的枚举值,必须为0;

Protobuf3语法详解 - 望星辰大海 - 博客园

3.protoc是什么?

protoc是proto文件的编译器,目前可以将proto文件编译成C++、Java、Python三种代码文件,编译格式如下:

protoc -I=$SRC_DIR --cpp_out=$DST_DIR /path/to/file.proto

上面的命令会生成xxx.pb.h 和 xxx.pb.cc两个C++文件。

个人测试
测试命令

protoc -I=/home/workspace/test/grpc_test --cpp_out=/home/workspace/test/grpc_test/out /home/workspace/test/grpc_test/helloworld.proto


proto文件的作用?
定义我们程序中需要处理的结构化数据。message为结构化数据。简单理解为结构体,里面记录要存储的数据类型。

编译 .proto 文件的作用是什么?
写好 proto 文件之后就可以用 Protobuf 编译器将该文件编译成目标语言了。
helloworld.pb.h , 定义了 C++ 类的头文件
helloworld.pb.cc , C++ 类的实现文件
在生成的头文件中,定义了一个 C++ 类 helloworld,

四、参考资料

序列化和反序列化理解 - 简

花了一个星期,我终于把RPC框架整明白了! - 51CTO.COM

gRPC –指南

gRPC 官方文档中文版_V1.0

Protobuf学习 - 入门 - Aut - 博客园

干货|了解Google远程过程调用(gRPC)技术,这一篇就够了

你可能感兴趣的:(gRPC,rpc,grpc,存根)