Java技术积累之Thrift
简介
Thrift是一个RPC框架,由facebook开发,07年四月开放源码,08年5月进入Apache孵化器。它支持可扩展且跨语言的服务的开发,它结合了功能强大的软件堆栈和代码生成引擎,以构建在C++、Java、Python、PHP、Ruby、Erlang、Perl、Haskell、C#、Cocoa、JavaScript、Node.js、Smalltalk and OCaml等等编程语言间无缝结合的、高效的服务。Thrift允许你定义一个简单的定义文件中的数据类型和服务接口。以作为输入文件,编译器生成代码用来方便地生成RPC客户端和服务器通信的无缝跨编程语言。
基本结构
- Transport层:抽象了数据在网络中的传输。
- Protocol层:定义了数据的序列化、反序列化方式。常用的格式有:二进制、压缩格式和json格式。
- Processor层:Thrift中最关键的一层,它包括thrift文件生成的接口,以及这些接口应对的实现。
- Server层:将所有这些(Transport、Protocol与Processor)封装在一起,对外提供服务。
例子
Thrift的安装
brew install thrift
项目结构
├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ └── thrift │ │ │ ├── client │ │ │ │ └── Client.java │ │ │ ├── demo │ │ │ │ ├── HelloWorldImpl.java │ │ │ │ └── HelloWorldService.java │ │ │ └── server │ │ │ └── Server.java │ │ ├── resources │ │ └── thrift │ │ └── HelloWorld.thrift │ └── test │ └── java
首先创建thrift文件夹及其路径下的HelloWorld.thrift,内容如下:
namespace java com.thrift.demo service HelloWorldService { string sayHello(1:string usrename); }
随后在thrift下启动终端并输入:
thrift --gen java HelloWorld.thrift
随后在原地生成com.thrift.demo.HelloWorldService.java,我们将其放到java路径下并新建两个包server与client。
感兴趣可以观察一下HelloWorldService.java的内容,竟然自动生成了近900行的代码。
随后在com.thrift.demo包下新建HelloWorldImpl,这个是我们实现业务逻辑的类。
package com.thrift.demo; import org.apache.thrift.TException; public class HelloWorldImpl implements HelloWorldService.Iface{ public HelloWorldImpl() {} public String sayHello(String usrename) throws TException { return "Hi,"+usrename+"Welcome to my website"; } }
值得注意的是,这个类实现了生成的HelloWorldService下的一个子接口。随后将方法补全。
下面给出Server以及Client的实现逻辑,解释内容以注释的形式给出:
package com.thrift.server; import com.thrift.demo.HelloWorldImpl; import com.thrift.demo.HelloWorldService; import org.apache.thrift.TProcessor; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.server.TServer; import org.apache.thrift.server.TSimpleServer; import org.apache.thrift.transport.TServerSocket; public class Server { public final static int SERVER_PORT = 7099; private static String SERVER_IP = "localhost"; public static void startServer() { try { System.out.println("HelloWorld Server start..."); // 定义ServerSocket TServerSocket serverSocket = new TServerSocket(SERVER_PORT); // 定义参数对象并绑定到ServerSocket TServer.Args args = new TServer.Args(serverSocket); /* HelloWorldService.Processor在定义时实现了org.apache.thrift.TProcessor,其构造函数需要传入一个iFace * 即我们的具体需求实现 */ TProcessor processor = new HelloWorldService.Processor(new HelloWorldImpl()); // 定义一个处理二进制协议的工厂 TBinaryProtocol.Factory portFactory = new TBinaryProtocol.Factory(true, true); // 参数绑定处理器以及工厂 args.processor(processor); args.protocolFactory(portFactory); // 定义工厂,还有TThreadPoolServer的线程池版本 TServer server = new TSimpleServer(args); // 启动服务 server.serve(); } catch (Exception e) { System.out.println("Server start error"); e.printStackTrace(); } } public static void main(String[] args) { Server.startServer(); } }
package com.thrift.client; import com.thrift.demo.HelloWorldService; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; public class Client { public static final int SERVER_PORT = 7099; public static final String SERVER_IP = "localhost"; public static void startClient(String username) { // 定义一个运输层实体 TTransport tTransport = null; try { // 绑定ip和端口 tTransport = new TSocket(SERVER_IP, SERVER_PORT); // 定义对应的协议 TProtocol protocol = new TBinaryProtocol(tTransport); // 生成对应的客户端并绑定协议 HelloWorldService.Client client = new HelloWorldService.Client(protocol); // 打开实体 tTransport.open(); // 进行远程调用 String result = client.sayHello(username); System.out.println("Thrift client result=" + result); Thread.sleep(2000); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { Client.startClient("zhouxiang"); } }
测试
先运行Server,再运行Client可从控制台观察到结果:
Thrift client result=Hi,zhouxiangWelcome to my website
Thrift支持的协议
Thrift可以让你选择客户端与服务端之间数据的传输通信协议,但要保证客户端和服务端的协议一致。在传输协议上总体上划分为文本和二进制传输协议,为节约带宽和提高传输效率,一般情况下使用二进制类型的传输协议较多,但有时会还是会使用基于文本类型的协议,这需要根据实际需求来看。
- TBinaryProtocol:二进制编码格式进行数据传输
- TCompactProtocol:这种协议非常有效,使用Variable-Length Quantity (VLQ) 编码对数据进行压缩
- TJSONProtocol:使用JSON的数据编码协议进行数据传输
- TSimpleJSONProtocol:这种节约只提供JSON只写的协议,适用于通过脚本语言解析
- TDebugProtocol:在开发的过程中帮助开发人员调试用的,以文本的形式展现方便阅读
Thrift支持的服务端
Thrift在服务端提供了很多不同类型的选择:
- TSimpleServer:TSimpleServer是单线程阻塞IO的实现,仅适用于demo
- TThreadPoolServer:顾名思义,TThreadPoolServer内部使用一个线程池来处理客户端的请求。它使用一个专门的线程来接收请求,一旦接收到请求就会放入ThreadPoolExecutor中的一个线程池处理,性能表现优异
- TNonblockingServer:单线程非阻塞IO的实现,通过java.nio.channels.Selector的select()接收连接请求,但是处理消息仍然是单线程,不可用于生产
- THsHaServer:THsHaServer继承了TNonblockingServer,不同之处在于THsHaServer内部使用了一个线程池来处理请求。THsHaServer相比较于TNonblockingServer类似TThreadPoolServer相比较于TSimpleServer。另外,当使用TNonblockingServer或者THsHaServer时,必须使用TFramedTransport来封装一下原始的transport
- TThreadedSelectorServer:是thrift 0.8引入的实现,处理请求也使用了线程池,比THsHaServer有更高的吞吐量和更低的时延