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最新版本0.6.1。
首先呢,Thrift使用了ThriftIDL来定义服务器和客户端的接口。例如,这是本文使用的一个thrift(calculator.thrift)文件。
接下来,我们需要把这个thrift文件编译成Java文件。
我们需要的编译工作可以到http://thrift.apache.org/download/下载:Thrift compiler for Windows (thrift-0.6.1.exe) 。千万不要下载“Snapshot Releases”的,这个不一定能用,我就下错了,结果无法使用,呵呵。
接下来我们运行命令
thrift-0.6.1.exe -o src --gen java calculator.thrift
可以看到在src目录下生成了如下的文件
把这几个文件加入到你的项目中吧。
下面,我们需要thrift的Java库。
使用maven或ivy的可以参考http://mvnrepository.com/artifact/org.apache.thrift/libthrift/0.6.1进行下载
thrift没有提供完整的下载,不过既然是搞Java的,起码要会maven或ivy吧。
实在不行,就去http://svn.apache.org/repos/asf/thrift/trunk/lib/java/下载源代码,然后运行一下ant就ok啦。
到这里我们已经准备好了thrift。
我们看一下thrift的架构
thrift最大的功劳就在于他帮助我们实现了传输层(TTransport)和协议层(TProtocol),我们只需要选择需要一个适合自己的实现就ok啦。
例如,我们客户端可以这么写
package calculator;
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;
public class Client {
public static void main(String[] args) {
// 传输层
// * TSocket- 使用堵塞式I/O进行传输,也是最常见的模式。
// * TFramedTransport- 使用非阻塞方式,按块的大小,进行传输,类似于Java中的NIO。
// * TFileTransport- 顾名思义按照文件的方式进程传输,虽然这种方式不提供Java的实现,但是实现起来非常简单。
// * TMemoryTransport- 使用内存I/O,就好比Java中的ByteArrayOutputStream实现。
// * TZlibTransport- 使用执行zlib压缩,不提供Java的实现。
TTransport transport = new TSocket("localhost", 9090);
// 协议层
// * TBinaryProtocol – 二进制编码格式进行数据传输。
// * TCompactProtocol – 这种协议非常有效的,使用Variable-Length Quantity
// (VLQ)编码对数据进行压缩。
// * TJSONProtocol – 使用JSON的数据编码协议进行数据传输。
// * TSimpleJSONProtocol – 这种节约只提供JSON只写的协议,适用于通过脚本语言解析
TProtocol protocol = new TBinaryProtocol(transport);
Calculator.Client client = new Calculator.Client(protocol);
try {
transport.open();
System.out.println(client.calculate(Operation.ADD, 1, 2));
System.out.println(client.calculate(Operation.SUBTRACT, 3, 5));
System.out.println(client.calculate(Operation.MULTIPLY, 4, 2));
System.out.println(client.calculate(Operation.DIVIDE, 1, 0));
} catch (TTransportException e) {
e.printStackTrace();
} catch (InvalidOperation e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
} finally {
// 最后一定要调用close来释放资源
transport.close();
}
}
}
这个客户端很简单,设置一个传输层(TTransport)和协议层(TProtocol)的实现类TSocket和TBinaryProtocol,就可以使用thrift帮我们生成的类alculator.Client(这个完全不用我们写一行代码的,自动生成的类)来调用服务器上的真正的Calculator对象了,用起来跟直接调用函数差不多,把网络传输细节都隐藏起来了。
下面我们看一下服务器的实现。
package calculator;
import org.apache.thrift.TException;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
import org.apache.thrift.transport.TTransportException;
public class Server {
/*
* 在服务器这边,由这个类来真正处理客户端的请求。
*/
public static class CalculatorHandler implements Calculator.Iface {
@Override
public int calculate(Operation operation, int num1, int num2)
throws InvalidOperation, TException {
switch (operation) {
case ADD:
return num1 + num2;
case SUBTRACT:
return num1 - num2;
case MULTIPLY:
return num1 * num2;
case DIVIDE:
if (num2 == 0) {
throw new InvalidOperation(1, "divide by zero");
}
return num1 / num2;
default:
throw new InvalidOperation(0, "impossible code");
}
}
}
public static void main(String[] args) {
// 既然是RPC,那么我们在服务器需要有一个真正的类来处理客户端的请求
Calculator.Processor processor = new Calculator.Processor(
new CalculatorHandler());
try {
TServerTransport serverTransport = new TServerSocket(9090);
// 服务端类型
// * TSimpleServer - 单线程服务器端使用标准的堵塞式I/O。
// * TThreadPoolServer - 多线程服务器端使用标准的堵塞式I/O。
// * TNonblockingServer – 多线程服务器端使用非堵塞式I/O,并且实现了Java中的NIO通道。
TServer server = new TSimpleServer(
new TServer.Args(serverTransport).processor(processor));
server.serve();
// 跟Client的写法比起来,是不是觉得少了TProtocol?
// 因为TServer.Args默认设置的TProtocol是TBinaryProtocol,因此就不显示指定了。
} catch (TTransportException e) {
e.printStackTrace();
}
}
}
服务器的实现也一样,设置一个传输层(TTransport)和协议层(TProtocol)的实现类TSocket和TBinaryProtocol,然后直接调用TServer的serve函数就ok了。之后服务器就挂起等待客户端的请求了。
当然,我们还需要在服务器这边写一个真正的CalculatorHandler实例。这个实例在服务器这边进行真正的计算,然后有TServer自动地把结果传递给client。
Thrift比起protobuf的优点
protobuf也提供了跨语言的数据传输解决方案,但是没有thrift的server和client的功能。使用protobuf实现server和client需要自己编写网络传输的代码(缺少thrift的TTransport和TProtocol这两层的实现)。另外,protobuf只支持c++,python,java,语言数量比较少。Thrift支持C++, C#, Erlang, Haskell, Java, Objective C/Cocoa, OCaml, Perl, PHP, Python, Ruby, and Squeak等各种听过或没听过的语言^_^。当然protobuf优势在于传输的数据比thrift小一些,当然差距不是很大。另外,事实上protobuf完全可以实现server/client功能,从protobuf的service功能可以看出来,不知道为什么google没有实现,而是留给用户去实现。
参考资源
http://www.javabloger.com/article/apache-thrift-architecture.html