gRPC框架学习 (一)

1.gRPC是什么?

gRPC 是一个高性能、开源和通用的远程过程调用(RPC)框架,面向移动和 HTTP/2 设计。

目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.

gRPC代码仓库地址:https://github.com/grpc/grpc
gRPC官方文档中文版:http://doc.oschina.net/grpc
gRPC底层原理:https://www.jianshu.com/p/5c3489d0da46

在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。

在这里插入图片描述

RPC与HTTP的区别:

  1. HTTP 是面向浏览器设计的应用层协议,操作的核心在资源。
  2. RPC 是为了在像在本地调用一个函数那样调用远程的代码而设计的,所以更关注减少本地调用和远程调用的差异,像 SOAP(简单对象访问协议) 这种东西是可以把对象当参数传的。
  3. RPC更偏向内部调用,REST更偏向外部调用。

HTTP和RPC的异同:

  • 传输协议
    RPC,可以基于TCP协议,也可以基于HTTP协议
    HTTP,基于HTTP协议
  • 传输效率
    RPC,使用自定义的TCP协议,可以让请求报文体积更小,或者使用HTTP2协议,也可以很好的减少报文的体积,提高传输效率
    HTTP,如果是基于HTTP1.1的协议,请求中会包含很多无用的内容,如果是基于HTTP2.0,那么简单的封装一下是可以作为一个RPC来使用的,这时标准RPC框架更多的是服务治理
  • 性能消耗,主要在于序列化和反序列化的耗时
    RPC,可以基于thrift实现高效的二进制传输
    HTTP,大部分是通过json来实现的,字节大小和序列化耗时都比thrift要更消耗性能
  • 负载均衡
    RPC,基本都自带了负载均衡策略
    HTTP,需要配置Nginx,HAProxy来实现
  • 服务治理(下游服务新增,重启,下线时如何不影响上游调用者)
    RPC,能做到自动通知,不影响上游
    HTTP,需要事先通知,修改Nginx/HAProxy配置

总结:
RPC主要用于公司内部的服务调用,性能消耗低,传输效率高,服务治理方便。HTTP主要用于对外的异构环境,浏览器接口调用,APP接口调用,第三方接口调用等。

https://blog.csdn.net/qq_34147021/article/details/85240628


2.gRPC有什么优势?

gRPC 基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特。这些特性使得其在移动设备上表现更好,更省电和节省空间占用。

常用RPC框架:

  • Netty - Netty框架不局限于RPC,更多的是作为一种网络协议的实现框架,比如HTTP,由于RPC需要高效的网络通信,就可能选择以Netty作为基础。
  • brpc是一个基于protobuf接口的RPC框架,在百度内部称为“baidu-rpc”,它囊括了百度内部所有RPC协议,并支持多种第三方协议,从目前的性能测试数据来看,brpc的性能领跑于其他同类RPC产品。
  • Dubbo是Alibaba开发的一个RPC框架,远程接口基于Java Interface, 依托于Spring框架。
  • gRPC的Java实现的底层网络库是基于Netty开发而来,其Go实现是基于net库。
  • Thrift是Apache的一个项目(http://thrift.apache.org),前身是Facebook开发的一个RPC框架,采用thrift作为IDL (Interface description language)。
  • jsonrpc是一种序列化协议。JSON 是 JS 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。非常简单,方便,速度慢。

总的来说,Python rpc框架选择较少,thrift性能最好,grpc性能比thrift稍差,原因是多了http2,而thrift直接基于tcp,但grpc序列化方案更通用(protobuf)优秀,文档较好;jsonrpc 本身基于http/1进行通信,速度最慢,相对于之前速度无提升,只是接口和数据格式更为统一。

什么时候应该选择gRPC而不是Thrift?
  需要良好的文档、示例
  喜欢、习惯HTTP/2、ProtoBuf
  对网络传输带宽敏感
  
什么时候应该选择Thrift而不是gRPC?
  需要在非常多的语言间进行数据交换
  对CPU敏感
  协议层、传输层有多种控制要求
  需要稳定的版本
  不需要良好的文档和示例

gRPC不足:
1)GRPC尚未提供连接池
2)尚未提供“服务发现”、“负载均衡”机制
3)因为基于HTTP2,绝大部多数HTTP Server、Nginx都尚不支持,即Nginx不能将GRPC请求作为HTTP请求来负载均衡,而是作为普通的TCP请求。

https://www.jianshu.com/p/b0343bfd216e

拓展:
Protobuf:
Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。

同 XML 相比, Protobuf 的主要优点在于性能高。它以高效的二进制方式存储,比 XML 小 3 到 10 倍,快 20 到 100 倍。

分配标识号

message SearchRequest {
    
    
  required string query = 1;
  optional int32 page_number = 2;
  optional int32 result_per_page = 3;
}

正如上述文件格式,在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。

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

https://colobu.com/2015/01/07/Protobuf-language-guide/

《InfoQ访谈:百度正式开始其PRC框架brpc,性能远超gRPC》


3.gRPC的功能特性?

(1) 服务定义

gRPC 基于如下思想:定义一个服务, 指定其可以被远程调用的方法及其参数和返回类型。gRPC 默认使用 protocol buffers 作为接口定义语言,来描述服务接口和有效载荷消息结构。如果有需要的话,可以使用其他替代方案。

