背景
远程过程调用(Remote Procedure Call,RPC)服务于分布式架构,本文从分布式构架面临的问题,期望的结果,引出两种比较受关注的RPC框架Grpc、Thrift。
gRPC
介绍
gRPC 是用于服务间通信的相对较新的一种远程过程调用(RPC)API 范式。与其他 RPC 一样,它允许位于不同服务器上的应用程序之间相互调用方法,就好像它们是本地对象一样,其由Google 2015年主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf序列化协议开发,且支持众多开发语言。由于是开源框架,通信的双方可以进行二次开发,所以客户端和服务器端之间的通信会更加专注于业务层面的内容,减少了对由gRPC框架实现的底层通信的关注。
特点
- 自由,开放:让所有人,所有平台都能使用,其实就是开源,跨平台,跨语言
- 协议可插拔:不同的服务可能需要使用不同的消息通信类型和编码机制,例如,JSON、XML 和 Thirft, 所以协议应允许可插拔机制,还有负载均衡,服务发现,日志,监控等都支持可插拔机制
- 阻塞和非阻塞:支持客户端和服务器交换的消息序列的异步和同步处理。
- 取消和超时:一次 RPC 操作可能是持久并且昂贵的,应该允许客户端设置取消 RPC 通信和对这次通信加上一个超时时间
- 拒绝:必须允许服务器通过在继续处理请求的同时拒绝新请求的到来并优雅地关闭。
- 流处理:存储系统依靠流和流控制来表达大型数据集,其他服务,如语音到文本或股票行情,依赖于流来表示与时间相关的消息序列
- 流控制:计算能力和网络容量在客户端和服务器之间通常是不平衡的。流控制允许更好地缓冲区管理,以及过度活跃的对等体提供对 DOS 的保护。
- 元数据交换 : 认证或跟踪等常见的跨领域问题依赖于不属于服务声明接口的数据交换。 依赖于他们将这些特性演进到服务,暴露 API 来提供能力。
- 标准化状态码 :客户端通常以有限的方式响应 API 调用返回的错误。应约束状态码名称空间,以使这些错误处理决策更加清晰。如果需要更丰富的特定领域的状态,则可以使用元数据交换机制来提供该状态。
- 互通性:报文协议 (Wire Protocol) 必须遵循普通互联网基础框架
架构
交互方式
在 gRPC 里,客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。
请求/响应模式
- 单项rpc (simple rpc):也就是发送一个请求,返回一个数据包,是属于比较通用的rpc请求模式.
- 服务侧流式rpc (server-side rpc):客户端发送一个请求,服务端返回连续的流式数据.
- 客户端侧流式rpc (client-side rpc):客户端发流式请求,服务端返回单个响应.
- 双向流式rpc (Bidirectional streaming RPC):客户端发流式请求,服务端返回连续的流式数据.
调用过程
远程过程调用流程图
- 调用客户端句柄,执行传送参数
- 调用本地系统内核发送网络消息
- 消息传送到远程主机上
- 服务器句柄得到消息并取得参数
- 执行远程过程
- 执行的过程将结果返回服务器句柄
- 服务器句柄返回结果,调用远程系统内核
- 消息传回本地主机上
- 客户端句柄由内核接收消息
- 客户接收句柄返回的数据
Thrift
介绍
Apache Thrift 最初是 Facebook 实现的一种支持多种编程语言、高效的远程服务器调用框架,它于 2008 年进入 Apache 开源项目。Apache Thrift 采用接口描述语言(IDL)定义 RPC 接口和数据类型,通过编译器生成不同语言的代码(支持 C++,Java,Python,Ruby等),其数据传输采用二进制格式,相对 XML 和 JSON 来说体积更小,对于高并发、大数据量和多语言的环境更有优势。在 Facebook,Apache Thrift 正是使用于其内部服务的通信,其稳定性和高性能已经在生产环境中得到证明的。
特点
- 跨语言,支持20+语言,几乎涵盖所有主流语言
- 消息定义文件支持注释,数据结构与传输表现的分离,支持多种消息格式
- 包含完整的客户端/服务端堆栈,可快速实现RPC,支持同步和异步通信
架构
Thrift实际上是实现了C/S模式,通过代码生成工具将接口定义文件生成服务器端和客户端代码(可以为不同语言),从而实现服务端和客户端跨语言的支持。用户在Thirft描述文件中声明自己的服务,这些服务经过编译后会生成相应语言的代码文件,然后用户实现服务(客户端调用服务,服务器端提供服务)便可以了。其中protocol(协议层, 定义数据传输格式,可以为二进制或者XML等)和transport(传输层,定义数据传输方式,可以为TCP/IP传输,内存共享或者文件共享等)被用作运行时库。
核心组件
- IDL服务描述组件,负责完成跨平台和跨语言(针对不同语言完成了Server层和Client代码的生成)
- TServer和Client,服务端和客户端组件的实现
- TProtocal 协议和解编码组件
- TTransport 传输组件
- TProcessor 服务调用组件,完成对服务实现的调用
传输格式
- TBinaryProtocol : 二进制格式.
- TCompactProtocol: 压缩格式
- TJSONProtocol: JSON格式
- TSimpleJSONProtocol:提供JSON只写协议, 生成的文件很容易通过脚本语言解析。
- TDebugProtocol – 使用易懂的可读的文本格式,以便于debug
传输方式
- TSocket:阻塞式socker
- TFramedTransport:以frame为单位进行传输,非阻塞式服务中使用。
- TFileTransport: 以文件形式进行传输。
- TMemoryTransport :将内存用于I/O. java实现时内部实际使用了简单的ByteArrayOutputStream。
- TZlibTransport :使用zlib进行压缩, 与其他传输方式联合使用。当前无java实现。
Server模型
- TSimpleServer : 简单的单线程服务模型,常用于测试
- TThreadPoolServer : 多线程服务模型,使用标准的阻塞式IO。
- TNonblockingServer : 多线程服务模型,使用非阻塞式IO(需使用TFramedTransport数据传输方式)
- THsHaServer: THsHa引入了线程池去处理,其模型读写任务放到线程池去处理,Half-sync/Half-async处理模式,Half-async是在处理IO事件上(accept/read/write io),Half-sync用于handler对rpc的同步处理;
结论
gRPC 性能更高,编码节约空间,使得在低带宽场景下很有优势。protobuf通过编译工具生成数据读写代码,提高开发者编码效率。支持向上游传递超时时间,让上游在发现超时时主动决定如何执行后续操作,http1.1则会直接断开连接。但是只有少数浏览器支持gRPC、移动端网络可能在wifi及4G频繁切换,无法体现出长连接以及多路复用的优势、由于使用protobuf编码后数据可读性低。Apache Thrift 支持非常多的语言绑定,thrift文件生成目标代码,简单易用, 数据结构与传输表现的分离,支持多种消息格式,包含完整的客户端/服务端堆栈,可快速实现RPC,支持同步和异步通信,但是和protobuf一样不支持动态特性,Thrift 适用于程序对程序静态的数据交换,需先确定好它的数据结构,它是完全静态化的,当数据节后发生变化时,必须重新编译 IDL 文件,实现类接口,Client 端和 Server 端做相应的修改
RPC框架功能比较:
功能 | gRPC | Thrift |
开发语言 | 跨语言 | 跨语言 |
分布式(服务治理) | × | × |
多序列化框架支持 | × 只支持protobuf) | ×(thrift格式) |
多种注册中心 | × | × |
管理中心 | × | × |
跨编程语言 | √ | √ |
支持REST | × | × |
关注度 | 中 | 中 |
上手难度 | 中 | 中 |
运维成本 | 中 | 低 |
开源机构 | Apache |