概述

gRPC 一开始由 google 开发,是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。

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

go grpc 客户端最佳 grpc服务器_c++

特性

基于HTTP/2

HTTP/2 提供了连接多路复用、双向流、服务器推送、请求优先级、首部压缩等机制。可以节省带宽、降低TCP链接次数、节省CPU,帮助移动设备延长电池寿命等。gRPC 的协议设计上使用了HTTP2 现有的语义,请求和响应的数据使用HTTP Body 发送,其他的控制信息则用Header 表示。

IDL使用ProtoBuf

gRPC使用ProtoBuf来定义服务,ProtoBuf是由Google开发的一种数据序列化协议(类似于XML、JSON、hessian)。ProtoBuf能够将数据进行序列化,并广泛应用在数据存储、通信协议等方面。压缩和传输效率高,语法简单,表达力强。

多语言支持(C, C++, Python, PHP, Nodejs, C#, Objective-C、Golang、Java)

gRPC支持多种语言,并能够基于语言自动生成客户端和服务端功能库。目前已提供了C版本grpc、Java版本grpc-java 和 Go版本grpc-go,其它语言的版本正在积极开发中,其中,grpc支持C、C++、Node.js、Python、Ruby、Objective-C、PHP和C#等语言,grpc-java已经支持Android开发。
gRPC已经应用在Google的云服务和对外提供的API中,其主要应用场景如下:

  • 低延迟、高扩展性、分布式的系统
  • 同云服务器进行通信的移动应用客户端
  • 设计语言独立、高效、精确的新协议
  • 便于各方面扩展的分层设计,如认证、负载均衡、日志记录、监控等
HTTP2.0 特性

HTTP/2,也就是超文本传输协议第2版,不论是1还是2,HTTP的基本语义是不变的,比如方法语义(GET/PUST/PUT/DELETE),状态码(200/404/500等),Range Request,Cacheing,Authentication、URL路径, 不同的主要是下面几点:

多路复用 (Multiplexing)

在 HTTP/1.1 协议中 「浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞」。

HTTP/2 的多路复用(Multiplexing) 则允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。

因此 HTTP/2 可以很容易的去实现多流并行而不用依赖建立多个 TCP 连接,HTTP/2 把 HTTP 协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息。并行地在同一个 TCP 连接上双向交换消息。

go grpc 客户端最佳 grpc服务器_go grpc 客户端最佳_02


二进制帧HTTP/2 传输的数据是二进制的。相比 HTTP/1.1 的纯文本数据,二进制数据一个显而易见的好处是:更小的传输体积。这就意味着更低的负载。二进制的帧也更易于解析而且不易出错,纯文本帧在解析的时候还要考虑处理空格、大小写、空行和换行等问题,而二进制帧就不存在这个问题。

go grpc 客户端最佳 grpc服务器_客户端_03


首部压缩(Header Compression)

HTTP是无状态协议。简而言之,这意味着每个请求必须要携带服务器需要的所有细节,而不是让服务器保存住之前请求的元数据。因为http2没有改变这个范式,所以它也需要这样(携带所有细节),因此 HTTP 请求的头部需要包含用于标识身份的数据比如 cookies,而这些数据的量也在随着时间增长。每一个请求的头部都包含这些大量的重复数据,无疑是一种很大的负担。对请求头部进行压缩,将会大大减轻这种负担,尤其对移动端来说,性能提高非常明显。

HTTP/2 使用的压缩方式是 HPACK。 http://http2.github.io/http2-spec/compression.html

HTTP2.0在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;通信期间几乎不会改变的通用键-值对(用户代理、可接受的媒体类型,等等)只需发送一次。

go grpc 客户端最佳 grpc服务器_客户端_04


事实上,如果请求中不包含首部(例如对同一资源的轮询请求),那么首部开销就是零字节。此时所有首部都自动使用之前请求发送的首部。

如果首部发生变化了,那么只需要发送变化了数据在Headers帧里面,新增或修改的首部帧会被追加到“首部表”。首部表在 HTTP2.0的连接存续期内始终存在,由客户端和服务器共同渐进地更新。

服务端推送(Server Push)

HTTP/2 的服务器推送所作的工作就是,服务器在收到客户端对某个资源的请求时,会判断客户端十有八九还要请求其他的什么资源,然后一同把这些资源都发送给客户端,即便客户端还没有明确表示它需要这些资源。

客户端可以选择把额外的资源放入缓存中(所以这个特点也叫 Cache push),也可以选择发送一个 RST_STREAM frame 拒绝任何它不想要的资源。

go grpc 客户端最佳 grpc服务器_客户端_05


主动重置链接

Length的HTTP消息被送出之后,我们就很难中断它了。当然,通常我们可以断开整个TCP链接(但也不总是可以这样),但这样导致的代价就是需要重新通过三次握手建立一个新的TCP连接。

HTTP/2 引入了一个 RST_STREAM frame 来让客户端在已有的连接中发送重置请求,从而中断或者放弃响应。当浏览器进行页面跳转或者用户取消下载时,它可以防止建立新连接,避免浪费所有带宽。

与其他rpc比较

与thrift,dubbo,motan等比较

go grpc 客户端最佳 grpc服务器_HTTP_06


go grpc 客户端最佳 grpc服务器_c++_07


grpc vs thrift:

go grpc 客户端最佳 grpc服务器_客户端_08

使用gRPC的公司或项目:

Google
Mochi中国
阿里OTS
腾讯部分部门
Tensorflow项目中使用了grpc
CoreOS — Production API for etcd v3 is entirely gRPC. etcd v3的接口全部使用grpc
Square — replacement for all of their internal RPC. one of the very first adopters and contributors to gRPC.
ngrok — all 20+ internal services communicate via gRPC 一个内网转发产品
Netflix
Yik Yak
VSCO
Cockroach

使用Thrift的公司或项目:

Facebook
雪球
饿了么
今日头条
evernote
友盟
小米
美团
Quora
Twitter
Pinterest
Foursquare
Maxeler Technologies

gRPC优缺点:

优点:
protobuf二进制消息,性能好/效率高(空间和时间效率都很不错)
proto文件生成目标代码,简单易用
序列化反序列化直接对应程序中的数据类,不需要解析后在进行映射(XML,JSON都是这种方式)
支持向前兼容(新加字段采用默认值)和向后兼容(忽略新加字段),简化升级
支持多种语言(可以把proto文件看做IDL文件)
Netty等一些框架集成

缺点:
1)GRPC尚未提供连接池,需要自行实现
2)尚未提供“服务发现”、“负载均衡”机制
3)因为基于HTTP2,绝大部多数HTTP Server、Nginx都尚不支持,即Nginx不能将GRPC请求作为HTTP请求来负载均衡,而是作为普通的TCP请求。(nginx1.9版本已支持)
4) Protobuf二进制可读性差(貌似提供了Text_Fromat功能)
默认不具备动态特性(可以通过动态定义生成消息类型或者动态编译支持)

