Thrift简介

The Apache Thrift software framework, for scalable cross-language services development, combines a software stack with a code generation engine to build services that work efficiently and seamlessly between C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi and other languages. http://thrift.apache.org

通过官方介绍,我们可以了解到Thrift是一个软件框架,可以提供跨语言的服务开发。Thrift框架包含一个软件栈,包含生成各种语言的引擎,我们通过Thrift提供的接口定义语言(IDL)来定义接口,然后引擎会自动生成各种语言的代码。

 

Thrift最初是由Facebook研发,主要用于各服务之间的RPC通信,支持跨语言,常用的语言比如C++、Java、Python、PHP、Ruby、Erlang、Perl、Haskell、C#、Cocoa、JavaScript、Node.js、Smalltalk、 OCaml and Delphi and other languages

 

Thrift是一个典型的C/S(客户端/服务端)结构,客户端和服务端可以使用不同的语言开发。既然客户端和服务端可以使用不同的语言开发,那么一定就要有一种中间语言来关联客户端和服务端的语言,这种语言就是IDL(Interface Definition Language)。

Thrift架构

image

Thrift传输格式 图中,TProtocol(协议层),定义数据传输格式,例如:

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

Thrift数据传输方式 TTransport(传输层),定义数据传输方式,可以为TCP/IP传输,内存共享或者文件共享等)被用作运行时库。

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

Thrift支持的服务模型

  • TSimpleServer:简单的单线程服务模型,常用于测试;
  • TThreadPoolServer:多线程服务模型,使用标准的阻塞式IO;
  • TNonblockingServer:多线程服务模型,使用非阻塞式IO(需使用TFramedTransport数据传输方式);
  • THsHaServer:THsHa引入了线程池去处理,其模型把读写任务放到线程池去处理;HaIf-sync/HaIf-async的处理模式,HaIf-async是在处理IO事件上(accept/read/write io),HaIf-sync用于handler对rpc的同步处理。

Thrift实际上是实现了C/S模式,通过代码生成工具将thrift文生成服务器端和客户端代码(可以为不同语言),从而实现服务端和客户端跨语言的支持。用户在Thirft文件中声明自己的服务,这些服务经过编译后会生成相应语言的代码文件,然后客户端调用服务,服务器端提服务便可以了。

 

一般将服务放到一个.thrift文件中,服务的编写语法与C语言语法基本一致,在.thrift文件中有主要有以下几个内容:变量声明(variable)、数据声明(struct)和服务接口声明(service, 可以继承其他接口)。

Thrift数据类型

  • Thrift不支持无符号类型,因为很多编程语言不存在无符号类型,比如Java。
  • byte:有符号字节
  • i16:16位有符号整数
  • i32:32位有符号整数
  • i64:64位有符号整数
  • dubbo:64位有浮点数
  • string:字符串
  • bool: 布尔值 (true or false), one byte
  • 特殊类型(括号内为对应的Java类型):binary(ByteBuffer):未经过编码的字节流

Thrift容器类型

  • 集合中的元素可以是除了service之外的任何类型,包括exception
  • list<T>:一系列由T类型的数据组成的有序列表,元素可以重复
  • set<T>:一系列由T类型的数据组成的无序集合,元素不可以重复
  • map<K,V>:一个字典结构,key为K类型,value为V类型,相当于Java中的HashMap

Thrift工作原理

  • 如何实现多语言的通信?
  • 数据传输使用socket(多种语言均支持),数据在以特定的格式(String等)发送,接收方语言进行解析
  • 定义thrift的文件,由thrift文件(IDL)生成双方语言的接口、model,在生成的model以及接口中会有解码编码的代码。

Thrift IDL文件

namespace java com.example.project
struct News{
    1:i32 id;
    2:string title;
    3:string content;
    4:string mediaFrom;
    5:string author;
}

service IndexNewsOperatorServices{
    bool indexNews(1:NewsModel indexNews),
    bool removeNewsById(1:i32 id)
}

结构体(struct)

  • 就像C语言一样,Thrift 支持struct 类型,目的就是将一些数据聚合在一起,方便传输管理。struct 的定义形式如下:
struct Peopel{
    1:string name;
    2:i32 age;
    3:string gender;
}

枚举(Enums)

  • 枚举的定义形式和Java的Enum定义类似:
