本篇将以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。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章