简述thrift与应用分析
阅读原文时间:2021年04月26日阅读:1

前言

  本篇将以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

thrift 类型

Base Types

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.

这里就是基本类型都有的。

Special Types

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。

Structs

c中的struct类似的,不过不支持自身的循环嵌套功能,也就是不支持如下定义

struct node{

1:i32    v;

2:node next;

}

在thrift文件中定义出来的struct在代码生成的时候,使用class来描述的(以类形式提供结构体的功能)。

Containers

thrift提供了容器的概念,set,map,list这三个thrift的类型都是属于容器。

对应到c++中就是set,map,vector。对应到java中就是HashSet,ArrayList,HashMap。

Exceptions

异常功能上和struct差不多,不过在这基础之是从各自语言的异常基类中继承来的,可以用于在service中指定异常。

Services 

service和java中的interface比较像,或者c++中的public接口。service 定义可以提供什么接口,之后再服务端实现这些接口,客户端就可进行调用。

thrift IDL

IDL就是接口定义语言,在thrift中就是,通过IDL可以定义thrift类型,thrift的代码生成器可以在指定的IDL 文件定义thrift类型,结构来生成各种目标语言的代码。

这其实和protobuf很像,protobuf根据定义的.proto文件,使用代码生成器生成指定语言的代码,之后就可以用生成的代码进行序列化反序列化。

thrift网络协议栈

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 文件编写

//下面介绍的.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命令可以用来对.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要好一点,另外更详细开销之类的对比可以看这里

hdfs中的thrift使用场景

以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中的thrift使用场景

    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。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章