enum Gender{
    MALE,       
   FEMALE  
}     

异常(exception)

  • Thrift 支持自定义exception,规则与struct 一样。
exception RequestException{
    1:i32 code;
    2:string reason;
}

服务(service)

  • Thrift 定义服务相当于Java中创建Interface一样,创建的service经过代码生成命令之后,就会生成客户端和服务端的框架代码,定义形式如下:
service HelloWorldService{
    //service中定义的函数,相当于Java interface中定义的方法
    string doAction(1:string name,2:i32 age);
}

类型定义

  • Thrift 支持类型C++一样的typedef定义:
typedef i32 int
typedef i64 long

常量(const)

  • Thrift 也支持常量定义,使用const关键字:
const i32 MAX_RETRIES_TIME = 10;
const string MY_WEBSITE = "http://facebook.com"

命名空间

  • Thrift 的命名空间相当于Java中的package的意思,主要目的是组织代码。Thrift使用关键字namespace定义命名空间:
namespace java com.test.thrift.demo
  • 格式是:namespace 语言名 路径

文件包含

  • Thrift 也支持文件包含,相当于C/C++中的include,Java中的import,使用关键字include定义:
include "global.thrift"

注释

  • Thrift 注释方式支持shell风格的注释,支持C/C++风格的注释,即#和//开头的语句都当做注释,/**/包裹的语句也是注释。

可选与必选

  • Thrift 提供两个关键字required、optional,分别用于表示对应的字段是必填的还是可选的
struct Peopel{
    1:required string name;
    2:optional i32 age;
}

Windows平台下Thrift安装与使用

下载与安装:http://thrift.apache.org/download image.png

下载thrift-0.12.0.exe文件命名为thrift .exe放在D盘下的一个thtift文件夹中。

建议:修改名称为thrift.exe,方便使用

建议:配置环境变量 在系统变量Path中添加thrift路径(D:\thrift)到环境变量中

这样就可以在dos窗口来使用thrift命令了,”thrift -version ”

image.png

生成代码

  • 了解了如何定义thrift文件之后,我们需要用定义好的thrift文件生成我们需要的目标语言的源码
  • 首先需要定义thrift接口描述文件,如data.thrift
namespace java thrift.generated

typedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string String

struct Person{
    1: optional String username,
    2: optional int age,
    3: optional boolean married
}

exception DataException{
    1: optional String message,
    2: optional String callStack,
    3: optional String data
}

service PersonService{
    Person getPersonByUsername(1: required String username) throws (1: DataException dataException),

    void savePerson(1: required Person person) throws (1: DataException dataException)
}
  • 生成代码,在idea控制台上输入:thrift --gen java src/thrift/data.thrift image.png

  • 引入maven或gradle依赖

//maven依赖
<dependency>
    <groupId>org.apache.thrift</groupId>
    <artifactId>libthrift</artifactId>
    <version>0.12.0</version>
</dependency>

//gradle依赖
compile group: 'org.apache.thrift', name: 'libthrift', version: '0.12.0'

实现Thrift定义的service

/**
 * @author: huangyibo
 * @Date: 2019/3/13 23:38
 * @Description: 实现Thrift定义的服务接口
 */
public class PersonServiceImpl implements PersonService.Iface {

    @Override
    public Person getPersonByUsername(String username) throws DataException, TException {
        System.out.println("Got client Param:" + username);
        Person person = new Person();
        person.setUsername(username);
        person.setAge(20);
        person.setMarried(false);
        return person;
    }

    @Override
    public void savePerson(Person person) throws DataException, TException {
        System.out.println("Got client Param:" + person);
        System.out.println(person.getUsername());
        System.out.println(person.getAge());
        System.out.println(person.isMarried());
    }
}

Thrift服务端实现

public class ThriftServer {
    public static void main(String[] args) throws TTransportException {
        //非阻塞socket
        TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(8899);
        //高可用server
        THsHaServer.Args arg = new THsHaServer.Args(serverSocket).minWorkerThreads(2).maxWorkerThreads(4);

        //处理器
        PersonService.Processor<PersonServiceImpl> processor = new PersonService.Processor<>(new PersonServiceImpl());

        arg.protocolFactory(new TCompactProtocol.Factory());
        arg.transportFactory(new TFramedTransport.Factory());
        arg.processorFactory(new TProcessorFactory(processor));

        TServer server = new THsHaServer(arg);

        System.out.println("Thrift server started!");

        server.serve();
    }
}

