![](https://i-blog.csdnimg.cn/blog_migrate/acef12ffaab4aa19d700dd07b3303575.webp?x-image-process=image/format,png)
年末最后一弹,我们来简单介绍一下Apache顶级项目Thrift并尝试了解其内部实现机制。
1. Thrift介绍
Thrift最初是由Facebook操刀实现的一个内部项目,主要用于Facebook内部各系统之间的RPC调用。2007年开源,2008年进入Apache孵化器,现在是Apache顶级项目之一。
官网极简,文档匮乏,简直不忍直视。
官网介绍Thrift是一个可伸缩,跨语言服务RPC框架,集成了强大的软件堆栈及代码生成引擎,使得各种语言做到无障碍,高效通信,目前支持C++, Java, Python, PHP, Ruby, Erlang, Perl, Hashkell, C#, Go, Cocoa,JavaScript,Node.js,Smalltalk等等。
RPC框架并非新鲜事物,十几二十年前在非互联网时代已经有了;目前流行的RPC框架也很多,如Google的gRPC,JBoss的Wildfly以及基于Go的rpcx。 国内则较知名有阿里Dubbo,新浪微博的Montan等众多优秀框架,试问Thrift有何能耐可以独树一帜?
2. RPC 机制
我们先简要了解一下RPC的原理与机制。
RPC远程过程调用,简单来说如即两台服务器A,B。A服务器上的应用需要调用B服务器上的应用提供的函数,比如说查询服务器B的实时内存使用率。
现代计算技术受限于内存空间,进程,无法直接调用,需要通过网络跨机器,系统,进程进行通信,这个过程统称RPC。
通信机制
![](https://i-blog.csdnimg.cn/blog_migrate/1d53885d9eab343bbe539cc04a6b1099.webp?x-image-process=image/format,png)
客户端与服务器通信,如TCP连接,包括按需端连接或者长连接
寻址问题,如IP地址 + 端口号;如果基于Web服务协议则需要URI,或者依赖于UDDI服务
序列化,需要把所调用的方法参数进行序列化成二进制通过如TCP协议进行传输
服务端收到请求后进行反序列化,恢复为内存中的表达,寻址对应方法进行本地调用
服务端返回调用结果,类似通过序列化返回客户端使用。
CPU方面Thrift综合来看也非常卓越。
其实任何异构平台的通信大抵如此,如与数据库交互,消息中间件等,都需要经历首先建立通信连接作为通信的基石,桥梁;其次对于双方交互的具体应用/系统通过IP与端口进行寻址,进行握手;之后则把通信的信息按照通信协议要求封装,如序列化/反序列化,收到信息方则进行本地寻址调用并返回调用结果。
![](https://i-blog.csdnimg.cn/blog_migrate/46c1f7695dd3f8421961213898f8e434.webp?x-image-process=image/format,png)
3. RPC协议
如上文所述,RPC协议并非新鲜事物,十几二十年前工程师们就开始探索跨机器,网络通信机制了。
如果你有用过,或者听过这些著名的协议:
CORBA, COM/DCOM/COM+, .NET Remoting, RMI, SOAP, Web Service, Hessian, HTTP/REST等。
可能上述协议很多人都没有听说过,尤其是新新人类,享受在互联网/大数据的海洋里。
CORBA, COM(仅限Windows),. Net Remoting(仅限.NET), RMI(仅限Java) SOAP早已或者逐步推出历史舞台。
对于相对较近几年流行如基于XML的Web Service与基于JSON的RESTful服务,其数据传输方式以XML, JSON为主,然而各有利弊。XML相对体积较大,传输效率低;JSON则表现能力等不够完善。
通信协议描述
维护服务/客户端契约
包装类
解析XML/JSON开销
存储空间相对较多
基于HTTP协议,性能受限
基于HTTP协议的RPC天生有良好的跨平台性,然而在严重依赖,需要高性能通信的情况下,如集群内部通信,Hadoop集群,HBase各个Region之间通信,Cassandra存储服务器
等等,使用高性能TCP协议的RPC框架是上选。
4. Thrift 架构
了解回顾RPC整体机制后,我们看一下Thrift如何封装整合,做到其称之为完整堆栈结构的。
4.1 堆栈架构
![](https://i-blog.csdnimg.cn/blog_migrate/4e56c210d1d0eaa3c82c3349bc9e4f37.webp?x-image-process=image/format,png)
可以看到整体架构图很清晰,对应整个RPC过程。其中显示了应用本身代码,以及根据Thrift定义服务接口描述自动生成代码框架,红色部分一下则为Thrift内部传输体系,协议及底层I/O通信。
Thrift包含了用于绑定协议,传输层的基础架构,提供阻塞/非阻塞,单线程/多线程模式,当然也可以配合服务器/容器一起运行,与J2EE服务器/Web容器无缝整合。
5. 简单示例
看一个简单事例吧以方便后续讲解RPC实现过程。 以学校课程管理吧:
Thrift的编码规范遵循以下Martin理念:
Any fool can write code that a computer can understand. Good programmers write code that humans can understand.- Martin Flower, 1999
5.1 IDL / .thrift文件
编写Thrift要首先使用Thrift类型编写IDL(Interface Definition)定义RPC所需数据结构及服务,老古董们应该似曾相识或者老友重逢。
![](https://i-blog.csdnimg.cn/blog_migrate/fafcd5711ab17588eed82f2bb4dcd1c0.webp?x-image-process=image/format,png)
接下来就是服务的定义:
![](https://i-blog.csdnimg.cn/blog_migrate/0714f9e9089973dad2f3909ce03e9eb0.webp?x-image-process=image/format,png)
还有一些异常的定义:
![](https://i-blog.csdnimg.cn/blog_migrate/4379a47dbbc6c1f7ff689499f5c369e2.webp?x-image-process=image/format,png)
5.2 Thrift Types
看到这里,我们应该了解一下Thrift支持的数据类型了:
![](https://i-blog.csdnimg.cn/blog_migrate/936ea6538198082eeb29d23a390b9bd6.webp?x-image-process=image/format,png)
还真是与以前的IDL一样啊,基本使用C风格的定义与风格。
http://thrift.apache.org/docs/idl
IDL支持描述,Header/Namespace/Package , include, Const, Typedef, Enum, Struct, Union, Service, Exception, Field, XSD Options, Functions, Types等,囊括了程序交互各种需求。
5.3 Code Generation
随后,我们可以进行代码生成,支持C/C++, C#, Java, Erlang, Perl, PHP, Python, Ruby, Go等等。
thrift --gen java course.thrift
![](https://i-blog.csdnimg.cn/blog_migrate/d46a7f01eb7f639d0897d96b172c5003.webp?x-image-process=image/format,png)
5.4 Java Server
好了,开始写一个Java的服务端吧:
![](https://i-blog.csdnimg.cn/blog_migrate/e7c5cc5b60e9b1f45a17062f385ac7b1.webp?x-image-process=image/format,png)
这是一个简单的单线程同步阻塞式I/O,其中的processor需要传入我们自己定义实习的服务接口实现类:
![](https://i-blog.csdnimg.cn/blog_migrate/20275c31e83395da7b633685ea807bf6.webp?x-image-process=image/format,png)
5.5 Java Client
客户端则更为精简,创建基本socket,通信连接后直接调用代码接口即可,客户端无须实现接口。
![](https://i-blog.csdnimg.cn/blog_migrate/f28e8beec2f7be9d7dda8037295b1add.webp?x-image-process=image/format,png)
5.6 部署架构
![](https://i-blog.csdnimg.cn/blog_migrate/f80097998a9ac9ab3a3c705056e7f573.webp?x-image-process=image/format,png)
可以看到,与经典RPC框架类似,客户端与服务器需要用到中间绿色区域公共的jar包与java文件,服务器端则实现.Iface接口,包括Server;客户端则实现调用代码;其中间通信则通过Thrift生成的API类实现远程服务调用。
6. 性能对比
我们来开始对比一下Thrift与其它RPC框架的性能:
6.1 Payload
Payload展现各序列化及压缩的功底:
![](https://i-blog.csdnimg.cn/blog_migrate/4de460b748c8e216de66dfceaa891ed1.webp?x-image-process=image/format,png)
Google的Protocol Buffer独领风骚,Thrift也优势尽显。
6.2 Runtime Performance
整体RPC性能则考察RPC框架的整体协作战斗力:
![](https://i-blog.csdnimg.cn/blog_migrate/41e505683b409fe50093a049976a8c90.webp?x-image-process=image/format,png)
Thrift一枝独秀,几乎5倍于传统XML/JSON的性能。
6.3 CPU
![](https://i-blog.csdnimg.cn/blog_migrate/67904284b65230743941792851017180.webp?x-image-process=image/format,png)
6.4 Thrift v.s. gRPC v.s. Dubbo
我们再来看一下Thrift与Google最新力作,基于HTTP2.0协议,底层依赖于Netty 5的gRPC协议,以及阿里的老牌产品Dubbo较力吧:
![](https://i-blog.csdnimg.cn/blog_migrate/0c6260f5b823a6a373cf2063bbc47123.webp?x-image-process=image/format,png)
具体数据如下:
![](https://i-blog.csdnimg.cn/blog_migrate/9176fa12717f84a81dff20c107d2e092.webp?x-image-process=image/format,png)
有图有数据,基本上Thrift领先一个数量级,Dubbo则也第二阶梯中领头羊。
好吧,看来人家官网极度精简是有资本的。
7. Thrift 设计
来看一下Thrift的底层设计吧:
7.1 Network Stack
Thrift为了更好的支持多种语言,底层则用了更为抽象简单的四层网络stack:
![](https://i-blog.csdnimg.cn/blog_migrate/edee1b6c979304715b97ae7a5a99050a.webp?x-image-process=image/format,png)
7.2 Transport
Transport层封装抽象,或者解耦了具体底层传输(序列化/反序列化),包含了open/close/read/write/flush等方法。
其基础类为Transport类以及C实现TVirtualTransport类。
TSocket, 阻塞I/O
THttpTransport, HTTP
TFileTransport, 文件
TZlibTransport, 压缩数据
除此之外,还封装了批量的高效的TFramedTransport, TMemoryTransport(ByteArrayOutputStream)等。
TSocket底层也是直接使用Java IOStream来实现:
![](https://i-blog.csdnimg.cn/blog_migrate/5eaaeaebbb3f81a4c5222aac3e40d6b4.webp?x-image-process=image/format,png)
7.3 Protocol
Protocol主要定义了如何在内存与IDL数据结构映射及序列化/反序列。
Thrift也支持文本及二进制流传输,以TVirtualProtocol为基类分为:
TBinaryProtocol, 二进制
TJsonProtocol
TCompactprocotol, 压缩二进制
TDebugProtocol
TSimpleJsonProtocol
VLQ
我们详细看一下TCompactProtocol, 压缩二进制,这个可是Thrift高效率的秘籍啊。
其内部则使用了Tag + Data, 通过VLQ(Variable-Length Quantity)编码。
![](https://i-blog.csdnimg.cn/blog_migrate/535485bd2cf83b9847d2152261223a9a.webp?x-image-process=image/format,png)
Variable-Length Quantity(VLQ)
![](https://i-blog.csdnimg.cn/blog_migrate/c12447dbe29f4691c17b846903eb0ab9.webp?x-image-process=image/format,png)
通过VLQ编码,上图数字106903 32bits的时候可以节省1个byte的长度。
https://en.wikipedia.org/wiki/Variable-length_quantity
7.4 Processor
Processor则封装了客户端/服务端对应Service的序列化与反序列化操作。
7.5 Server
Server则相当于一个容器,工厂,创建TProcessor, TTransport, TProtocol对象并整合通信。
TSimpleServer, 阻塞Blocking Server
TThreadPoolServer, 阻塞多线程处理Server
TThreadSelectorServer, NonBlockingServer
8. 总结
时间较紧,简单介绍了一下Thrift的架构,实现及优势。总体来说,虽然Thrift继承了一些传统,陈旧的思想如IDL,代码生成(猜想作者应该经历过原始的RPC协议调用)等, 但其优秀的性能表现,跨多语言的支持,对于很多程序需要高新能RPC调用来说,还是比较推荐的。
感谢大家一年来的支持,也祝大家新年快乐!
◆ ◆ ◆ ◆
2017新年快乐
HAPPY NEW YEAR
◆ ◆ ◆ ◆
公众号:技术极客TechBooster
![](https://i-blog.csdnimg.cn/blog_migrate/f62e364bddef6a37a8877217f8b54d34.webp?x-image-process=image/format,png)