grpc坑:
来自https://news.ycombinator.com/item?id=12345223的网友:
http2只允许单个链接传输10亿流数据。原因在于:
htt2使用31位整形标示流,服务端使用奇数,客户端使用偶数,所以总共10亿可用。

HTTP/2.0 uses an unsigned 31-bit integer to identity individual streams over a connection.
Server-initiated streams must use even identifiers. 
Client-initiated streams must use odd identifiers.

解决思路:超过一定数量的流,需要重启链接。

gRPC通信方式

gRPC有四种通信方式:
1、 Simple RPC 简单rpc
这就是一般的rpc调用,一个请求对象对应一个返回对象
proto语法:

rpc simpleHello(Person) returns (Result) {}

2、 Server-side streaming RPC 服务端流式rpc
一个请求对象,服务端可以传回多个结果对象
proto语法

rpc serverStreamHello(Person) returns (stream Result) {}

3、 Client-side streaming RPC 客户端流式rpc
客户端传入多个请求对象,服务端返回一个响应结果
proto语法

rpc clientStreamHello(stream Person) returns (Result) {}

4、 Bidirectional streaming RPC 双向流式rpc
结合客户端流式rpc和服务端流式rpc,可以传入多个对象,返回多个响应对象
proto语法

rpc biStreamHello(stream Person) returns (stream Result) {}

