(安西) 发表于 2015-8-5 11:03:26

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

Map from one type to another


list

Ordered list of one type


set

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 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 protocolFactory(new TBinaryProtocolFactory());
shared_ptr handler(new CalculatorHandler());
shared_ptr processor(new CalculatorProcessor(handler));
shared_ptr serverTransport(new TServerSocket(9090));
shared_ptr 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::newSimpleThreadManager(workerCount);
shared_ptr threadFactory =
shared_ptr(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 socket(new TSocket("localhost", 9090));
shared_ptr transport(new TBufferedTransport(socket));
shared_ptr 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)


参考资料


[*]Thrift Wiki IDL, http://wiki.apache.org/thrift/ThriftIDL
[*]Thrift Wiki Types, http://wiki.apache.org/thrift/ThriftTypes
  
页: [1]
查看完整版本: Apache Thrift