原文:http://blog.csdn.net/guxch/article/details/12157151
------------------------------------------------------------------------------------
一、概述
Thrift是Apache下的一个子项目,最早是Facebook的项目,后来Facebook提供给Apache作为开源项目,在官网上,Thrift被描述为“Scalable Cross-Language Services Implementation”,说的通俗一些,Thrift具有以下特征:- 它有自己的跨机器的通信框架,并提供一套库。
- 它是一个代码生成器,按照它的规则,可以生成多种编程语言的通讯过程代码。
与Thrift相类似的开源项目是Google的Protocol Buffer(Protobuf),Protobuf目前提供了 C++、Java、Python 三种语言的 API,比Thrift简单一些,应用也不如Thrift广泛,有评论说Protobuf写复杂的应用比较困难。
目前thrift的版本是0.9.1,以下的讨论均以该版为基准,代码语言以C++为基准。
二、Thrift应用场景
Thrift其实应分成三个部分,一个叫做Thrift代码生成器,一个叫做Thrift应用框架(库),最后一个是它生成的代码。Thrift应用的基本流程如下图所示。
从上图,要生成一个Thrift应用,需用以下文件:
- 一个.thrift文件:该文件是通信接口的定义,最主要的是信息流的格式。
- 编程语言:这个无需解释。
- Thrift代码生成器(Thrift compiler,翻译成代码生成器似乎更合适):这个东西是安装thrift过程中生成的,它可以产生若干符合你约定通信格式的代码。
- Thrift应用框架库:这个东西也是在安装过程中产生的。
- 其他第三方支撑库:对C++来说,最主要是boost.thread、libevent,log4cxx等,按照运行的模式,生成的代码中可能需用调用这些库。
三、Linux下安装
Thrift的安装包括上面提到生成代码生成器和应用框架库,网页(http://thrift.apache.org/docs/install/)描述了安装依赖项,除了gcc及其编译工具本身外,编译Thrift最大的依赖就是boost。安装过程并不复杂,请参阅相关网上文章。
四、Windows下Thrift的使用
将Windows环境单独拿出来讨论是因为以前的Thrift版本(0.8以前)是不支持Windows的,虽然有些人做了补丁,但看其文档,相当麻烦。0.8开始支持windows,目前官方文档的描述中还是需要Cygwin这样的东西。其实0.9.1已经能很好的支持windows了。
Thrift compiler的编译:在\compiler\cpp下有一个compiler.sln的VS2010的解决方案,它有一个叫做compiler的VC项目。可惜的是,要编译compiler,必须flex和bison的支持,这个可以到http://sourceforge.net/projects/winflexbison/?source=dlp下载,在VC的项目属性中,修改“生成事件->预先生成事件”中的命令行为(注意win_flex和win_bison的路径):
再将inttypes.h(网上下载)和thrifty.h(在上级目录)拷到src目录下,编译即可。上面的步骤也可手工完成,这样更保险些(可以参考compiler\cpp\README_Windows.txt,但其中有点小错误)。Thrift库的编译:在“\lib\cpp”目录,有一个叫thrift.sln的VS2010的解决方案文件,它包含两个VC项目:libthriftnb和libthrift,libthrift依赖于boost,libthriftnb依赖于boost和libevent,在正确设置好引用库后(先要编译生成boost和libevent),可以编译这两个工程,得到两个dll,即thrift的应用框架库,在thrift应用中,需要使用这个框架库。
五、Thrift基本概念与应用
这一部分很多文章已经涉及,本文只是从Thrift的White Paper角度加一些个人理解与注释。
1.Thrift有以下几个概念:
类型系统(typesystem)
Thrift定义了一套数据传输描述语言(有点类似IDL),它是“语言中性”的,这个就是它的类型系统。它分为五种类型(数据类型表达3种,预定义类/结构1种,接口表达1种):
基本类型(basictype),也就是bool、byte、i16、i32、i64、double、string,任何语言都有这些基本类型,比较有意思的是string,它即表达text,也表达binary bytes。另一个特点是整型没有unsigned,原因比较简单,因为有些语言不支持。
- 结构类型(struct):就是C语言中的struct,将基本类型组合起来。
- 容器类型(container):就是集合类型(list/set/map),其中的元素是任何Thrift可识别的基本、结构、容器类型。【不知道是否有不支持list/set/map的语言,那么Thrift如何处理呢?】
- 异常类型(exception):从数据结构讲就是结构类型,可以认为是便于异常的处理而单独拿出来的、预定义的、有特殊意义的结构类型。
- 服务定义类型(service):这个类型实际是用来定义接口的,Thrift代码生成器会根据这个定义,生成代码框架。
传输(transport)
也就是信息的传输渠道以及读写方式,例如,介质可以是socket、shared memory或file,Thrift规定了一些基本的操作(open/close/isOpen/read/write/flush,对server,再加上listen/accept)。特别的,针对Socket方式,有TScoket类,对file方式,有TFileTransport类,上面类比较底层,还有几个实用的类:TBufferedTransport,TFramedTransport,TMemoryBuffer等。
协议(protocol)
是对传输协议的封装,也就是传输采用二进制、XML或者text来表示信息,它的功能有两个:1.双向的消息队列;2.信息的编码和解码(也就是对上面类型的读/写)。关于流式格式,thrift数据类型是自我分割的,意思是说,thrift会自己在数据域的分割处插入标志,在解码的时候,即使没有数据域定义,thrift也能成功分割出各数据域。在若干篇文章中,都提到thrift的二进制流式编码有相当的效率(可以配合压缩),因此首选的协议应该是binary协议。
版本(versioning)
如果一个程序分开来开发,那版本问题就是绕不过去的问题。Thrift的版本是通过“field identifiers”来实现的,每个结构由其标识,结构中的每个域有其标识,这两个标识唯一决定了一个数据域。在解码的时候,数据域的标识被检查,如果不能识别,则该数据域被抛弃。Thrift也可以通过”Isset”机制来明确某些域的设置与否(发送端用来指明是否设置,接收端用来检测是否设置)。
四种情况:
- 添加了数据域, 旧客户端,新服务器端:客户端发送的数据中没有该域,服务器端能检测出来,可按缺省值处理。
- 删除了数据域, 旧客户端,新服务器端:客户端发送的数据中有该域,服务器端忽略该域。
- 添加了数据域, 新客户端,旧服务器端:客户端发送的数据中有该域,服务器端忽略该域。
- 删除了数据域, 新客户端,旧服务器端:客户端发送的数据中没有该域,服务器端可能不知道如何处理这种情况。
处理器(processor)
就是如何将各部分协调起来,形成代码(或用户代码的框架)。它有两个重要的类:TProcessor和TServer。TProcessor用来实现RPC调用,TServer是所有Server类的基类,TServer类主要处理连接和线程,而不管诸如传输、编码等。用户代码主要关注的一是.thrift文件,二就是这个接口。Thrift为此实现了TSimpleServer(单线程), TThreadedServer(每连接一个线程)和 TThreadPoolServer(线程池)等类。
下图是thrift生成代码的基本结构(C++)。
图中,ServiceIf是根据接口文件(.thrift)生成的虚接口类,用户的具体实现在ServiceHandler中。各种调用方式在TServer中实现。【详细的描述见实例】
2.Thrift实现上的几个考虑
目标语言
虽然有多种选择,但最常用的(可能也是支持最好的)是C++, Java, and Python。
生成的结构体
数据域成员都是公有的,没有set,get之类的东西,虽然建议采用isset,但也可以不用,系统足够强健来处理类似“FieldNotSetException”之类的问题,因而也没有涉及该异常。Read和write方面也是公有的,这样用户可以在固有的RPC之外来使用它们。
RPC方法标识:实现RPC时,建立函数名与函数指针之间的映射,大致如下(不同的语言表达方式不同,C++,map):
std::map<std::string,函数指针> processMap_;
这样加快函数调用。
多线程
对C++实现,在开发过程中,thrift开发人员研究过boost,ACE中与thread,timer相关的东西,开发人员不想引入过多的第三方依赖,因此thrift中只有对boost::shared_ptr的引用是必须的,但为了跨平台或获得更多的功能,一般情况下,boost中thread,timer及其依赖库也是需要的。
ThreadManager和TimerManager
线程管理类用来管理线程池,定时器管理类可以定时触发Runnable的对象,开启一件事情(可以放到或不放到一个单独线程)。
NonblockingOperation
这个东西需要libevent的支持。
Compiler(代码生成器)
这个东西是用C++写的,依赖于lex/yacc。代码生成分两步:第一,检查包含的文件和类型定义文件,生成“解析树”(the parse tree);第二,将各类型放到解析树中,根据解析树生成代码。
TFileTransport
这个类(及其继承类)可以将request消息记入文件,为提高性能,它先缓存记录,并存入磁盘。记录文件是分块的(文件固定大小),采用padding,记录不能跨块。
(未完)