**

gRPC服务发现与服务治理的方案

**
目前gRPC主流分布式方案有这么几种: etcd, zookeeper, consul.


1、集中式LB(Proxy Model)

go grpc 客户端最佳 grpc服务器_go grpc 客户端最佳_09


在服务消费者和服务提供者之间有一个独立的LB,通常是专门的硬件设备如 F5,或者基于软件如 LVS,HAproxy等实现。LB上有所有服务的地址映射表,通常由运维配置注册,当服务消费方调用某个目标服务时,它向LB发起请求,由LB以某种策略,比如轮询(Round-Robin)做负载均衡后将请求转发到目标服务。LB一般具备健康检查能力,能自动摘除不健康的服务实例。 该方案主要问题:

1、 单点问题,所有服务调用流量都经过LB,当服务数量和调用量大的时候,LB容易成为瓶颈,且一旦LB发生故障影响整个系统;
2、服务消费方、提供方之间增加了一级,有一定性能开销。


2、进程内LB(Balancing-aware Client)

go grpc 客户端最佳 grpc服务器_go grpc 客户端最佳_10


针对第一个方案的不足,此方案将LB的功能集成到服务消费方进程里,也被称为软负载或者客户端负载方案。服务提供方启动时,首先将服务地址注册到服务注册表,同时定期报心跳到服务注册表以表明服务的存活状态,相当于健康检查,服务消费方要访问某个服务时,它通过内置的LB组件向服务注册表查询,同时缓存并定期刷新目标服务地址列表,然后以某种负载均衡策略选择一个目标服务地址,最后向目标服务发起请求。LB和服务发现能力被分散到每一个服务消费者的进程内部,同时服务消费方和服务提供方之间是直接调用,没有额外开销,性能比较好。该方案主要问题:

1、开发成本,该方案将服务调用方集成到客户端的进程里头,如果有多种不同的语言栈,就要配合开发多种不同的客户端,有一定的研发和维护成本;
2、另外生产环境中,后续如果要对客户库进行升级,势必要求服务调用方修改代码并重新发布,升级较复杂。


3、独立 LB 进程(External Load Balancing Service)

go grpc 客户端最佳 grpc服务器_HTTP_11


该方案是针对第二种方案的不足而提出的一种折中方案,原理和第二种方案基本类似。

不同之处是将LB和服务发现功能从进程内移出来,变成主机上的一个独立进程。主机上的一个或者多个服务要访问目标服务时,他们都通过同一主机上的独立LB进程做服务发现和负载均衡。该方案也是一种分布式方案没有单点问题,一个LB进程挂了只影响该主机上的服务调用方,服务调用方和LB之间是进程内调用性能好,同时该方案还简化了服务调用方,不需要为不同语言开发客户库,LB的升级不需要服务调用方改代码。

该方案主要问题:部署较复杂,环节多,出错调试排查问题不方便。

服务发现负载均衡实现

gRPC开源组件官方并未直接提供服务注册与发现的功能实现,但其设计文档已提供实现的思路,并在不同语言的gRPC代码API中已提供了命名解析和负载均衡接口供扩展。

go grpc 客户端最佳 grpc服务器_客户端_12

其基本实现原理:

1、服务启动后gRPC客户端向命名服务器发出名称解析请求,名称将解析为一个或多个IP地址,每个IP地址标示它是服务器地址还是负载均衡器地址,以及标示要使用那个客户端负载均衡策略或服务配置。
2、客户端实例化负载均衡策略,如果解析返回的地址是负载均衡器地址,则客户端将使用grpclb策略,否则客户端使用服务配置请求的负载均衡策略。
3、负载均衡策略为每个服务器地址创建一个子通道(channel)。
4、当有rpc请求时,负载均衡策略决定那个子通道即grpc服务器将接收请求,当可用服务器为空时客户端的请求将被阻塞。