Apache Thrift

Thrift 是一个创建夸语言、可伸缩服务的框架。Thrift 最初由Facebook 研发,并捐献给Apache 以求更好的发展。Thrift 基于Apache 2.0 许可。

通过简单直接的接口定义语言(IDL,Interface Definition Language),Thrift 允许你用各种语言定义、创建服务。Thrift 使用代码生成的方式创建用于构建客户端/服务器的相关文件。除了交互性,Thrift 还拥有高效的序列化机制。

在Facebook,变成语言的选择,取决于你手头的工作。然而当这些程序要相互调用时,会产生很多问题!经过研究,Facebook 的工程师们没有找到任何现成的东西来满足它们所需要,跨语言、高效传输,而且简单!因此,Facebook 的工程师们开发了一些高效的协议和底层服务,这就是Thrift。Facebook 现在用Thrift 作为它们的后端服务!

本文将讨论如下内容:

  • Thrift 的架构
  • 支持的协议、传输 和服务器
  • .thrift文件说明
  • 创建一个Thrift 服务
  • 结论

Thrift 的架构

Thrift 包含一个创建客户端、服务器所需的完整的栈。如下图所示:

 

Thrift 之.thrift文件

.thrift文件的用途

.thrift文件存放着IDL(Interface Definition Language,接口定义语言),被代码生成器用来生成不同语言的框架代码。

 

IDL(Interface Definition Language)

注释

IDL支持类似C语言的注释

数据类型

bool   Boolean, one byte
byte   Signed byte
i16   Signed 16-bit integer
i32   Signed 32-bit integer
i64   Signed 64-bit integer
double   64-bit floating point value
string   String
binary   Blob (byte array)
map<t1,t2>   Map from one type to another
list<t1>   Ordered list of one type
set<t1>   Set of unique elements of one type

 

注意:没有单独的无符号类型,因为很多语言没有无符号类型

include

.thrift文件可以包含其它.thrift文件,例如

  • IDL代码
    # include "xxx.thrift"

    namespace

    用namespace来对于不同语言生成的代码中namespace/package等属性的值。例如:
  • IDL代码
    # namespace py shared.hello
    会生成如下python代码:
    import shared.hello
    其它的语言也类似。一个thrift文件中可以定义多个namespace,针对不同的语言类型来定制。

数据/类型定义

typedef和C语言一样,可以用typedef来指定自定义数据类型。

  • IDL代码
    typedef i32 MyInteger
    const 定义常量,常量一般所有字母大写
  • Idl代码
    const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}
    const i32 INT32CONSTANT = 9853
    enum 枚举类型背后对应的是32位整数,默认由1开始,和C类似
  • Idl代码
    enum Operation {
    ADD = 1,
    SUBTRACT = 2,
    MULTIPLY = 3,
    DIVIDE = 4
    }
    struct 结构体由field构成,每个field由整数标识符 、数据类型 、符号名 和可选的默认值 组成。 field可以被申明为optional,当它没有被设置值的时候,不会被序列化。
  • Idl代码
    struct Work {
    1: i32 num1 = 0,
    2: i32 num2,
    3: Operation op,
    4: optional string comment,
    }
    exception 异常和结构体类似
  • Idl代码
     exception InvalidOperation {
    1: i32 what,
    2: string why
    }

定义服务

  • Idl代码
     service SharedService {
    SharedStruct getStruct(1: i32 key)
    }

    service Calculator extends shared.SharedService {
    void ping(),
    i32 add(1:i32 num1, 2:i32 num2),
    i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),
    oneway void zip()
    }
    oneway标识符表示客户只需要发起一个请求,但是不用等待返回值。oneway方法的返回值必须是void。

生成代码框架

$thrift -gen cpp xxx.thrift

然后在gen-cpp目录下面就是服务相关的代码了,其它语言的也类似。

创建服务和客户端代码

服务器代码tutorial/cpp/CppServer.cpp

详细的代码请参看具体文件,http://svn.apache.org/repos/asf/incubator/thrift/trunk/tutorial/cpp/结构简单,有一个类CalculatorHandler实现了自动生成的代码中的CalculatorIf接口,用来处理请求。 看看其中一个服务接口的处理函数

int32_t add(const int32_t n1, const int32_t n2) {
printf("add(%d,%d)\n", n1, n2);
return n1 + n2;
}

简单就return就好。序列化、网络传输等其它事情会由上层thrift自己的模块来处理。 看看main函数,启动服务的代码

shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
shared_ptr<CalculatorHandler> handler(new CalculatorHandler());
shared_ptr<TProcessor> processor(new CalculatorProcessor(handler));
shared_ptr<TServerTransport> serverTransport(new TServerSocket(9090));
shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());

TSimpleServer server(processor,
serverTransport,
transportFactory,
protocolFactory);
server.serve();

可以通过这里的代码稍微窥视一下thrift的模块组成。 提供服务的实体是TSimpleServer,要启动它需要指定processor、TServerTransport、TTransportFactory和TProtocolFactory。

  • processort是服务的逻辑代码,与服务支持代码是完全分离的。
  • TServerTransport提供传输支持
  • TProtocolFactory负责和协议打交道。没错,thrift支持多种传输方式

此外,在代码里面还有一段被注释了的代码:

shared_ptr<ThreadManager> threadManager =
ThreadManager::newSimpleThreadManager(workerCount);
shared_ptr<PosixThreadFactory> threadFactory =
shared_ptr<PosixThreadFactory>(new PosixThreadFactory());
threadManager->threadFactory(threadFactory);
threadManager->start();
TThreadPoolServer server(processor,
serverTransport,
transportFactory,
protocolFactory,
threadManager);

TThreadedServer server(processor,
serverTransport,
transportFactory,
protocolFactory);

这个是多线程的版本。

客户端代码tutorial/cpp/CppClient.cpp

只看main函数的一部分就可以了

  shared_ptr<TTransport> socket(new TSocket("localhost", 9090));
shared_ptr<TTransport> transport(new TBufferedTransport(socket));
shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
CalculatorClient client(protocol);

transport->open();

client.ping();
printf("ping()\n");

int32_t sum = client.add(1,1);
printf("1+1=%d\n", sum);

用内置的TTransport连接上以后,就可以像调用本地方法一样调用远程方法了。

运行一下

启动服务器:

$./CppServer

启动客户端,发起请求,返回结果:

ping()
1+1=2
InvalidOperation: Cannot divide by 0
15-10=5
Check log: 5

同时在服务器的console下看到输出:

ping()
add(1,1)
calculate(1,{4,1,0})
calculate(1,{2,15,10})
getStruct(1)


参考资料

  1. Thrift Wiki IDL, http://wiki.apache.org/thrift/ThriftIDL
  2. Thrift Wiki Types, http://wiki.apache.org/thrift/ThriftTypes

 

posted @ 2011-10-28 13:28  残夜  阅读(1399)  评论(0编辑  收藏  举报