Thrift客户端实现

public class ThriftClient {

    public static void main(String[] args) {
        TTransport transport = new TFramedTransport(new TSocket("localhost",8899),1000);
        TProtocol protocol = new TCompactProtocol(transport);
        PersonService.Client client = new PersonService.Client(protocol);

        try {
            transport.open();//打开socket
            Person person = client.getPersonByUsername("张三");
            System.out.println(person.getUsername());
            System.out.println(person.getAge());
            System.out.println(person.isMarried());

            System.out.println("-------------------------");

            Person person1 = new Person();
            person1.setUsername("李四");
            person1.setAge(20);
            person1.setMarried(false);
            client.savePerson(person1);

        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(),e);
        } finally {
            transport.close();
        }
    }
}

Thrift Python实现的服务端和客户端

修改Thrift的IDL文件

namespace py py.thrift.generated

typedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string String

struct Person{
    1: optional String username,
    2: optional int age,
    3: optional boolean married
}

exception DataException{
    1: optional String message,
    2: optional String callStack,
    3: optional String data
}

service PersonService{
    Person getPersonByUsername(1: required String username) throws (1: DataException dataException),

    void savePerson(1: required Person person) throws (1: DataException dataException)
}

通过thrift命令生成Python版thrift代码

thrift --gen py src/thrift/data.thrift

实现Thrift定义的service(Python)

# _*_ coding:utf-8 _*_
__author__ = 'huangyibo'

from py.thrift.generated import ttypes

class PersonServiceImpl:
    
    def getPersonByUsername(self, username):
        print 'Got client param:' + username
        person = ttypes.Person()
        person.username = username
        person.age = 20
        person.married = False
        return person

    def savePerson(self, person):
        print 'Got client param:'
        print person.username
        print person.age
        print person.married

Thrift服务端代码实现(Python)

# _*_ coding:utf-8 _*_
__author__ = 'huangyibo'

from py.thrift.generated import PersonService
from PersonServiceImpl import PersonServiceImpl

from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TCompactProtocol
from thrift.server import TServer

try:
    personServiceHandler = PersonServiceImpl()
    processor = PersonService.Processor(personServiceHandler)
    
    serverSocket = TSocket.TServerSocket(port=8899)
    transportFactory = TTransport.TFramedTransportFactory()
    protocolFactory = TCompactProtocol.TCompactProtocolFactory()

    server = TServer.TSimpleServer(processor,serverSocket,transportFactory,protocolFactory)
    server.service()

except Thrift.TException, ex:
    print '%s' % ex.message

Thrift客户端代码实现(Python)

# _*_ coding:utf-8 _*_
__author__ = 'huangyibo'

from py.thrift.generated import PersonService
from py.thrift.generated import ttypes

from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TCompactProtocol

import sys
reload(sys)
sys.setdefaultencoding('utf8')

try:
    tSocket = TSocket.TSocket('localhost',8899)
    tSocket.setTimeout(1000)

    transport = TTransport.TFramedTransport(tSocket)
    protocol = TCompactProtocol.TCompactProtocol(transport)
    client = PersonService.Client(protocol)

    transport.open()
    person = client.getPersonByUsername('张三')
    print person.username
    print person.age
    print person.married

    print '-------------------------'

    newPerson = ttypes.Person()
    newPerson.username = '李四'
    newPerson.age = 20
    newPerson.married = True

    client.savePerson(newPerson)

    transport.close()

except Thrift.TException, tx;
    print '%s' % tx.message

这样就可以实现Java客户端调用Python服务端、Python客户端调用Java服务端(其他语言之间的跨语言调用和这个类似)、Java客户端调用Java服务端、Python客户端调用Python服务端,特别是跨语言异构平台之间的调用价值很大,且比基于HTTP调用方式的RPC框架效率高很多。

 

参考:

Thrift 使用方法

Thrift小试牛刀

Thrift IDL

Thrift 的原理和使用

Apache Thrift系列详解(一) - 概述与入门

Thrift入门初探(2)--thrift基础知识详解

Thrift原理与使用实例

Thrift学习笔记—IDL基本类型