本文由 简悦 SimpRead 转码, 原文地址 juejin.cn
前言 之前对于 gRPC 和 Thrift 只停留在会用的阶段,虽然也初步了解过两者的执行流程,但时间一长又忘了,如果让我评估两者如何选型,我更是蒙圈。所以就想把之前学习的知识整理一下,来填补自己的知识盲区。
讲解 gRPC 和 Thrift 的基本原理,以及两者如何选型。
之前对于 gRPC 和 Thrift 只停留在会用的阶段,虽然也初步了解过两者的执行流程,但时间一长又忘了,如果让我评估两者如何选型,我更是蒙圈。所以就想把之前学习的知识整理一下,来填补自己的知识盲区。
更多内容请参考《【RPC 基础系列 1】聊聊 RPC》
RPC 框架的目标就是让远程服务调用更加简单、透明,RPC 框架负责屏蔽底层的传输方式(TCP 或者 UDP)、序列化方式(XML/Json/ 二进制)和通信细节。服务调用者可以像调用本地接口一样调用远程的服务提供者,而不需要关心底层通信细节和调用过程。
gRPC 简介
gRPC 是一个高性能、通用的开源 RPC 框架,其由 Google 2015 年主要面向移动应用开发并基于 HTTP/2 协议标准而设计,基于 ProtoBuf 序列化协议开发,且支持众多开发语言。
由于是开源框架,通信的双方可以进行二次开发,所以客户端和服务器端之间的通信会更加专注于业务层面的内容,减少了对由 gRPC 框架实现的底层通信的关注。
如下图,DATA 部分即业务层面内容,下面所有的信息都由 gRPC 进行封装。
gRPC 特点
- 语言中立,支持多种语言;
- 基于 IDL 文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub;
- 通信协议基于标准的 HTTP/2 设计,支持双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量;
- 序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 调用的高性能。
gRPC 交互过程
- 交换机在开启 gRPC 功能后充当 gRPC 客户端的角色,采集服务器充当 gRPC 服务器角色;
- 交换机会根据订阅的事件构建对应数据的格式(GPB/JSON),通过 Protocol Buffers 进行编写 proto 文件,交换机与服务器建立 gRPC 通道,通过 gRPC 协议向服务器发送请求消息;
- 服务器收到请求消息后,服务器会通过 Protocol Buffers 解译 proto 文件,还原出最先定义好格式的数据结构,进行业务处理;
- 数据处理完后,服务器需要使用 Protocol Buffers 重编译应答数据,通过 gRPC 协议向交换机发送应答消息;
- 交换机收到应答消息后,结束本次的 gRPC 交互。
简单地说,gRPC 就是在客户端和服务器端开启 gRPC 功能后建立连接,将设备上配置的订阅数据推送给服务器端。我们可以看到整个过程是需要用到 Protocol Buffers 将所需要处理数据的结构化数据在 proto 文件中进行定义。
什么是 Protocol Buffers?
你可以理解 ProtoBuf 是一种更加灵活、高效的数据格式,与 XML、JSON 类似,在一些高性能且对响应速度有要求的数据传输场景非常适用。ProtoBuf 在 gRPC 的框架中主要有三个作用:
- 定义数据结构
- 定义服务接口
- 通过序列化和反序列化,提升传输效率
为什么 ProtoBuf 会提高传输效率呢?
我们知道使用 XML、JSON 进行数据编译时,数据文本格式更容易阅读,但进行数据交换时,设备就需要耗费大量的 CPU 在 I/O 动作上,自然会影响整个传输速率。Protocol Buffers 不像前者,它会将字符串进行序列化后再进行传输,即二进制数据。
可以看到其实两者内容相差不大,并且内容非常直观,但是 Protocol Buffers 编码的内容只是提供给操作者阅读的,实际上传输的并不会以这种文本形式,而是序列化后的二进制数据。字节数会比 JSON、XML 的字节数少很多,速率更快。
如何支撑跨平台,多语言呢?
Protocol Buffers 自带一个编译器也是一个优势点。前面提到的 proto 文件就是通过编译器进行编译的,proto 文件需要编译生成一个类似库文件,基于库文件才能真正开发数据应用。具体用什么编程语言编译生成这个库文件呢?由于现网中负责网络设备和服务器设备的运维人员往往不是同一组人,运维人员可能会习惯使用不同的编程语言进行运维开发,那么 Protocol Buffers 其中一个优势就能发挥出来——跨语言。
从上面的介绍,我们得出在编码方面 Protocol Buffers 对比 JSON、XML 的优点:
- 简单,体积小,数据描述文件大小只有 1/10 至 1/3;
- 传输和解析的速率快,相比 XML 等,解析速度提升 20 倍甚至更高;
- 可编译性强。
基于 HTTP 2.0 标准设计
除了 Protocol Buffers 之外,从交互图中和分层框架可以看到, gRPC 还有另外一个优势——它是基于 HTTP 2.0 协议的。
由于 gRPC 基于 HTTP 2.0 标准设计,带来了更多强大功能,如多路复用、二进制帧、头部压缩、推送机制。这些功能给设备带来重大益处,如节省带宽、降低 TCP 连接次数、节省 CPU 使用等。gRPC 既能够在客户端应用,也能够在服务器端应用,从而以透明的方式实现两端的通信和简化通信系统的构建。
HTTP 版本分为 HTTP 1.X、 HTTP 2.0,其中 HTTP 1.X 是当前使用最广泛的 HTTP 协议,HTTP 2.0 称为超文本传输协议第二代。HTTP 1.X 定义了四种与服务器交互的方式,分别为:GET、POST、PUT、DELETE,这些在 HTTP 2.0 中均保留。HTTP 2.0 的新特性:
- 双向流、多路复用
- 二进制帧
- 头部压缩
Thrift 简介
thrift 是一种可伸缩的跨语言服务的 RPC 软件框架。它结合了功能强大的软件堆栈的代码生成引擎,以建设服务,高效、无缝地在多种语言间结合使用。2007 年由 facebook 贡献到 apache 基金,是 apache 下的顶级项目,具备如下特点:
- 支持多语言:C、C++ 、C# 、D 、Delphi 、Erlang 、Go 、Haxe 、Haskell 、Java 、JavaScript、node.js 、OCaml 、Perl 、PHP 、Python 、Ruby 、SmallTalk
- 消息定义文件支持注释,数据结构与传输表现的分离,支持多种消息格式
- 包含完整的客户端 / 服务端堆栈,可快速实现 RPC,支持同步和异步通信
Thrift 框架结构
Thrift 是一套包含序列化功能和支持服务通信的 RPC(远程服务调用)框架,也是一种微服务框架。其主要特点是可以跨语言使用,这也是这个框架最吸引人的地方。
图中 code 是用户实现的业务逻辑,接下来的 Service.Client 和 write()/read() 是 thrift 根据 IDL 生成的客户端和服务端的代码,对应于 RPC 中 Client stub 和 Server stub。TProtocol 用来对数据进行序列化与反序列化,具体方法包括二进制,JSON 或者 Apache Thrift 定义的格式。TTransport 提供数据传输功能,使用 Apache Thrift 可以方便地定义一个服务并选择不同的传输协议。
Thrift 网络栈结构
thirft 使用 socket 进行数据传输,数据以特定的格式发送,接收方进行解析。我们定义好 thrift 的 IDL 文件后,就可以使用 thrift 的编译器来生成双方语言的接口、model,在生成的 model 以及接口代码中会有解码编码的代码。thrift 网络栈结构如下:
Transport 层
代表 Thrift 的数据传输方式,Thrift 定义了如下几种常用数据传输方式:
- TSocket: 阻塞式 socket;
- TFramedTransport: 以 frame 为单位进行传输,非阻塞式服务中使用;
- TFileTransport: 以文件形式进行传输。
TProtocol 层
代表 thrift 客户端和服务端之间传输数据的协议,通俗来讲就是客户端和服务端之间传输数据的格式 (例如 json 等),thrift 定义了如下几种常见的格式:
- TBinaryProtocol: 二进制格式;
- TCompactProtocol: 压缩格式;
- TJSONProtocol: JSON 格式;
- TSimpleJSONProtocol: 提供只写的 JSON 协议。
Server 模型
- TSimpleServer: 简单的单线程服务模型,常用于测试;
- TThreadPoolServer: 多线程服务模型,使用标准的阻塞式 IO;
- TNonBlockingServer: 多线程服务模型,使用非阻塞式 IO(需要使用 TFramedTransport 数据传输方式);
- THsHaServer: THsHa 引入了线程池去处理,其模型读写任务放到线程池去处理,Half-sync/Half-async 处理模式,Half-async 是在处理 IO 事件上 (accept/read/write io),Half-sync 用于 handler 对 rpc 的同步处理;
功能比较
直接贴上网上的两幅截图:
性能比较
也是基于网上测试的结果,仅供参考:
- 整体上看,长连接性能优于短连接,性能差距在两倍以上;
- 对比 Go 语言的两个 RPC 框架,Thrift 性能明显优于 gRPC,性能差距也在两倍以上;
- 对比 Thrift 框架下的的两种语言,长连接下 Go 与 C++ 的 RPC 性能基本在同一个量级,在短连接下,Go 性能大概是 C++ 的二倍;
- 对比 Thrift&C++ 下的 TSimpleServer 与 TNonblockingServer,在单进程客户端长连接的场景下,TNonblockingServer 因为存在线程管理开销,性能较 TSimpleServer 差一些;但在短连接时,主要开销在连接建立上,线程池管理开销可忽略;
- 两套 RPC 框架,以及两大语言运行都非常稳定,5w 次请求耗时约是 1w 次的 5 倍;
如何选型
什么时候应该选择 gRPC 而不是 Thrift:
- 需要良好的文档、示例
- 喜欢、习惯 HTTP/2、ProtoBuf
- 对网络传输带宽敏感
什么时候应该选择 Thrift 而不是 gRPC:
- 需要在非常多的语言间进行数据交换
- 对 CPU 敏感
- 协议层、传输层有多种控制要求
- 需要稳定的版本
- 不需要良好的文档和示例
这篇文章应该非常详细介绍 gRPC 和 Thrift 两者的特点和区别,目前我还没有发现有哪篇文章总结的比我这还要好,当然除了源码解读部分(个人不建议上来就解读源码,知道执行流程和区别,便于我们使用和选型就可以)。
通篇总结下来,总结如下:
- GRPC 主要就是搞了个 ProtoBuf,然后采用 HTTP 协议,所以协议部分没有重复造轮子,重点就在 ProtoBuf 上。
- Thrift 的数据格式是用的现成的,没有单独搞一套,但是它在传输层和服务端全部是自己造轮子,所以可以对协议层、传输层有多种控制要求。
欢迎大家多多点赞,更多文章,请关注微信公众号 “楼仔进阶之路”,点关注,不迷路~~