Thrift简介

  • 时间:2013-12-22
  • 方式:原创

Thrift 是什么?

Thrift源于大名鼎鼎的facebook之手,在2007年facebook提交Apache基金会将Thrift作为一个开源项目,对于当时的facebook来说创造thrift是为了解决facebook系统中各系统间大数据量的传输通信以及系统之间语言环境不同需要跨平台的特性。所以thrift可以支持多种程序语言,例如: C++, C#, Cocoa, Erlang, Haskell, Java, Ocami, Perl, PHP, Python, Ruby, Smalltalk. 在多种不同的语言之间通信thrift可以作为二进制的高性能的通讯中间件,支持数据(对象)序列化和多种类型的RPC服务。Thrift适用于程序对程 序静态的数据交换,需要先确定好他的数据结构,他是完全静态化的,当数据结构发生变化时,必须重新编辑IDL文件,代码生成,再编译载入的流程,跟其他IDL工具相比较可以视为是Thrift的弱项,Thrift适用于搭建大型数据交换及存储的通用工具,对于大型系统中的内部数据传输相对于JSON和xml无论在性能、传输大小上有明显的优势。

目前thrift支持的语言你可以在这里找到

一般来说,使用Thrift来开发应用程序,主要建立在两种场景下:

  • 第一,在我们开发过程中需要团队进行协作,而每个团队的成员在编程技术方面的技能可能不一定相同,为了实现这种跨语言的开发氛围,使用Thrift来构建服务
  • 第二,企业之间合作,在业务上不可避免出现跨语言的编程环境,使用Thrift可以达到类似Web Services的跨平台的特性

Thrift支持的传输格式如下:

1)支持的传输格式

  • TBinaryProtocol – 二进制格式.
  • TCompactProtocol – 压缩格式
  • TJSONProtocol – JSON格式
  • TSimpleJSONProtocol –提供JSON只写协议, 生成的文件很容易通过脚本语言解析。
  • TDebugProtocol – 使用易懂的可读的文本格式,以便于debug

2)支持的数据传输方式

  • TSocket -阻塞式socker
  • TFramedTransport – 以frame为单位进行传输,非阻塞式服务中使用。
  • TFileTransport – 以文件形式进行传输。
  • TMemoryTransport – 将内存用于I/O. java实现时内部实际使用了简单的ByteArrayOutputStream。
  • TZlibTransport – 使用zlib进行压缩, 与其他传输方式联合使用。当前无java实现。

3)支持的服务模型

  • TSimpleServer – 简单的单线程服务模型,常用于测试
  • TThreadPoolServer – 多线程服务模型,使用标准的阻塞式IO。
  • TNonblockingServer – 多线程服务模型,使用非阻塞式IO(需使用TFramedTransport数据传输方式)

Thrift 使用

下载并安装:

1.你可以在这里找到thrift最新的下载包.
wget http://mirror.bit.edu.cn/apache/thrift/0.9.1/thrift-0.9.1.tar.gz
2.在下载完成后.你可以按如下步骤来安装Thrift.

Thrift是基于C++编写的.在安装前你可能需要一些基础的环境.可以参考centos下的安装命令

sudo yum install automake libtool flex bison pkgconfig gcc-c++ boost-devel libevent-devel zlib-devel python-devel ruby-devel openssl-devel

在基础环境安装完成后可以按如下方法安装

tar zxvf thrift-0.9.1.tar.gz 
cd thrift-0.9.1/
./configure
make
make install
3.Thrift安装完成后.可以开始编写 .thrift的文件.

.thrift的文件的编写语法遵守IDL(Thrift interface description language).IDL允许以Thfirt的格式来定义变量等.Thfite的变量目前有如下几种.你还可以在这里这里找到最新的说明

3.1格式说明

1) 基本类型

bool:布尔类型(true or value),占一个字节
byte:有符号字节
i16:16位有符号整型
i32:32位有符号整型
i64:64位有符号整型
double:64位浮点数
string:未知编码或者二进制的字符串

注意,thrift不支持无符号整型,因为很多目标语言不存在无符号整型(如java)。

2) 容器类型

Thrift容器与类型密切相关,它与当前流行编程语言提供的容器类型相对应,采用java泛型风格表示的。Thrift提供了3种容器类型:

List<t1>:一系列t1类型的元素组成的有序表,元素可以重复
Set<t1>:一系列t1类型的元素组成的无序表,元素唯一
Map<t1,t2>:key/value对(key的类型是t1且key唯一,value类型是t2)。

容器中的元素类型可以是除了service意外的任何合法thrift类型(包括结构体和异常)。

3) 枚举和结构体

Thrift结构体在概念上同C语言结构体类型—-一种将相关属性聚集(封装)在一起的方式。在面向对象语言中,thrift结构体被转换成类。 结构体由一系列域组成,每个域有唯一整数标识符,类型,名字和可选的缺省参数组成。如:

enum TweetType {
    TWEET,          #编译器默认从0开始赋值
    RETWEET = 2,    #可以赋予某个常量某个整数
    DM = 0xa,       #允许常量是十六进制整数
    REPLY
}                   #末尾没有逗号

struct Tweet {
    1:  required i32 userId;                            # 每个域有一个唯一的,正整数标识符
    2:  required string userName;                       
    3:  required string text;                           # 每个域可以标识为required或者optional(也可以不注明)
    4:  optional Location loc;
    5:  optional TweetType tweetType = TweetType.TWEET   #结构体可以包含其他结构体,域可以有缺省值, 给常量赋缺省值时,使用常量的全称
    16: optional string language = "english"
}

