剖析gRPC演讲稿
各位好,今天的主题是剖析gRPC
, 我们在实际工作中大量的应用gRPC
做服务之间的调用。经常用写一些proto
文件,用protoc
把我们的proto
文件生成相应语言的代码,但大多数人很少关注protoc
生成的相应语言代码里都有什么内容,由于他的简单易用,我们基本上读一下文档,写一个小例子就能快速入门使用他。但一问你他的原理是什么,为什么生成的相关语言的代码里会有一些我们用不到的字段,比如生成的 file_protos_xxxxxxx_proto_rawDesc
是一个[]byte
,他是做什么的?还有客户端在进行调用的时候是如何定位到具体方法的,我没有没有想过?现在大部分服务是跑在k8s里服务的发现和治理我们都不用关心,如果不用k8s
我们怎么做服务的注册和发现,gRPC
为我们提供了什么方案让我们集成服务的注册和发现呢?如果你心中有疑问,那您今天就来着了,我今天主要讲的就是这些基础的内容,其中也会有一些代码例子,分析实际具体的代码会让大家能更深入的了解gRPC
的原理
分享大概1小时左右,好下面就开始进入主题。
什么是Rpc & gRPC
在分布式计算,远程过程调用(英语:Remote Procedure Call
,缩写为 RPC
)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程(无需关注细节)。RPC
是一种服务器-客户端(Client/Server
)模式,经典实现是一个通过发送请求-接受回应进行信息交互的系统
gRPC
是一种现代化开源的高性能RPC框架,能够运行于任意环境之中。最初由谷歌进行开发。它使用HTTP/2
作为传输层。
为什么要用gRPC
使用gRPC
,我们可以一次性的在一个.proto
文件中定义服务并使用任何支持它的语言去实现客户端和服务端,反过来,它们可以应用在各种场景中,gRPC
帮你解决了不同语言及环境间通信的复杂性。使用protocol buffers
还能获得其他好处,包括高效的序列号,简单的IDL
以及容易进行接口更新。使用gRPC
能让我们更容易编写跨语言的分布式代码。
gRPC 的核心
gRPC
的两个核心一个是http2
一个是protobuf
。
http2
http2
做为gRPC
的核心之一,他有哪些好处呢。
* 多路复用(Multiplexing),允许同一个连接发起多重请求-响应。一个request
对应一个stream
并分配一个id,同时可以有多个stream
,每个stream
的frame
可以随机的混杂在一起,接收方可以根据 stream id
将frame
归属到不同的request
里
* Header
压缩,http1.x
都是用的明文,http2.0
使用encoder
来减少需要传输的header
大小,通讯双方各自cache
一份header fields
表,既避免了重复header
的传输,又减小了需要传输的大小。高效的压缩算法可以很大的压缩header
,减少发送包的数量从而降低延迟。
* 流量控制,http2.0
的flow control
是类似receive window
的做法,数据的接收方通过告知对方自己的flow window
大小表明自己还能接收多少数据。只有Data
类型的frame
才有flow control
的功能。其他特点,这里就不说了,有时候大家可以查一下相关资料
当然还有很多其他的优点,这里就不一一说了。
我们在做长链接服务的时候一定针会做的一件事就是确定一个数据包的格式和数据长度,如果数据包太大还要处理大包拆分包的逻辑,这些http2
都有并且他还有其他的好处。
Protocol Buffers
protobuf
,json
不香吗?一般得到的回答都是protobuf
比json
快,但是快在了哪呢?等会儿我会详细说明。
Protocol Buffers
是一种与语言、平台无关,可扩展的序列化结构化数据的方法,常用于通信协议,数据存储等等。相较于 JSON
、XML
,它更小、更快、更简单,因此也更受开发人员的青眯
ppt
这有语法
protobuf
是grpc
默认的传输协议,当然你也可以自定义比如使用json
等。大多数人提到为什么要使用
定义完 proto
文件后,生成相应语言的代码
protoc --proto_path=. --go_out=plugins=grpc,paths=source_relative:. xxxx.proto
--proto_path
或者 -I
参数用以指定所编译源码(包括直接编译的和被导入的 proto 文件)的搜索路径
--go_out
参数之间用逗号隔开,最后用冒号来指定代码目录架构的生成位置, eg:--go_out=plugins=grpc,paths=import:.
。注意一下 paths
参数,他有两个选项,import
和 source_relative
。默认为 import
,代表按照生成的 go
代码的包的全路径去创建目录层级,source_relative
代表按照 proto
源文件的目录层级去创建 go
代码的目录层级,如果目录已存在则不用创建。
看一下ppt
里Student
结构体,转换成JSON
和样子,我们一眼就能看明白,JSON
是给人读的。
转换成protobuf
数据格式是一串二进制,我们是不能一下子知道这一串二进制是什么意思。
再看一下protobuf
支持的几种数据类型(wire types),protobuf
是语言无关的,所有支持protobuf
的库序列化时最终都会转换成这几种类型,反序列化时会转换成相应语言的类型。
字段的 Index和类型
Protobuf
把一个字段的 index 和类型放在了一起
(field_number << 3) | wire_type
eg: 0 000 1000
首位为标识位,index: 1
后三位为wire_type:0eg: 10010 index: 2 wire_type: 2
Varint 内存存储方式
Varint
数据类型,最高位(msb
)标志位
* 1说明后面还有byte
* 0说明后面没有byte
7个Bit位存储数值
看ppt
这里的1
和300
在内存的存储方式
Length-delimited
字符串和数组类型,就是length-delimited
我们看一下字符串内存的展示方式,protobuf
一个汉字占3
个byte
“孙悟空”内存的数据
代码语言:txt复制11100101 10101101 10011001 11100110 10000010 10011111 11100111 10101001 10111010
“孙悟空”前面的一个byte
:1001
就是字符串的长度。
gRPC的调用方式
gRPC
目前有四种
一. Unary RPC
:一元 RPC,发送 RPC 请求,等待同步响应,得到回调后返回响应结果
二. Server-side streaming RPC
:服务端流式 RPC
三. Client-side streaming RPC
:客户端流式 RPC
四. Bidirectional streaming RPC
:双向流式 RPC
需要注意一点:只能是客户端发起请求,目前grpc 没有推送功能。
看ppt
里每一种调用方式的图片,就能很方便的理解。
gRPC 方法调用流程
我们在编写客户端代码时,能非常方便的调用服务端的代码
代码语言:txt复制 rev, err := client.StudentByID(ctx, req)
在通信过程中,是如何正确的访问服务端的方法的?
protoc
为我们做的工作不只是把 message 转换成相应语言的数据结构,而且把路由信息也生成了,直接看代码来讲解。
scheme: "http"
method: "/api.StudentSrv/StudentByID"
host: "localhost:10001"
Intercepto
grpc
服务端提供了interceptor
拦截器功能,类似gin
里的middleware
,可以在服务端接收到请求时,对请求中的数据做一些处理后再处理具体业务。
看ppt
用的就是https://github.com/grpc-ecosystem/go-grpc-middleware
库里的方法。
gRPC 服务发现&负载均衡
常用的就两种方式
* Proxy Model
,可以使用nginx
或者traefik
等代理软件来实现。
* Client Model
,客户端自己实现
ppt
里的图,是用nginx
来做的。
最常用就是客户端自己实现,用zk
或者etcd
等来实现。原理很简单,就是服务端注册,客户端resolver
和watch
更新地址列表等。今天的例子是用etcd
实现的.
etcd 简介
etcd
是一个高可用的键值分布式存储系统,主要用于共享配置和服务发现
服务注册:主要思路是创建一个lease
租约,put
一个前缀的Key
(方便服务发现时根据前缀取key
对应的值);然后通过keepAlive
续约,并监听keepAlive
通道保持在线,如果不监听,或者程序崩溃,etcd
会删除这个租约上的key
。
服务发现:通过前缀取出key
对应的values
;然后启动一个监听服务监听key
的变化.
etcd
已经为我们做了一些工作,这是官网的说明https://etcd.io/docs/v3.2.17/dev-guide/grpc_naming/
,我们要做的主要是,实现Resolver
和Builder
两个接口,看一下我们的例子。
服务端,我们没有指定具体的端口,是让系统随机给分的,这样我们就可以把启动的实例的ip port
保存到etcd
,服务端还有一点,就是用了拦截器,这里打印出了当前的端口,这样,我们在做测试的时候就能看到是哪个服务进行的消息处理。
客户端,我们连的是etcd
,调用gRPC
的注册方法,把我们自定义的Builder
进行注册,这个接口的方法Build
返回的就是我们自定义的Resolver
,里面把从etcd
里watch
到的数据通过调用Grpc
的UpdateState
来更新集群的地址。
到此为止就是这次分享的主要内容,还有最后一页就是分享几个常用的工具。
https://github.com/uw-labs/bloomrpc
图形工具还是很推荐大家用的,写好服务后,这个工具能方便的进行rpc
调用。
也可以用这个库 https://github.com/fullstorydev/grpcurl
像 curl
一样访问 grpc
还有几个库,有兴趣的可以点开看看。
谢谢大家,如果还有什么问题可以说出来,一起讨论。