1. 概述
Apache Thrift 是 Facebook 实现的一种高效的、支持多种编程语言的远程服务调用的框架。本文将从 Java 开发人员角度详细介绍 Apache Thrift 的架构、开发和部署,并且针对不同的传输协议和服务类型给出相应的 Java 实例,同时详细介绍 Thrift 异步客户端的实现,最后提出使用 Thrift 需要注意的事项。
目前流行的服务调用方式有很多种,例如基于 SOAP 消息格式的 Web Service,基于 JSON 消息格式的 RESTful 服务等。其中所用到的数据传输方式包括 XML,JSON 等,然而 XML 相对体积太大,传输效率低,JSON 体积较小,新颖,但还不够完善。本文将介绍由 Facebook 开发的远程服务调用框架 Apache Thrift,它采用接口描述语言定义并创建服务,支持可扩展的跨语言服务开发,所包含的代码生成引擎可以在多种语言中,如 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk 等创建高效的、无缝的服务,其传输数据采用二进制格式,相对 XML 和 JSON 体积更小,对于高并发、大数据量和多语言的环境更有优势。本文将详细介绍 Thrift 的使用,并且提供丰富的实例代码加以解释说明,帮助使用者快速构建服务。
2. Maven依赖
Maven地址:http://mvnrepository.com/artifact/org.apache.thrift/libthrift
在这里,我使用的是0.9.3版本:
<!-- https://mvnrepository.com/artifact/org.apache.thrift/libthrift -->
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.9.3</version>
</dependency>
3. 安装 Thrift compiler
在编译.thrift文件时需要用到
xiaosi@Qunar:/opt/apache-flume-1.6.0-bin$ sudo apt-get install thrift-compiler
[sudo] xiaosi 的密码:
正在读取软件包列表... 完成
正在分析软件包的依赖关系树
正在读取状态信息... 完成
下列【新】软件包将被安装:
thrift-compiler
升级了 0 个软件包,新安装了 1 个软件包,要卸载 0 个软件包,有 162 个软件包未被升级。
需要下载 819 kB 的软件包。
解压缩后会消耗掉 2,944 kB 的额外空间。
获取:1 http://cn.archive.ubuntu.com/ubuntu/ wily/universe thrift-compiler amd64 0.9.1-2 [819 kB]
下载 819 kB,耗时 0秒 (982 kB/s)
正在选中未选择的软件包 thrift-compiler。
(正在读取数据库 ... 系统当前共安装有 273041 个文件和目录。)
正准备解包 .../thrift-compiler_0.9.1-2_amd64.deb ...
正在解包 thrift-compiler (0.9.1-2) ...
正在处理用于 man-db (2.7.4-1) 的触发器 ...
正在设置 thrift-compiler (0.9.1-2) ...
4. 创建Thrift文件
在安装Thrift编译器之后,我们需要创建一个.thrift文件,在这里我们在main文件夹下创建calculator.thrift文件。
这个文件是一个接口定义(服务)文件。这些服务会被服务器端实现,被客户端调用。服务器端和客户端下面会讲解到。
// defines the namespace
namespace java com.sjf.open
service CalculatorService {
i32 add(1:i32 num1, 2:i32 num2)
i32 minus(1:i32 num1, 2:i32 num2)
i32 multi(1:i32 num1, 2:i32 num2)
i32 divi(1:i32 num1, 2:i32 num2)
}
其中定义了CalculatorService服务的4个方法,每个方法包含一个方法名,参数列表和返回类型。每个参数包括参数序号,参数类型以及参数名(1:i32 num1)。 Thrift 是对 IDL(Interface Definition Language) 描述性语言的一种具体实现。因此,以上的服务描述文件使用 IDL 语法编写。使用 Thrift 工具编译 Calculator.thrift,就会生成相应的 CalculatorService.java 文件。namespace 定义了命名空间,在java中为包,在这里我们会在com.sjf.open包下生成CalculatorService.java 文件。
5. 编译Thrift文件
Thrift编译器会将thrift文件编译成Java代码。使用下面的命令编译.thrift文件:
thrift --gen <language> <Thrift filename>
在我们例子中,命令为:
xiaosi@Qunar:~/code/open/openDiary/ThriftDemo/src/main$ thrift --gen java calculator.thrift
编译之后,会在gen-java文件夹下创建com.sjf.open包,并在包下生成CalculatorService.java 文件。
该文件包含了在 Calculator.thrift 文件中描述的服务 CalculatorService 的接口定义,即 CalculatorService.Iface 接口,以及服务调用的底层通信细节,包括客户端的调用逻辑 CalculatorService.Client 以及服务器端的处理逻辑 CalculatorService.Processor,用于构建客户端和服务器端的功能。
6. 创建Service Handler类
Service handler 类必须实现 CalculatorService.Iface接口。Handler类是接口的实现类。
package com.sjf.open;
import org.apache.thrift.TException;
/**
* Created by xiaosi on 16-9-20.
*/
public class CalculatorHandler implements CalculatorService.Iface{
public int add(int num1, int num2) throws TException {
return num1 + num2;
}
public int minus(int num1, int num2) throws TException {
return num1 - num2;
}
public int multi(int num1, int num2) throws TException {
return num1 * num2;
}
public int divi(int num1, int num2) throws TException {
if(num2 == 0){
throw new RuntimeException("分母不能为0");
}
return num1 / num2;
}
}
7. 创建服务器端CalculatorServer
创建服务器端实现代码,将 CalculatorHandler 作为具体的处理器传递给 Thrift 服务器。
package com.sjf.open;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
/**
* Created by xiaosi on 16-9-20.
*/
public class CalculatorServer {
private static int port = 9090;
private static CalculatorHandler handler;
private static CalculatorService.Processor processor;
/**
* 启动服务器
* @param processor
*/
public static void start(CalculatorService.Processor processor){
try {
TServerTransport serverTransport = new TServerSocket(port);
TServer server = new TSimpleServer(new TServer.Args(serverTransport).processor(processor));
// Use this for a multithreaded server
// TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(serverTransport).processor(processor));
System.out.println("Starting the simple server...");
server.serve();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
handler = new CalculatorHandler();
processor = new CalculatorService.Processor(handler);
start(processor);
}
}
8. 创建客户端CalculatorClient
创建客户端实现代码,调用 CalculatorService.client 访问服务端的逻辑实现。
package com.sjf.open;
import com.google.common.base.Objects;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
/**
* Created by xiaosi on 16-9-20.
*/
public class CalculatorClient {
private static int port = 9090;
private static String ip = "localhost";
private static CalculatorService.Client client;
private static TTransport transport;
/**
* 创建 TTransport
* @return
*/
public static TTransport createTTransport(){
TTransport transport = new TSocket(ip, port);
return transport;
}
/**
* 开启 TTransport
* @param transport
* @throws TTransportException
*/
public static void openTTransport(TTransport transport) throws TTransportException {
if(Objects.equal(transport, null)){
return;
}
transport.open();
}
/**
* 关闭 TTransport
* @param transport
*/
public static void closeTTransport(TTransport transport){
if(Objects.equal(transport, null)){
return;
}
transport.close();
}
/**
* 创建客户端
* @return
*/
public static CalculatorService.Client createClient(TTransport transport){
if(Objects.equal(transport, null)){
return null;
}
TProtocol protocol = new TBinaryProtocol(transport);
if(Objects.equal(protocol, null)){
return null;
}
CalculatorService.Client client = new CalculatorService.Client(protocol);
return client;
}
public static void main(String[] args) {
try {
// 创建 TTransport
transport = createTTransport();
// 开启 TTransport
openTTransport(transport);
// 创建客户端
client = createClient(transport);
// 调用服务
if(Objects.equal(client, null)){
System.out.println("创建客户端失败...");
return;
}
System.out.println(client.add(100, 200));
} catch (TException e) {
e.printStackTrace();
}
finally {
// 关闭 TTransport
closeTTransport(transport);
}
}
}
9. 运行
完成结构图:
运行服务端代码(CalculatorServer)将会看到下面的输出:
Starting the simple server...
然后运行客户端代码(CalculatorClient),将会看到如下输出:
300
10. 代码连接
https://github.com/sjf0115/OpenDiary/tree/master/ThriftDemo
参考连接: