当我们需要在跨语言之间进行通信的时候,我们可能需要规范一下传输数据(消息)的格式以满足我们的需求 ,当然GRPC的优势远不止这些,下面我们来慢慢的研究一下。。。。
回顾http2.0
- 新的二进制格式
•Binary Format,HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。
- 多路复用
•MultiPlexing,即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面
- header压缩
•HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小
- 服务端推送
•http2.0是SPDY的升级版,因此也具有server push的功能;
http1.0与http2.0的区别
- https://juejin.im/entry/5981c5df518825359a2b9476
nginx支持http2.0并且在一些浏览器不支持非https的http2.0的请求也会自动的向下兼容的:
- https://www.nginx.com/blog/nginx-1-9-5/
- https://www.nginx.com/blog/http2-module-nginx/
- http://nginx.org/en/docs/http/ngx_http_v2_module.html
通信协议protocol buffers
Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
- 序列化:将数据结构或对象转换成二进制串的过程
- 反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程
目前的protocol buffers有两个版本(proto2/proto3),在Grpc的使用中建议使用proto3的版本
Protobuf(protocol buffers)是谷歌推出的一种二进制数据编码格式通信协议,相比 XML 和 JSON 的文本数据编码格式更有优势,与XML比较
- are simpler
- are 3 to 10 times smaller
- are 20 to 100 times faster
- are less ambiguous
- generate data access classes that are easier to use programmatically
接口描述性语言IDL
典型的序列化和反序列化过程往往需要如下组件:
- IDL(Interface description language)文件: 参与通讯的各方需要对通讯的内容需要做相关的约定(Specifications)。 为了建立一个与语言和平台无关的约定,这个约定需要采用与具体开发语言、平台无关的语言来进行描述。 这种语言被称为接口描述语言(IDL),采用IDL撰写的协议约定称之为IDL文件。
- IDL Compiler: IDL文件中约定的内容为了在各语言和平台可见,需要有一个编译器,将IDL文件转换成各语言对应的动态库。
- Stub/Skeleton Lib: 负责序列化和反序列化的工作代码。 Stub是一段部署在分布式系统客户端的代码,一方面接收应用层的参数,并对其序列化后通过底层协议栈发送到服务端,另一方面接收服务端序列化后的结果数据,反序列化后交给客户端应用层;
- Client/Server: 指的是应用层程序代码,他们面对的是IDL所生存的特定语言的class或struct。
- 底层协议栈和互联网: 序列化之后的数据通过底层的传输层、网络层、链路层以及物理层协议转换成数字信号在互联网中传递。
解析协议性能
Total Time 指一个对象操作的整个时间,包括创建对象,将对象序列化为内存中的字节序列,然后再反序列化的整个过程
序列化空间开销
结论:
- XML序列化(Xstream)无论在性能和简洁性上比较差
- Thrift与Protobuf相比在时空开销方面都有一定的劣势
- Protobuf和Avro在两方面表现都非常优越
使用protocol定义IDL
File structure
- License header (if applicable)
- File overview
- Syntax
- Package
- Imports (sorted)
- File options
- Everything else
对于 JSON
- 在 PHP 中需使用
json_encode()
和json_decode()
去编解码,在 Golang 中需使用 json 标准库的Marshal()
和Unmarshal()
… 每次解析和编码比较繁琐 - 优点: 可读性好、开发成本低
- 缺点: 相比 protobuf 的读写速度更慢、存储空间更多
对于 Protobuf
- .proto 可生成 .php 或 *.pb.go … 在项目中可直接引用该文件中编译器生成的编码、解码函数
- 优点: 高效轻量、一处定义多处使用
- 缺点: 可读性差、开发成本高
proto2与proto3版本之间的更新
- 删除原始值字段的字段存在逻辑
- 删除 required 字段
- 删除 optional 字段,默认就是
- 删除 default 字段
- 删除扩展特性,新增 Any 类型来替代它
- 删除 unknown 字段的支持
- 新增 JSON Mapping
- 新增 Map 类型的支持
- 修复 enum 的 unknown 类型
- repeated 默认使用 packed 编码
- 引入了新的语言实现(C#,JavaScript,Ruby,Objective-C)
protocol buffer安装
- 安装protobuf为了生成对应语言的文件必须需要protoc的命名,protoc是c语言的protobuf的命名. 编译安装
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.6.1/protobuf-cpp-3.6.1.tar.gz
> tar -zxvf protobuf-cpp-3.6.1.tar.gz
> cd protobuf-3.6.1> ./configure
> make
> make install
> protoc --version
libprotoc 3.6.1
- 其他方式安装,如:
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.6.1/protoc-3.6.1-osx-x86_64.zip
> tar -zxvf protoc-3.6.1-osx-x86_64.zip
> cd protoc-3.6.1-osx-x86_6/bin
> ./protoc --version
> libprotoc 3.6.1
前期准备工作
代码语言:javascript复制> git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc
> git clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net
> git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text
> git clone https://github.com/golang/sys.git $GOPATH/src/golang.org/x/sys
> go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
> git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto
> cd $GOPATH/src/
> go install google.golang.org/grpc
protoc文件与.pb.go文件对应关系
.proto文件
的service: 定义了微服务要暴露为外界调用的函数,而这个函数就是RPC远程调用需要的函数,再由 protobuf 编译器的 grpc 插件处理后生成.pb.go文件中的
interface- message: 定义了通信的数据格式,由 protobuf 编译器处理后生成 struct
proto文件中数据标识符的使用:
[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。
Protobuf 的优点
- Protobuf 有如 XML,不过它更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。只需使用 Protobuf 对数据结构进行一次描述,即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写。
- 它有一个非常棒的特性,即“向后”兼容性好,人们不必破坏已部署的、依靠“老”数据格式的程序就可以对数据结构进行升级。这样您的程序就可以不必担心因为消息结构的改变而造成的大规模的代码重构或者迁移的问题。因为添加新的消息中的 field 并不会引起已经发布的程序的任何改变。
- Protobuf 语义更清晰,无需类似 XML 解析器的东西(因为 Protobuf 编译器会将 .proto 文件编译生成对应的数据访问类以对 Protobuf 数据进行序列化、反序列化操作)。
- 使用 Protobuf 无需学习复杂的文档对象模型,Protobuf 的编程模式比较友好,简单易学,同时它拥有良好的文档和示例,对于喜欢简单事物的人们而言,Protobuf 比其他的技术更加有吸引力。
Protobuf 的不足
- Protbuf 与 XML 相比也有不足之处。它功能简单,无法用来表示复杂的概念。
- XML 已经成为多种行业标准的编写工具,Protobuf 只是 Google 公司内部使用的工具,在通用性上还差很多。
- 由于文本并不适合用来描述数据结构,所以 Protobuf 也不适合用来对基于文本的标记文档(如 HTML)建模。另外,由于 XML 具有某种程度上的自解释性,它可以被人直接读取编辑,在这一点上 Protobuf 不行,它以二进制的方式存储,除非你有 .proto 定义,否则你没法直接读出 Protobuf 的任何内容
grpc简介和优势
gRPC是一个高性能、通用的开源 RPC 框架,其由 Google 主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers) 序列化协议开发,且支持众多开发语言。gRPC 提供了一种简单的方法来精确地定义服务和为iOS、Android 和后台支持服务自动生成可靠性很强的客户端功能库。客户端充分利用高级流和链接功能,从而有助于节省带宽、降低的TCP 链接次数、节省 CPU 使用、和电池寿命
gRPC 是谷歌开源的轻量级 RPC 框架,其中的通信协议基于二进制数据流,使得 gRPC 具有优异的性能。
rpc是远端过程调用remote process call,其调用协议通常包含传输协议和序列化协议。
- 调用协议如grpc. 使用的就是http2协议
- 序列化协议包含: 如基于文本编码的 xml json,也有二进制编码的 protobuf hessian等
- 客户端(gRPC Sub)调用 A 方法,发起 RPC 调用
- 对请求信息使用 Protobuf 进行对象序列化压缩(IDL)
- 服务端(gRPC Server)接收到请求后,解码请求体,进行业务逻辑处理并返回
- 对响应结果使用 Protobuf 进行对象序列化压缩(IDL)
- 客户端接受到服务端响应,解码请求体。回调被调用的 A 方法,唤醒正在等待响应(阻塞)的客户端调用并返回响应结果
在同等RPC框下的thrift与gRpc因为实现形式的不同,也决定了在微服务框架下使用rpc框架的区别,grpc因为是基于http2的,可以在header上增加一些不充的参数,所以对比如istio是可以轻松感知链路的,很方便的得到API的请求次数,但是thrift这种基于tcp传输方式来说是很难做的,在微服务使用上自然显得不那么友好。
gRPC 框架的目标就是让远程服务调用更加简单、透明,RPC 框架负责屏蔽底层的传输方式(TCP 或者 UDP)、序列化方式(XML/Json/ 二进制)和通信细节。服务调用者可以像调用本地接口一样调用远程的服务提供者,而不需要关心底层通信细节和调用过程
常用RPC框架的对比
跨语言 | 多 IDL | 服务治理 | 注册中心 | 服务管理 | |
---|---|---|---|---|---|
gRPC | √ | × | × | × | × |
Thrift | √ | × | × | × | × |
Rpcx | × | √ | √ | √ | √ |
Dubbo | × | √ | √ | √ | √ |
介绍常见的三种:
- gRPC:是 Google 公布的开源软件,基于最新的 HTTP 2.0 协议,并支持常见的众多编程语言。RPC 框架是基于 HTTP 协议实现的,底层使用到了 Netty 框架的支持。
- Thrift:是 Facebook 的开源 RPC 框架,主要是一个跨语言的服务开发框架。用户只要在其之上进行二次开发就行,应用对于底层的 RPC 通讯等都是透明的。不过这个对于用户来说需要学习特定领域语言这个特性,还是有一定成本的。
- Dubbo:是阿里集团开源的一个极为出名的 RPC 框架,在很多互联网公司和企业应用中广泛使用。协议和序列化框架都可以插拔是极其鲜明的特色。
实现grpc服务端
- 监听指定 TCP 端口,用于接受客户端请求
- 创建 gRPC Server 的实例对象
- gRPC Server 内部服务和路由的注册
- Serve() 调用服务器以执行阻塞等待,直到进程被终止或被 Stop() 调用
grpc客户端实现
- 创建 gRPC Channel 与 gRPC Server 进行通信(需服务器地址和端口作为参数)
- 设置 DialOptions 凭证(例如,TLS,GCE凭据,JWT凭证)
- 创建 Search Client Stub
- 调用对应的服务方法
grpc服务发现以及负载均衡
gRPC开源组件官方并未直接提供服务注册与发现的功能实现,但其设计文档已提供实现的思路,并在不同语言的gRPC代码API中已提供了命名解析和负载均衡接口供扩展。
其基本实现原理:
- 服务启动后gRPC客户端向命名服务器发出名称解析请求,名称将解析为一个或多个IP地址,每个IP地址标示它是服务器地址还是负载均衡器地址,以及标示要使用那个客户端负载均衡策略或服务配置。
- 客户端实例化负载均衡策略,如果解析返回的地址是负载均衡器地址,则客户端将使用grpclb策略,否则客户端使用服务配置请求的负载均衡策略。
- 负载均衡策略为每个服务器地址创建一个子通道(channel)。
- 当有rpc请求时,负载均衡策略决定那个子通道即grpc服务器将接收请求,当可用服务器为空时客户端的请求将被阻塞。
grpc应用的场景
- 低延迟、高扩展性、分布式的系统
- 同云服务器进行通信的移动应用客户端
- 设计语言独立、高效、精确的新协议
- 便于各方面扩展的分层设计,如认证、负载均衡、日志记录、监控等
grpc解决的问题
- 解决分布式系统中,服务之间的调用问题。
- 远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。
参考
- https://grpc.io/
- https://developers.google.com/protocol-buffers/
- https://juejin.im/entry/5981c5df518825359a2b9476
- http://jiangew.me/grpc-01/
- https://doc.oschina.net/grpc?t=58010
- https://mp.weixin.qq.com/s/sDMX7ICSasyzrFQVeK4vmw