Thrift 源于 Facebook,在 2007 年 FacebookThrift 作为一个开源项目提交给了 Apache 基金会。对于当时的 Facebook 来说,创造 Thrift 是为了解决 Facebook 各系统间大数据量的传输通信以及系统之间语言环境不同需要跨平台的特性,因此 Thrift 可以支持多种程序语言,如 C++C#CocoaErlangHaskellJavaOcamiPerlPHPPythonRubySmalltalk。它被当作一个远程过程调用(RPC)框架来使用。

在多种不同的语言之间通信,Thrift 可以作为高性能的通信中间件使用,它支持数据(对象)序列化和多种类型的 RPC 服务。Thrift 适用于静态的数据交换,需要先确定好它的数据结构,当数据结构发生变化时,必须重新编辑 IDL 文件、生成代码和编译,这一点跟其他 IDL 工具相比可以视为是 Thrift 的弱项。Thrift 适用于搭建大型数据交换及存储的通用工具,对于大型系统中的内部数据传输,相对于 JSONXML 在性能和传输大小上都有明显的优势。

# 一、Thrift 主要由5部分组成

1)、语言系统以及 IDL编译器:负责由用户给定的 IDL文件生成相应语言的接口代码;
2)、TProtocolRPC 的协议层,可以选择多种不同的对象序列化方法,如 JSONBinary
3)、TTransportRPC 的传输层,同样可以选择不同的传输层实现,如 SocketNIOMemoryBuffer 等;
4)、TProcessor:作为协议层和用户提供的服务实现之间的纽带,负责调用服务实现的接口;
5)、TServer:聚合TProtocolTTransportTProcessor 等对象;

我们重点关注的是编解码框架,与之对应的就是 TProtocol。由于 Thrift 的 RPC(Remote Procedure Call) 服务调用和编解码框架绑定在一起,所以 ,通常使用 Thrift 的时候会采取 RPC 框架的方式。但是,它的 TProtocol 编解码框架还是可以以类库的方式独立使用的。

Protobuf(GoogleProtobuf) 比较类似的是,Thrift 通过 IDL 描述接口和数据结构定义,它支持 8种 Java 基本类型、MapSetList,支持可选和必须定义,功能非常强大。因为可以定义数据结构中字段的顺序,所以它也可以支持协议的前向兼容。

# 二、Thrift 支持三种典型的编解码方式

 ■ 通用的二进制编解码;
 ■ 压缩二进制编解码;
 ■ TJSONProtocolJSON 格式;
 ■ 优化的可选字段压缩编解码;
由于支持二进制压缩编解码,Thrift 的编解码性能表现也相当优异,远远超过 Java序列化和 RMI等。

Thrift 工作原理(实现多语言间的通信):数据的传输使用 socket(多种语言均支持),数据在以特定的格式(String等)发送,接收方按对应的格式进行解析。定义 thrift 文件,有 thrift 文件(IDL)生成双方语言的接口、Model,在生成接口和 Model 的代码中包含编解码的代码。

# 三、Thrift 数据/容器类型

Thrift 通过中间件语言IDL(接口定义语言)来定义 RPC 的数据类型和接口,这些内容写在以 .thrift 结尾的文件中。然后通过特殊的编译器来生成不同语言的代码,以满足不同需求的开发者。比如 java开发者,就可以生成 java代码,c++开发者可以生成 c++代码,生成的代码中不但包含目标语言的接口定义、方法、数据类型,还包含有 RPC协议层和传输层的实现代码。

Thrift 不支持无符号类型,因为很多语言不存在无符号类型,比如 Java
byte:有符号字节;
i16:16位有符号整数;
i32:32位有符号整数;
i64:64位有符号整数;
double:64位浮点数;
string:字符串

容器类型:ListSetMap:与 Java 中的类型类似。

# 四、Thrift 的协议栈结构

粘包

Thrift 是一种 C/S 的架构体系。TServer 主要任务是高效的接受客户端请求,并将请求转发给 TProcessor 处理。
■ 最上层是用户自行实现的业务逻辑代码;
Processor 是由 thrift 编译器自动生成的代码,它封装了从输入流中读取数据和向数据流中写数据的操作,它的主要工作是:从连接中读取数据,把处理交给用户实现 impl,最后把结果写入连接;
TProtocol 是用于数据类型解析的,将结构化数据转化为字节流给 TTransport进行传输。从 TProtocol以下部分是 thirft的传输协议和底层 I/O通信;
TTransport 是与底层数据传输密切相关的传输层,负责以字节流方式接收和发送消息体,不关注是什么数据类型;
■ 底层 IO 负责实际的数据传输,包括 socket、文件和压缩数据流等;

# 五、Thrift IDL 文件

namespace java com.test.thrift.demo

struct News {
	1:i32 id;
	2:string titie;
	3:string content;
	4:string mediaFrom;
	5:string autor;
}

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

enum Gender {
        MALE,
        FEMALE
}

