本篇将以thrift-0.9.0为背景讲述thrift的基础,使用案例,启发。
thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa,JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务。
thrift最初由facebook开发,07年四月开放源码,08年5月进入apache孵化器。
thrift允许你定义一个简单的定义文件中的数据类型和服务接口。以作为输入文件,编译器生成代码用来方便地生成RPC客户端和服务器通信的无缝跨编程语言。
简单来说,就是提供了rpc框架,序列化功能,多语言的代码生成等功能,跨语言间的协调开发就是基于这些功能来完成。
首先,关于编译前,各语言对应的库的需求可以见这里
boost和libevent都是编译过没有安装的,--with-libevent指定可能有点问题,就换成了在编译时指定路径。
boost可能有依赖到库目录,就指定下库所在的路径,libevent也指定了一下。
另外,如果不需要非阻塞功能的,可以不用去指定libevent相关头文件目录和库目录。
./configure --with-boost=/home/chlaws/packet/boost_1_47_0 --with-qt4=no \
CPPFLAGS="-I /home/chlaws/packet/libevent-2.0.21-stable -I /home/chlaws/packet/libevent-2.0.21-stable/include" \
CXXFLAGS="-I /home/chlaws/packet/libevent-2.0.21-stable -I /home/chlaws/packet/libevent-2.0.21-stable/include" \
LDFLAGS="-L/home/chlaws/packet/boost_1_47_0/stage/lib-L/home/chlaws/packet/libevent-2.0.21-stable/.libs"
make时候,在编译java的库的时候,可能会出现编译会出现有方法找不到的异常,这个我自己是有碰到
在thrift-0.9.0/lib/java中的Makefile中的
all-local:
$(ANT) $(ANT_FLAGS)
改为
all-local:
sudo $(ANT) $(ANT_FLAGS)
或者进入到lib/java以root权限执行sudoant
如果都没有这问题以及boost安装在默认目录了(/usr/local下),那就简单点,./configure ;make;make install;
另外编译好了,自己验证下:
进入thrift-0.9.0/tutorial 目录执行 thrift -r --gencpp tutorial.thrift
进入thrift-0.9.0/tutorial/cpp 执行Makefile看看是否成功,这里我自己会碰到基础类型没定义和htons之类的没定义的错误
参看thrift wiki上的usage ,在Makefile的编译选项中 指定-DHAVE_INTTYPES_H-DHAVE_NETINET_IN_H (注:其实就是config.h的关系)
下面是我改过的Makefile, 我boost没有安装因此boost目录需要改一下默认的路径
BOOST_DIR = /home/chlaws/packet/boost_1_47_0 THRIFT_DIR = /usr/local/include #LIB_DIR = -L /usr/local/lib GEN_SRC = ../gen-cpp/SharedService.cpp../gen-cpp/shared_types.cpp ../gen-cpp/tutorial_types.cpp ../gen-cpp/Calculator.cpp default: server client server: CppServer.cpp g++-DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H -o CppServer -I${THRIFT_DIR}-I${BOOST_DIR} -I../gen-cpp ${LIB_DIR} -lthrift CppServer.cpp${GEN_SRC} client: CppClient.cpp g++-DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H -o CppClient -I${THRIFT_DIR}-I${BOOST_DIR} -I../gen-cpp ${LIB_DIR} -lthrift CppClient.cpp ${GEN_SRC} clean: $(RM) -rCppClient CppServer
The base types were selected with the goal of simplicityand clarity rather than abundance, focusing on the key types available in allprogramming languages.
· bool: Aboolean value (true or false)
· byte: An8-bit signed integer
· i16: A16-bit signed integer
· i32: A32-bit signed integer
· i64: A64-bit signed integer
· double: A64-bit floating point number
· string: Atext string encoded using UTF-8 encoding
Note the absence of unsigned integer types. This is dueto the fact that there are no native unsigned integer types in many programminglanguages.
这里就是基本类型都有的。
binary: a sequence of unencoded bytes
N.B.: This is currently a specialized form of the stringtype above, added to provide better interoperability with Java. The currentplan-of-record is to elevate this to a base type at some point.
还有一个c++中没有的这个对应类型的 binary。
c中的struct类似的,不过不支持自身的循环嵌套功能,也就是不支持如下定义
struct node{
1:i32 v;
2:node next;
}
在thrift文件中定义出来的struct在代码生成的时候,使用class来描述的(以类形式提供结构体的功能)。
thrift提供了容器的概念,set,map,list这三个thrift的类型都是属于容器。
对应到c++中就是set,map,vector。对应到java中就是HashSet,ArrayList,HashMap。
异常功能上和struct差不多,不过在这基础之是从各自语言的异常基类中继承来的,可以用于在service中指定异常。
service和java中的interface比较像,或者c++中的public接口。service 定义可以提供什么接口,之后再服务端实现这些接口,客户端就可进行调用。
IDL就是接口定义语言,在thrift中就是,通过IDL可以定义thrift类型,thrift的代码生成器可以在指定的IDL 文件定义thrift类型,结构来生成各种目标语言的代码。
这其实和protobuf很像,protobuf根据定义的.proto文件,使用代码生成器生成指定语言的代码,之后就可以用生成的代码进行序列化反序列化。
Transport传输层提供简单的抽象用于对网络的读写。
Protocol 协议指定在网络中以什么样的数据格式进行传输。
Processor 处理器允许同流中读取数据和将数据写到流中,数据输出流是通过协议对象来解释的。
Server 就是将上诉组件一起组合使用,以对外提供访问。
具体对各组件的解释可见这里
+-------------------------------------------+
| Server |
| (single-threaded, event-driven etc) |
+-------------------------------------------+
| Processor |
| (compiler generated) |
+-------------------------------------------+
| Protocol |
| (JSON, compact etc) |
+-------------------------------------------+
| Transport |
| (raw TCP, HTTP etc) |
+-------------------------------------------+
首先,thrift解压开后目录格式是如下所示
thrift/
compiler/
Contains the Thrift compiler, implementedin C++.
thrift编译器的c++实现
lib/
Contains the Thrift software libraryimplementation, subdivided by
language of implementation.
thrift的各语言的库实现
cpp/
java/
php/
py/
rb/
test/
Contains sample Thrift files and test codeacross the target programming
languages.
包含.thrift文件和各语言的测试代码
tutorial/
Contains a basic tutorial that will teachyou how to develop software
using Thrift.
包含最基本的使用,用于告知初用者如何使用thrift开发。建议没用过thrift的,先看看该目录下的.thrift定义和各语言的使用。
//下面介绍的.thrift文件是在tutorial目录下的tutorial.thrift,将简单注释介绍下各定义是什么意思。
include "shared.thrift" //这里是包含另外的.thrift文件, /** * Thrift files can namespace, package, or prefixtheir output in various * target languages. */ //namespace 和java的package和c++的namespace差不多,后面跟的是语言的类型,最后tutorial是一个标识名。 namespace cpp tutorial namespace d tutorial namespace java tutorial namespace php tutorial namespace perl tutorial /** * Thrift lets you do typedefs to get pretty namesfor your types. Standard * C style here. */ typedef i32 MyInteger //可以有typedef进行别名定义,和c++中的差不多 /** * Thrift also lets you define constants for useacross languages. Complex * types and structs are specified using JSONnotation. */ const i32 INT32CONSTANT = 9853 //定义常量 //thrift提供了容器map,map在.thrift中可以初始化,初始化的形式和python中一样。 const map<string,string> MAPCONSTANT ={'hello':'world', 'goodnight':'moon'} /** * You can define enums, which are just 32 bitintegers. Values are optional * and start at 1 if not supplied, C style again. */ //thrift中提供了枚举类型,和c++,java中差不多。 enum Operation { ADD = 1, SUBTRACT = 2, MULTIPLY = 3, DIVIDE = 4 } /** * Structs are the basic complex data structures.They are comprised of fields * which each have an integer identifier, a type, asymbolic name, and an * optional default value. * * Fields can be declared "optional",which ensures they will not be included * in the serialized output if they aren't set. Note that this requires some * manual management in some languages. */ //也提供了结构体,在结构体中定义依次需要定义的序号,即1: aa,2:bb, struct Work { 1: i32 num1 = 0, 2: i32 num2, 3: Operation op, 4: optional string comment, } /** * Structs can also be exceptions, if they arenasty. */ exception InvalidOperation { 1: i32 what, 2: string why } /** * Ahh, now onto the cool part, defining a service.Services just need a name * and can optionally inherit from another serviceusing the extends keyword. */ service就是个接口的定义,需要在生成代码后在server中实现这些接口。同时,server还提供了继承,可以从另外一个service中继承接口,继承的关键字是用的java中的extend。另外thrift对于service有一些要求,如果继承另外接口,那么不能有重名的接口名(也就是oop中的重载),还有就是,thrift不提供多态的概念。 service Calculator extends shared.SharedService { /** * A method definition looks like C code. Ithas a return type, arguments, * and optionally a list of exceptions thatit may throw. Note that argument * lists and exception lists are specifiedusing the exact same syntax as * field lists in struct or exceptiondefinitions. */ void ping(), i32 add(1:i32 num1, 2:i32 num2), i32 calculate(1:i32 logid, 2:Work w) throws(1:InvalidOperation ouch), /** * This method has a oneway modifier. Thatmeans the client only makes * a request and does not listen for anyresponse at all. Oneway methods * must be void. */ oneway void zip() }
在编译安装后,会有thrift命令可以用来对.thrift进行生成指定语言的代码,然后对于service进行实现,启动网络进行封装,就可对外提供服务。
以tutorial下的例子为例,先用thrift -r --gen 语言
语言可以是c++,java,python等等,具体可以用thrift --help查看帮助
c++ (thrift-0.9.0/tutorial/cpp的实现例子)
在生成代码后,可以从gen-cpp中copy Calculator_server.skeleton.cpp文件到其他目录,重命名下,比如CppServer.cpp
在其中实现接口(应为还include了shared.thrift,在内部有getStruct方法也要在类中实现),另外在main中封装server,代码如下:
int main(int argc, char **argv) { shared_ptr<TProtocolFactory>protocolFactory(new TBinaryProtocolFactory()); shared_ptr<CalculatorHandler> handler(newCalculatorHandler()); shared_ptr<TProcessor> processor(newCalculatorProcessor(handler)); shared_ptr<TServerTransport>serverTransport(new TServerSocket(9090)); shared_ptr<TTransportFactory>transportFactory(new TBufferedTransportFactory()); TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory); printf("Starting the server...\n"); server.serve(); printf("done.\n"); return 0; }
编译后,一个RPCserver就完成了。
client的话,可以直接看下提供的client的cpp的实现。
int main(int argc, char** argv) { shared_ptr<TTransport> socket(newTSocket("localhost", 9090)); shared_ptr<TTransport> transport(newTBufferedTransport(socket)); shared_ptr<TProtocol> protocol(newTBinaryProtocol(transport)); //thrift 在生成serviceCalculator对应的代码的同时也会生成CalculatorClient, //这样客户端只要在建立连接之后open下,就可正常调用函数样调用server上实现的同名方法。 CalculatorClient client(protocol); try { transport->open(); client.ping(); printf("ping()\n"); int32_t sum = client.add(1,1); printf("1+1=%d\n", sum); Work work; work.op = Operation::DIVIDE; work.num1 = 1; work.num2 = 0; try { int32_t quotient =client.calculate(1, work); printf("Whoa? We can divide byzero!\n"); } catch (InvalidOperation &io) { printf("InvalidOperation:%s\n", io.why.c_str()); } work.op = Operation::SUBTRACT; work.num1 = 15; work.num2 = 10; int32_t diff = client.calculate(1, work); printf("15-10=%d\n", diff); // Note that C++ uses return by referencefor complex types to avoid // costly copy construction SharedStruct ss; client.getStruct(ss, 1); printf("Check log: %s\n",ss.value.c_str()); //断开连接 transport->close(); } catch (TException &tx) { printf("ERROR: %s\n", tx.what()); } }
thrift 提供多语言的支持,RPC,序列化等功能。
protobuf 提供了序列化功能。
avro 和thrift类似,但多了动态动态类型。
压缩率的话,protobuf要好一点,另外更详细开销之类的对比可以看这里
以hadoop-1.1.2为例,\hadoop-1.1.2\src\contrib\thriftfs\if中定义了thrift的接口,其中service如下
service ThriftHadoopFileSystem { // set inactivity timeout period. The period isspecified in seconds. // if there are no RPC calls to the HadoopThriftserver for this much // time, then the server kills itself. void setInactivityTimeoutPeriod(1:i64periodInSeconds), // close session void shutdown(1:i32 status), // create a file and open it for writing ThriftHandle create(1:Pathname path) throws(1:ThriftIOException ouch), // create a file and open it for writing ThriftHandle createFile(1:Pathname path, 2:i16mode, 3:bool overwrite, 4:i32 bufferSize, 5:i16 block_replication, 6:i64blocksize) throws (1:ThriftIOException ouch), // returns a handle to an existing file forreading ThriftHandle open(1:Pathname path) throws(1:ThriftIOException ouch), // returns a handle to an existing file forappending to it. ThriftHandle append(1:Pathname path) throws(1:ThriftIOException ouch), // write a string to the open handle for the file bool write(1:ThriftHandle handle, string data)throws (1:ThriftIOException ouch), // read some bytes from the open handle for thefile string read(1:ThriftHandle handle, i64 offset, i32size) throws (1:ThriftIOException ouch), // close file bool close(1:ThriftHandle out) throws(1:ThriftIOException ouch), // delete file(s) or directory(s) bool rm(1:Pathname path, 2:bool recursive) throws(1:ThriftIOException ouch), // rename file(s) or directory(s) bool rename(1:Pathname path, 2:Pathname dest)throws (1:ThriftIOException ouch), // create directory bool mkdirs(1:Pathname path) throws(1:ThriftIOException ouch), // Does this pathname exist? bool exists(1:Pathname path) throws(1:ThriftIOException ouch), // Returns status about the path FileStatus stat(1:Pathname path) throws(1:ThriftIOException ouch), // If the path is a directory, then returns thelist of pathnames in that directory list<FileStatus> listStatus(1:Pathname path)throws (1:ThriftIOException ouch), // Set permission for this file void chmod(1:Pathname path, 2:i16 mode) throws(1:ThriftIOException ouch), // set the owner and group of the file. void chown(1:Pathname path, 2:string owner,3:string group) throws (1:ThriftIOException ouch), // set the replication factor for all blocks ofthe specified file void setReplication(1:Pathname path, 2:i16replication) throws (1:ThriftIOException ouch), // get the locations of the blocks of this file list<BlockLocation> getFileBlockLocations(1:Pathnamepath, 2:i64 start, 3:i64 length) throws (1:ThriftIOException ouch), }
从service上可以看出只是提供了简单的文件读写接口,再看实现,在hadoop-1.1.2\src\contrib\thriftfs\src\java\org\apache\hadoop\thriftfs中实现了ThriftHadoopFileSystem,其中构造函数如下
public HadoopThriftHandler(String name) { conf = new Configuration(); now = now(); try { inactivityThread = newDaemon(new InactivityMonitor()); fs =FileSystem.get(conf); } catch (IOException e) { LOG.warn("Unable to openhadoop file system..."); Runtime.getRuntime().exit(-1); } }
其中fs = FileSystem.get(conf);这里会根据配置中指定的fs.default.name是hdfs://开头的,而确定fs是 DistributedFileSystem对象。
在ThriftHadoopFileSystem内部可以其他文件操作接口的具体实现,都可以看到时调用的DistributedFileSystem的接口。
因此,可以知道,hdfs对于多语言的支持是通过独立提供一个thrift server(达到和hdfs内部协议分离),在server端通过java实现底层调用的是真正的HDFS客户端接口,客户端只要是thrift支持的语言,就可以与thrift server通信,从而达到对hdfs的文件操作。
storm在storm-0.8.2-src\storm-0.8.2\src中有一个storm.thrift,内部定义的比较长,在thrift文件中定义了3个service,分别是
service Nimbus,serviceDistributedRPC,service DistributedRPCInvocations。需要注意的是storm使用的thrift0.7的版本,
我自己用0.9的thrift生成了下java代码,发现storm-0.8.2-src\storm-0.8.2\src\jvm\backtype\storm\generated和一样,但storm没有用
java去实现这些接口,而是用clojure在核心代码中去完成对接口的实现,从而可以知道,storm和外部多语言的支持是基于thrift的RPC。
了解过thrift后,可以就很清楚的知道了storm的协议,方便了对storm的源码分析。
hdfs和storm都是用thrift作为多语言的支持,但使用的形式不同,对于我们自己的云存储以后对多语言的支持可以考虑使用hdfs类似的方式,
这样内部协议和外部thrift的协议分开,内部协议的改动都只局限在集群内部和Client,而thrift的server只要类似hdfs这样在server内部调用真正的接口就ok。