service HelloService {
    
    
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

message HelloRequest {
    
    
  required string greeting = 1;
}

message HelloResponse {
    
    
  required string reply = 1;
}

gRPC 允许定义四类服务方法:

  1. 单项 RPC,即客户端发送一个请求给服务端,从服务端获取一个应答,就像一次普通的函数调用。
rpc SayHello(HelloRequest) returns (HelloResponse){
    
    
}
  1. 服务端流式 RPC,即客户端发送一个请求给服务端,可获取一个数据流用来读取一系列消息。客户端从返回的数据流里一直读取直到没有更多消息为止。
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
    
    
}
  1. 客户端流式 RPC,即客户端用提供的一个数据流写入并发送一系列消息给服务端。一旦客户端完成消息写入,就等待服务端读取这些消息并返回应答。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
    
    
}
  1. 双向流式 RPC,即两边都可以分别通过一个读写数据流来发送一系列消息。这两个数据流操作是相互独立的,所以客户端和服务端能按其希望的任意顺序读写,例如:服务端可以在写应答前等待所有的客户端消息,或者它可以先读一个消息再写一个消息,或者是读写相结合的其他方式。每个数据流里消息的顺序会被保持。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
    
    
}

(2) 远程调用执行过程

  1. 单项 RPC
    a. 首先我们来了解一下最简单的 RPC 形式:客户端发出单个请求,获得单个响应。
    b. 一旦客户端通过桩调用一个方法,服务端会得到相关通知 ,通知包括客户端的元数据,方法名,允许的响应期限(如果可以的话)
    c. 服务端既可以在任何响应之前直接发送回初始的元数据,也可以等待客户端的请求信息,到底哪个先发生,取决于具体的应用。
    d. 一旦服务端获得客户端的请求信息,就会做所需的任何工作来创建或组装对应的响应。如果成功的话,这个响应会和包含状态码以及可选的状态信息等状态明细及可选的追踪信息返回给客户端 。
    e. 假如状态是 OK 的话,客户端会得到应答,这将结束客户端的调用。

  2. 服务端流式 RPC
    服务端流式 RPC 除了在得到客户端请求信息后发送回一个应答流之外,在发送完所有应答后,服务端的状态详情(状态码和可选的状态信息)和可选的跟踪元数据被发送回客户端,以此来完成服务端的工作。客户端在接收到所有服务端的应答后也完成了工作。

  3. 客户端流式 RPC
    客户端流式 RPC中,客户端通过发送一个请求流给服务端,取代了原先发送的单个请求。服务端通常(但并不必须)会在接收到客户端所有的请求后发送回一个应答,其中附带有它的状态详情和可选的跟踪数据。

  4. 双向流式 RPC
    a. 双向流式 RPC ,调用由客户端调用方法来初始化,而服务端则接收到客户端的元数据,方法名和截止时间。服务端可以选择发送回它的初始元数据或等待客户端发送请求。
    b. 下一步怎样发展取决于应用,因为客户端和服务端能在任意顺序上读写 - 这些流的操作是完全独立的。例如服务端可以一直等直到它接收到所有客户端的消息才写应答,或者服务端和客户端可以像"乒乓球"一样:服务端后得到一个请求就回送一个应答,接着客户端根据应答来发送另一个请求,以此类推。

元数据(Metadata),又称中介数据、中继数据,为描述数据的数据(data about data),主要是描述数据属性(property)的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能。

(3) RPC 生命周期

  1. 截止时间
    gRPC 允许客户端在调用一个远程方法前指定一个最后期限值。这个值指定了在客户端可以等待服务端多长时间来应答,超过这个时间值 RPC 将结束并返回DEADLINE_EXCEEDED错误。在服务端可以查询这个期限值来看是否一个特定的方法已经过期,或者还剩多长时间来完成这个方法。
    各语言来指定一个截止时间的方式是不同的 - 比如在 Python 里一个截止时间值总是必须的,但并不是所有语言都有一个默认的截止时间。

  2. RPC 终止
    在 gRPC 里,客户端和服务端对调用成功的判断是独立的、本地的,他们的结论可能不一致。这意味着,比如你有一个 RPC 在服务端成功结束(“我已经返回了所有应答!”),到那时在客户端可能是失败的(“应答在最后期限后才来到!”)。也可能在客户端把所有请求发送完前,服务端却判断调用已经完成了。

  3. 取消 RPC
    无论客户端还是服务端均可以在任何时间取消一个 RPC 。一个取消会立即终止 RPC 这样可以避免更多操作被执行。它不是一个"撤销", 在取消前已经完成的不会被回滚。当然,通过同步调用的 RPC 不能被取消,因为直到 RPC 结束前,程序控制权还没有交还给应用。

(4) 频道channel

在创建客户端存根时,一个 gRPC 频道提供一个特定主机和端口服务端的连接。客户端可以通过指定频道参数来修改 gRPC 的默认行为,比如打开关闭消息压缩。一个频道具有状态,包含已连接和空闲 。
gRPC 如何处理关闭频道是语言相关的。有些语言可允许询问频道状态。

http://doc.oschina.net/grpc?t=58010

猜你喜欢

转载自blog.csdn.net/weixin_43202635/article/details/115219176
今日推荐