exception RequestExce {
	1:i32 id;
	2:string titie;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

【1】结构体(struct):就像 C语言一样,Thrift 支持 Struct 类型,目的就是将一些数据聚合在一起方便传输管理。
【2】枚举(enum):定义形式与 Java 的定义形式类似。
【3】异常(exception):支持自定义 exception,规则与 Struct 一致。
【4】服务(service):Thrift 定义服务相当于 java 中创建 interface 一样,创建的 service 经过代码命名之后就会生成客户端和服务端的框架代码。相当于若干个方法的集合。
【5】类型定义:支持类型C++ 一样的 typedef 定义:typedef i32 int ,后续的操作就可以使用 int 代替 i32
【6】常量(const):使用 const 关键字:const i32 MAX_RETRIES_TIME = 10
【7】命名空间:相当于 java中的 package的意思,主要的目的是组织代码。thrift 的关键字 namespacenamespace 语言名(java) 路径(com.test.thrit.demo
【8】文件包含:Thrift 也支持文件包含,相当于C/C++ 中的 include,Java 中的 import。

include "test.thrift"
1

【9】注释:Thrift 注释支持 shell 风格的注释,支持 C/C++ 风格的注释,既#和//开头的都可以当注释,/**/包裹的可以当做注释。
【10】可选和必选:Thrift 提供了两个关键字:requiredoptional 分别用于字段的必填和可选。

# 六、Thrift 安装

官网下载地址:链接 (opens new window)

Thrift compiler for Windows [thrift-0.12.0.exe][PGP][MD5]
1

将下载的*.exe 所处的目录,配置在path 环境变量中,并重命名为 thrift.exe 即可(测试:thrift -version

E:\learnWorkspacesDesign\netty_learn\src\main\java> thrift -version
Thrift version 0.12.0
1
2

# 七、代码生成

【1】定义一个 .thrift 文件:data.thrift

namespace java com.thrift
#数据类型转化
typedef i16 short
typedef i32 int
typedef i64 log
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 date
}

#定义方法
service PersionService {
    Person getPersonByUsername(1:required String username) throws (1:DataException dataException),
    void savePerson(1:required Person person) throws (1:DataException dataException)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

【2】执行命令格式: thrift --gen java 文件路径

E:\learnWorkspacesDesign\netty_learn\src\main\java>thrift --gen java ../../thrift/data.thrift
1

【3】生成目录结构如下:
粘包
【4】导入依赖的 jar 包:

<dependency>
  <groupId>org.apache.thrift</groupId>
  <artifactId>libthrift</artifactId>
  <version>0.12.0</version>
</dependency>
1
2
3
4
5

# 八、数据传输方式

【1】TSocket:阻塞式 socket(效率底);
【2】(常用)TFramedTransport:以 Frame 为单位进行传输,非阻塞式服务中使用;
【3】TFileTransport:以文件形式进行传输;
【4】TMemoryTransport:将内存用于I/OJava 实现时内容实际使用了简单的 ByteArraryOutPutStream
【5】TZlibTransport:使用 Zlib 进行压缩与其他传输方式联合使用,当前无 Java 实现;

# 九、 Thrift 支持的服务模式

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

# 十、Thrift 服务端与客户端交互

【1】服务端实现类

public class PersionServiceImpl implements PersionService.Iface {
    @Override
    public Person getPersonByUsername(String username) throws DataException, TException {
        Person person = new Person();
        person.setAge(2);
        person.setUsername("zhengzhaoxiang");
        person.setMarried(false);
        return person;
    }

    @Override
    public void savePerson(Person person) throws DataException, TException {
        System.out.printf("服务端");
        System.out.printf(person.getUsername());

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

【2】thrift 服务端程序编写

public class Server {
    public static void main(String[] args) throws Exception {
        TNonblockingServerSocket socket = new TNonblockingServerSocket(8899);
        //服务  引入了线程池去处理
        THsHaServer.Args arg = new THsHaServer.Args(socket).minWorkerThreads(2).maxWorkerThreads(4);
        PersionService.Processor<PersionServiceImpl> processor = new PersionService.Processor<>(new PersionServiceImpl());

        arg.protocolFactory(new TCompactProtocol.Factory());
        arg.transportFactory(new TFramedTransport.Factory());
        arg.processorFactory(new TProcessorFactory(processor));
        TServer server = new THsHaServer(arg) ;
        System.out.println("服务端启动。。。。");
        server.serve();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

【3】thrift 客户端程序编写

public class PersionClient {
    public static void main(String[] args) {
        TTransport tTransport = new TFramedTransport(new TSocket("localhost",8899),600);
        TProtocol protocol = new TCompactProtocol(tTransport);
        PersionService.Client client = new PersionService.Client(protocol);

        try {
            tTransport.open();
            Person person = client.getPersonByUsername("zhangsan");
            System.out.println(person.getUsername());
            System.out.println(person.getAge());
            System.out.println(person.isMarried());
            System.out.println("---------");

            Person person1 = new Person();
            person1.setMarried(true);
            person1.setAge(20);
            person1.setUsername("李四");
            client.savePerson(person1);
        } catch (Exception e) {
            e.printStackTrace();
            tTransport.close();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

【4】测试结果(客户端/服务端)展示:

# 客户端结果展示
zhangsan
2
false
------------
# 服务端结果展示
服务端启动。。。。服务端
李四
1
2
3
4
5
6
7
8
(adsbygoogle = window.adsbygoogle || []).push({});