规范的struct定义中的每个域均会使用required或者optional关键字进行标识。如果required标识的域没有赋值,thrift将给予提示。如果optional标识的域没有赋值,该域将不会被序列化传输。如果某个optional标识域有缺省值而用户没有重新赋值,则该域的值一直为缺省值。 结构体不支持继承,即,一个结构体不能继承另一个结构体。

4) 服务 服务的定义方法在语法上等同于面向对象语言中定义接口。Thrift编译器会产生实现这些接口的client和server桩。

#“Twitter”与“{”之间需要有空格!!!
service Twitter {
    # 方法定义方式类似于C语言中的方式,它有一个返回值,一系列参数和可选的异常
    # 列表. 注意,参数列表和异常列表定义方式与结构体中域定义方式一致.

    void ping(),                                    # 函数定义可以使用逗号或者分号标识结束
    bool postTweet(1:Tweet tweet);                  # 参数可以是基本类型或者结构体,参数是只读的(const),不可以作为返回值!!!
    TweetSearchResult searchTweets(1:string query); # 回值可以是基本类型或者结构体

    # ”oneway”标识符表示client发出请求后不必等待回复(非阻塞)直接进行下面的操作,
    # ”oneway”方法的返回值必须是void
    oneway void zip()                               #返回值可以是void
}

Service支持继承,一个service可使用extends关键字继承另一个service

5) 类型定义 Thrift支持C/C++风格的typedef:

typedef i32 MyInteger  //末尾没有逗号
typedef Tweet ReTweet  //struct可以使用typedef

说明:

6) 注释 # This is a valid comment.

/*
* This is a multi-line comment.
* Just like in C.
*/

7) 命名空间 Thrift中的命名空间同C++中的namespace和java中的package类似,它们均提供了一种组织(隔离)代码的方式。因为每种语言均有自己的命名空间定义方式(如python中有module),thrift允许开发者针对特定语言定义namespace:

namespace cpp com.example.project  #转化成namespace com { namespace example { namespace project {
namespace java com.example.project #转换成package com.example.project

8) 文件包含 Thrift允许thrift文件包含,用户需要使用thrift文件名作为前缀访问被包含的对象,如:

include "tweet.thrift"           #thrift文件名要用双引号包含,末尾没有逗号或者分号

struct TweetSearchResult {
    1: list<tweet.Tweet> tweets; #注意tweet

}

9) 常量 Thrift允许用户定义常量,复杂的类型和结构体可使用JSON形式表示。

const i32 INT_CONST = 1234;    # 分号是可选的,可有可无;支持十六进制赋值。
const map<string,string> MAP_CONST = {"hello": "world", "goodnight": "moon"}
3.2生成thrift文件
thrift --gen <language> <Thrift filename>

如: thrift --gen go hello.thrift
使用

以一个简单的helloword来演示如何使用thrift.

1.创建文件

struct Print {
    1: string str,
}

struct Result {
    1: bool isOk,
}

service SayHello {
    Result say(1: Print str)
}

另存为hello.thrift

并且运行.thrift -gen go hello.thrift 会生成如下文件
gen-go/
    hello/
        constants.go
        say_hello.go
        ttypes.go
        say_hello-remote/
            say_hello-remote.go

其中constants.go定义一些常量,say_hello.go中实现了一些和协议相关的方法.而ttypes.go中是类型相关的方法.
say_hello-remote.go是自带的demo

2.服务端:

package main

import (
    "fmt"
    "git.apache.org/thrift.git/lib/go/thrift"
    "hello"
    "os"
)

const (
    NetworkAddr = "127.0.0.1:19090"
)

//自己实实现的服务端接口
type helloclient struct {
}
//自己实实现的服务端接口处理
func (p *helloclient) Say(str *hello.Print) (r *hello.Result, err error) {
    fmt.Println(str.Str)
    return &hello.Result{true}, nil
}

func main() {
    //这里用的是最简单的传输方法.还有很多其他的方法可以参考官方.
    transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
    protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
    //protocolFactory := thrift.NewTCompactProtocolFactory()

    serverTransport, err := thrift.NewTServerSocket(NetworkAddr)
    if err != nil {
        fmt.Println("Error!", err)
        os.Exit(1)
    }

    handler := &helloclient{}
    processor := hello.NewSayHelloProcessor(handler)

    server := thrift.NewTSimpleServer4(processor, serverTransport, transportFactory, protocolFactory)
    fmt.Println("thrift server in", NetworkAddr)
    server.Serve()
}

3.客户端:

package main

import (
    "fmt"
    "git.apache.org/thrift.git/lib/go/thrift"
    "hello"
    "net"
    "os"
    "time"
)

func main() {
    startTime := currentTimeMillis()
    transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
    protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()

    transport, err := thrift.NewTSocket(net.JoinHostPort("127.0.0.1", "19090"))
    if err != nil {
        fmt.Fprintln(os.Stderr, "error resolving address:", err)
        os.Exit(1)
    }

    useTransport := transportFactory.GetTransport(transport)
    client := hello.NewSayHelloClientFactory(useTransport, protocolFactory)
    if err := transport.Open(); err != nil {
        fmt.Fprintln(os.Stderr, "Error opening socket to 127.0.0.1:19090", " ", err)
        os.Exit(1)
    }
    defer transport.Close()

    for i := 0; i < 1000; i++ {
        str := hello.Print{"str"}
        fmt.Println(str)
        result, err := client.Say(&str)
        if err == nil {
            fmt.Println(i, "Call->", result)
        }

    }

    endTime := currentTimeMillis()
    fmt.Println("Program exit. time->", endTime, startTime, (endTime - startTime))
}

// 转换成毫秒
func currentTimeMillis() int64 {
    return time.Now().UnixNano() / 1000000
}

自此.一个简单的thrift的helloword程序就OK了.当然.我们也可以使用thrift的文件来生成其他的语言的代码.并且和我们的项目进行整合.