应用笔记 · 2023年10月1日

RPC两种实现对比——gRPC和Thrift的基本原理和区别

转自知乎:https://zhuanlan.zhihu.com/p/389845152?utm_id=0

Rpc回顾

更多内容请参考《【RPC基础系列1】聊聊RPC》

RPC 框架的目标就是让远程服务调用更加简单、透明,RPC 框架负责屏蔽底层的传输方式(TCP 或者 UDP)、序列化方式(XML/Json/ 二进制)和通信细节。服务调用者可以像调用本地接口一样调用远程的服务提供者,而不需要关心底层通信细节和调用过程。

gRPC

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 简介

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的同步处理;

gRPC VS Thrift

功能比较

直接贴上网上的两幅截图:

性能比较

也是基于网上测试的结果,仅供参考:

  • 整体上看,长连接性能优于短连接,性能差距在两倍以上;
  • 对比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的数据格式是用的现成的,没有单独搞一套,但是它在传输层和服务端全部是自己造轮子,所以可以对协议层、传输层有多种控制要求。