rpc
框架是微服务时代绕不过去的坎,作为连接各个微服务的“纽带”,也是我们后端从业人员必须掌握的。博主目前接触过thrift
和grpc
,两者可谓各有千秋,都值得我们学习。
以下是早期的学习笔记,部分来源已经找不到出处了,记录一下。
远程过程调用(Remote Procedure Call,RPC)是一个计算机通信协议。
比如有两台服务器,分别是 A、B。在 A 上的应用 C 想要调用 B 服务器上的应用 D,通过rpc可以请求实现,就像本地函数调用一样。
1.http基于http协议,rpc是基于tcp/ip协议,底层是一样的
2.rpc通过序列化发送有效数据,相比http来说效率更高。且rpc是长链接,
不用每次通信都进行三次握手。不过http2.0也可以链接复用了
3.rpc一般比较复杂,涉及RPC框架,服务注册与发现,服务治理
4.rpc操作的是方法对象,而http操作的是资源
Thrift
是一个跨语言的服务部署框架,通过一个中间语言(IDL
, 接口定义语言)来定义RPC
的接口和数据类型,然后通过一个编译器生成不同语言的代码,支持二进制传输格式。
thrift
是Apache
的一个跨语言的高性能的服务框架,也得到了广泛的应用。它的功能类似 gRPC
, 支持跨语言,不支持服务治理。
常规用法:
1.跨语言的rpc框架,可以支持go和php
2.一般需要先定义idl文件,里面的接口返回文件结构定义为xxx.thrift
3.使用thrift工具例如:thrift -r --gen go echo.thrift 生成gen/go文件夹,相当于把定义的协议翻译成go代码
4.go项目中引入idl项目,此时即可调用idl中生成的方法
详细的thrift学习可以戳:
Thrift序列化与反序列化
Thrift实现原理
gRPC
是一个高性能、通用的开源RPC
框架,其由Google
主要面向移动应用开发并基于HTTP/2
协议标准而设计,基于ProtoBuf(Protocol Buffers)
序列化协议开发,且支持众多开发语言。
grpc调用流程:
(1)server创建
创建 Netty HTTP/2 服务端;将需要调用的服务端接口实现类注册到内部的 Registry
中,RPC 调用时,可以根据 RPC 请求消息中的服务定义信息查询到服务接口实现类;
创建 gRPC Server,它是 gRPC 服务端的抽象,聚合了各种 Listener,用于 RPC
消息的统一调度和处理
(2)client创建
1)客户端 Stub(GreeterBlockingStub) 调用 sayHello(request),发起 RPC 调用;
2)通过 DnsNameResolver 进行域名解析,获取服务端的地址信息(列表),随后使用
3)默认的 LoadBalancer 策略,选择一个具体的 gRPC 服务端实例;
4)如果与路由选中的服务端之间没有可用的连接,则创建 NettyClientTransport 和
NettyClientHandler,发起 HTTP/2 连接;
5)对请求消息使用 PB(Protobuf)做序列化,通过 HTTP/2 Stream 发送给 gRPC 服务端;
6)接收到服务端响应之后,使用 PB(Protobuf)做反序列化;
7)回调 GrpcFuture 的 set(Response) 方法,唤醒阻塞的客户端调用线程,获取 RPC
响应。
(3)server调用
1)gRPC 的客户端请求消息由 Netty Http2ConnectionHandler 接入,由 gRPC 负责将
PB 消息(或者 JSON)反序列化为 POJO 对象,然后通过服务定义查询到该消息
对应的接口实例,发起本地 Java 接口调用,调用完成之后,将响应消息序列化为
PB(或者 JSON),通过 HTTP2 Frame 发送给客户端。
2)gRPC 请求消息接入;
3)gRPC 消息头和消息体处理;
4)内部的服务路由和调用;
5)响应消息发送。
市面上grpc
的学习资料非常多,大家可以很详细的学习一下。
grpc
的序列化协议是protobuff
,它的序列化速度快,体积小等优点一直比较受推崇,那么为什么速度那么快呢?
protobuf
全称protocol buffers
,是一种语言无关、平台无关、可扩展的序列化结构数据方法。
1)压缩率高
Tag - Length - Value,标识 - 长度 - 字段值 存储方式
1.不需要分隔符就能分隔开字段,减少了分隔符的使用
2.各字段 存储得非常紧凑,存储空间利用率非常高
3.字段没有被设置字段值,那么该字段在序列化时的数据中是完全
不存在的,即不需要编码
4.采用了独特的编码方式,如Varint、Zigzag编码方式等等
2)序列化和反序列速度快
a. 编码 / 解码 方式简单(只需要简单的数学运算 = 位移等等)
b. 采用 Protocol Buffer 自身的框架代码 和 编译器 共同完成
序列化和反序列化过程:
序列化过程如下:
1.判断每个字段是否有设置值,有值才进行编码
2.根据 字段标识号&数据类型 将 字段值 通过不同的编码
方式进行编码
反序列化过程如下:
1.调用消息类的parseFrom(input)解析从输入流读入的二进制字节数据流
2.将解析出来的数据 按照指定的格式读取到Java、C++、Phyton对应的
结构类型中
thrift
中没有像protobuff
这么知名的序列化协议,相对来说官方为大家提供了几种类型的序列化协议,方便根据实际场景选择。
1)数据结构(通过TProtocol进行序列化和反序列化)
TBinaryProtocol: 二进制格式;
TCompactProtocol: 压缩格式;
TJSONProtocol: JSON格式;
TSimpleJSONProtocol: 提供只写的JSON协议。
2)传输协议
TSocket: 阻塞式socket;
TFramedTransport: 以frame为单位进行传输,非阻塞式服务中使用;
TFileTransport: 以文件形式进行传输。
参考:【RPC基础系列2】一文搞懂gRPC和Thrift的基本原理和区别
1)GRPC主要就是搞了个ProtoBuf,然后采用HTTP协议,所以协议部分没有重复造轮子,重点就在ProtoBuf上。
2)Thrift的数据格式是用的现成的,没有单独搞一套,但是它在传输层和服务端
全部是自己造轮子,所以可以对协议层、传输层有多种控制要求。
性能比较参考:分布式RPC框架dubbo、motan、rpcx、gRPC、thrift简介与性能比较
相对于Dubbo
来说,grpc
和thrift
框架都没有提供特别好的服务治理能力。
Thrift实现原理
gRPC 源码解读之服务发现
grpc的服务发现:通过 resolver 接口去定义,支持业务自己实现服务发现的 resolver。
假如业务使用 dns 进行服务发现,grpc 提供了 dns_resolver,通过对指定的service
服务,protocol协议以及name域名进行srv查询,来返回 server 的 address 列表。
thrift的服务发现:无
一般我们都需要借用外部存储比如:consul,zk.etcd
等实现服务注册发现。
比如:
grpc+consul
thrift+zk
序列化: 将数据结构或对象转换成二进制串的过程
反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程
(1)IDL(Interface description language)文件:参与通讯的各方需要对通讯的内容
需要做相关的约定(Specifications)。为了建立一个与语言和平台无关的约定,
这个约定需要采用与具体开发语言、平台无关的语言来进行描述。这种语言被称为
接口描述语言(IDL),
采用IDL撰写的协议约定称之为IDL文件。
(2)IDL Compiler:IDL文件中约定的内容为了在各语言和平台可见,需要有一个
编译器,将IDL文件转换成各语言对应的动态库。
(3)Stub/Skeleton Lib:负责序列化和反序列化的工作代码。Stub是一段部署在
分布式系统客户端的代码,一方面接收应用层的参数,并对其序列化后通过底层协议栈
发送到服务端。
另一方面接收服务端序列化后的结果数据,反序列化后交给客户端应用层;
Skeleton部署在服务端,其功能与Stub相反,从传输层接收序列化参数,反序列化后交给
服务端应用层,并将应用层的执行结果序列化后最终传送给客户端Stub。
(4)Client/Server:指的是应用层程序代码,他们面对的是IDL所生存的特定语言
class或struct。
(5)底层协议栈和互联网:序列化之后的数据通过底层的传输层、网络层、链路层以及
物理层协议转换成数字信号在互联网中传递。
end