thrift 的简单实用案例
thrift是由facebook 开发的一套跨语言的rpc服务框架,在2007年捐献给Apache软件基金会,它结合了软件堆栈和强大的代码生成引擎。以构建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些语言无缝的结合与高效的服务。
目前流行的服务调用方式有很多种,例如:基于SOAP的消息格式的webservice,基于json消息格式的Restful服务、基于java的远程方法调用服务等。然而XML 相对体积过大,传输效率非常低,json体积小,不够完善,现在我们要试试apache thrift 其传输数据为二进制传输,支持可扩展的跨语言的服务开发,它的代码生成器引擎非常强大,例如生成java、c++ 、c、php、go等语言创建无缝而又高效的服务。对高并发、大数据和多语言的环境更有优势。如果你只用java语言请看下阿里巴巴的dubbo 框架。下面我们来看下thrift的入门。
1、利用thrift的接口描述语言(IDL)写一个简单的xxx.thrift
/********************* thrift ld ***/ namespace cpp org.dongtian.sys.thrift namespace java org.dongtian.sys.thrift namespace go org.dongtian.sys.thrift exception UserServiceThriftException { 1: i32 whatOp, 2: string why } struct User { 1: i32 userId, 2: string userName, 3: string password, 4: i32 state, 5: i32 age, 6: i32 sex, 7: string address, 8: string mobile, 9: string email, 10: i32 userType, 11: string nickName, 12: string createTime, 13: string updateTime, 14: string regTime } service UserServiceThrift { User login(1:string userName) throws (1:UserServiceThriftException error), listfindUserList(1:string userName,2:string nickName,3:string email,4:string mobile,5:string startTime,6:string endTime) throws (1:UserServiceThriftException error), i32 findUserCount(1:string userName,2:string nickName,3:string email,4:string mobile,5:string startTime,6:string endTime) throws (1:UserServiceThriftException error), bool checkEmail(1:string email,2:i32 userId) throws (1:UserServiceThriftException error), bool checkMobile(1:string mobile,2:i32 userId) throws (1:UserServiceThriftException error), bool addUser(1:User user) throws (1:UserServiceThriftException error), User findUserByUserId(1:i32 userId) throws (1:UserServiceThriftException error), bool updateUser(1:User user) throws (1:UserServiceThriftException error), bool deleteUserByUserId(1:i32 userId) throws (1:UserServiceThriftException error), bool checkNickName(1:string nickName, 2:i32 userId) throws (1:UserServiceThriftException error), bool checkUserName(1:string userName, 2:i32 userId) throws (1:UserServiceThriftException error) }
说明 其中在服务名称为UserServiceThrift定义了几个服务方法,每个方法都包含有方法名、参数、返回值。并且每个方法参数都要有从1开始的序号,thrift就是根据IDL 接口描述语言的实现。我们使用thrift开发流程:1、通过上面的IDL语言的语法编写xxx.thrift服务描述文件。2、通过thrift的代码生成工具编译对应的xxx.thrift生成对应语言的代码(比如:我们生成java 代码 thrift --gen java UserServiceThrift.thrift,就生成了UserServiceThrift.java 以及对应自定义类型的类。UserServiceThrift.java包含有UserServiceThrift.Iface接口 以及底层调用通讯细节 包括客户端的调用逻辑 UserServiceThrift.Client 服务端的处理逻辑 UserServiceThrift.Processor 这两个由于构建客户端和服务端的功能。)。把生成的代码复制到我们要使用thrift项目中。下面以java语言为例:
1、服务端服务接口具体实现的类实现(UserServiceThrift.Iface接口)
package org.dongtian.sys.thrift.server; import java.util.ArrayList; import java.util.List; import org.apache.thrift.TException; import org.dongtian.sys.thrift.User; import org.dongtian.sys.thrift.UserServiceThrift; import org.dongtian.sys.thrift.UserServiceThriftException; public class UserHandler implements UserServiceThrift.Iface { @Override public User login(String userName) throws UserServiceThriftException, TException { // TODO Auto-generated method stub return new User(); } @Override public ListfindUserList(String userName, String nickName, String email, String mobile, String startTime, String endTime) throws UserServiceThriftException, TException { return new ArrayList (); } @Override public int findUserCount(String userName, String nickName, String email, String mobile, String startTime, String endTime) throws UserServiceThriftException, TException { return 0; } @Override public boolean checkEmail(String email, int userId) throws UserServiceThriftException, TException { System.err.println("============================="); return false; } @Override public boolean checkMobile(String mobile, int userId) throws UserServiceThriftException, TException { return false; } @Override public boolean addUser(User user) throws UserServiceThriftException, TException { return false; } @Override public User findUserByUserId(int userId) throws UserServiceThriftException, TException { return new User(); } @Override public boolean updateUser(User user) throws UserServiceThriftException, TException { return false; } @Override public boolean deleteUserByUserId(int userId) throws UserServiceThriftException, TException { return false; } @Override public boolean checkNickName(String nickName, int userId) throws UserServiceThriftException, TException { return false; } @Override public boolean checkUserName(String userName, int userId) throws UserServiceThriftException, TException { return false; } }
2、创建服务端用于启动服务
package org.dongtian.sys.thrift.server; import org.apache.thrift.server.TServer; import org.apache.thrift.server.TThreadedSelectorServer; import org.apache.thrift.transport.TNonblockingServerSocket; import org.dongtian.sys.thrift.UserServiceThrift; import org.dongtian.sys.thrift.UserServiceThrift.Processor; public class TestServer { public static void main(String[] args) throws Exception { //设置服务端口 TNonblockingServerSocket serverTransport = new TNonblockingServerSocket(9090); //具体服务实现 UserHandler handler = new UserHandler(); //服务端具体实现的处理器 Processorprocessor = new UserServiceThrift.Processor (handler); org.apache.thrift.server.TThreadedSelectorServer.Args args2 = new org.apache.thrift.server.TThreadedSelectorServer.Args(serverTransport); //关联处理器与服务类的实现 args2.processor(processor); TServer server = new TThreadedSelectorServer(args2); server.serve(); } }
3、客户端具体调用代码
package org.dongtian.sys.thrift.client; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; import org.dongtian.sys.thrift.UserServiceThrift.Client; public class TestClient { public static void main(String[] args) { TTransport transport = null; try { TTransport socket = new TSocket("127.0.0.1", 9090,5000); transport = new TFramedTransport(socket); TProtocol protocol = new TBinaryProtocol(transport); Client client = new Client(protocol); transport.open(); boolean has = client.checkEmail("xx", 0); System.err.println(has); transport.close(); } catch (Exception e) { e.printStackTrace(); }finally { if(transport != null) { transport.close(); } } } }
4、启动服务器端再启动客户端测试调用会打印一个false
二、Thrift架构
thrift 包含有一个完整的堆栈结构用于构建服务端和客户端,下面看下它的架构图
黄色部分为我们开发者实现的代码业务逻辑,褐色部分是根据 Thrift 定义的服务接口描述文件生成的客户端和服务器端代码框架,红色部分是根据 Thrift 文件生成代码实现数据的读写操作。红色部分以下是 Thrift 的传输体系、协议以及底层 I/O 通信,使用 Thrift 可以很方便的定义一个服务并且选择不同的传输协议和传输层而不用重新生成代码。thrift服务器包含用于绑定协议和传输层基础架构,它提供阻塞、非阻塞、单线程、多线程的模式运行在服务器中。
三、数据类型
Thrift 脚本可定义的数据类型包括以下几种类型:
- 基本类型:
- bool:布尔值,true 或 false,对应 Java 的 boolean
- byte:8 位有符号整数,对应 Java 的 byte
- i16:16 位有符号整数,对应 Java 的 short
- i32:32 位有符号整数,对应 Java 的 int
- i64:64 位有符号整数,对应 Java 的 long
- double:64 位浮点数,对应 Java 的 double
- string:未知编码文本或二进制字符串,对应 Java 的 String
- 结构体类型:
- struct:定义公共的对象,类似于 C 语言中的结构体定义,在 Java 中是一个 JavaBean
- 容器类型:
- list:对应 Java 的 ArrayList
- set:对应 Java 的 HashSet
- map:对应 Java 的 HashMap
- 异常类型:
- exception:对应 Java 的 Exception
- 服务类型:
- service:对应服务的类
四、传输协议
thrift做到很好的让用户在服务器端与客户端选择对应的传输协议,总体上一般为2中传输协议二进制或者文本,但是为了节省带宽大多数采用二进制的协议。开发者可用根据自己的项目需求选择对应的协议。
1、TCompactProtocol-紧凑的、高效的二进制传输协议。构建协议非常简单:
TTransport transport = null; TCompactProtocol tCompactProtocol = new TCompactProtocol(transport); new Client(tCompactProtocol);
2、TBinaryProtocol-基于二进制传输的协议,使用方法与TCompactProtocol 相同
3、TJSONProtocol-使用json格式编码传输协议
4、TSimpleJSONProtocol-仅仅是写入的json格式编码传输协议-适用于通过脚本语言解析
五、传输层
1、TSocket -使用阻塞io进行传输最常见模式
try { // 设置调用的服务地址为本地,端口为 9090 TTransport transport = new TSocket("localhost", 9090); transport.open(); // 设置传输协议为 TBinaryProtocol TProtocol protocol = new TBinaryProtocol(transport); UserServiceThrift.Client client = new UserServiceThrift.Client(protocol); // 调用服务的 helloVoid 方法 client.checkEail("XX",0); transport.close(); } catch (TTransportException e) { e.printStackTrace(); } catch (TException e) { e.printStackTrace(); }
2、TFramedTransport-非阻塞方式按照块的大小进行传输,类似java的NIO,如果使用了TFramedTransport作为传输层,则服务端必须改成非阻塞服务类型,TNonblockingServerTransport 用于构建非阻塞socket 下面具体代码用于构建非阻塞socket:
//设置服务端口 TNonblockingServerSocket serverTransport = new TNonblockingServerSocket(9090);
客户端修改为TFramedTransport 传输
TTransport transport = null; try { TTransport socket = new TSocket("127.0.0.1", 9090,5000); transport = new TFramedTransport(socket); TProtocol protocol = new TBinaryProtocol(transport); Client client = new Client(protocol); transport.open(); boolean has = client.checkEmail("xx", 0); System.err.println(has); } catch (Exception e) { e.printStackTrace(); }finally { if(transport != null) { transport.close(); } }
六、服务端类型
1、TSimpleServer-单线程类型的服务类型使用标准的阻塞io 结合tsocket
TSimpleServer server2 = new TSimpleServer(args2); server2.serve();
2、TThreadPoolServer -多线程服务类型采用标准的阻塞IO
Args arg = new Args(serverTransport); arg.executorService(Executors.newFixedThreadPool(15)); arg.processor(processor); TThreadPoolServer poolServer = new TThreadPoolServer(arg); poolServer.serve();
3、TThreadedSelectorServer-非阻塞服务类型 使用非阻塞io 结合TFramedTransport使用
//设置服务端口 TNonblockingServerSocket serverTransport = new TNonblockingServerSocket(9090); //具体服务实现 UserHandler handler = new UserHandler(); //服务端具体实现的处理器 Processorprocessor = new UserServiceThrift.Processor (handler); org.apache.thrift.server.TThreadedSelectorServer.Args args2 = new org.apache.thrift.server.TThreadedSelectorServer.Args(serverTransport); //关联处理器与服务类的实现 args2.processor(processor); TServer server = new TThreadedSelectorServer(args2); server.serve();
七、构建异步客户端
比如有这样一个场景-我们需要调用对应的一个服务进行数据备份操作,而且备份的数据非常大,一般在几分钟或者几个小时无法完成,但是我们客户端想要知道服务端备份完成之后做出相应给客户端。那么我们只能做异步了,值得庆幸的是thrift支持异步的客户端的构建操作。TAsyncClientManager这个类用于管理客户端的请求,它能够在一个线程上进行追踪请求和响应,在此类中包含有一个任务队列private final ConcurrentLinkedQueue
package org.dongtian.sys.thrift.client; import org.apache.thrift.async.AsyncMethodCallback; public class ClientAsyncMethod implements AsyncMethodCallback { private Object obj = null; /*** * 服务端处理完成调用 */ @Override public void onComplete(Object response) { obj = response; } /*** * 服务端抛出异常 */ @Override public void onError(Exception exception) { obj = exception; } /*** * 客户端判断是否服务端已经响应完成调用此方法 * @return */ public Object returnObj() { return obj; } }
修改客户端方法
package org.dongtian.sys.thrift.client; import org.apache.thrift.async.TAsyncClientManager; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.protocol.TProtocolFactory; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TNonblockingServerSocket; import org.apache.thrift.transport.TNonblockingSocket; import org.apache.thrift.transport.TNonblockingTransport; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; import org.dongtian.sys.thrift.UserServiceThrift; import org.dongtian.sys.thrift.UserServiceThrift.Client; public class TestClient { public static void main(String[] args) { TNonblockingTransport transport = null; try { transport = new TNonblockingSocket("127.0.0.1", 9090,5000); TAsyncClientManager clientManager = new TAsyncClientManager(); TProtocolFactory factory = new TBinaryProtocol.Factory(); UserServiceThrift.AsyncClient client = new UserServiceThrift.AsyncClient(factory, clientManager, transport); transport.open(); ClientAsyncMethod asyncMethod = new ClientAsyncMethod(); client.checkEmail("xx", 0,asyncMethod); while (asyncMethod.returnObj() != null) { System.err.println(asyncMethod.returnObj()); } } catch (Exception e) { e.printStackTrace(); }finally { if(transport != null) { transport.close(); } } } }
八、总结
apache thrift是一个非常优秀的跨语言高性能的服务框架,thrift基于RPC (远程过程调用)服务框架,比如现在很多的框架都有很好的整合到去,比如hadoop和habase 提供thrift接口 来解决为支撑不同语言客户端跨语言的服务调用。跨语言方面解决了,但是thrift也有不足的地方,比如做服务集群的时候,需要自己进行设置解决。没有像阿里巴巴的dubbo那样基于服务注册中心: 服务的发布与订阅 审计功能解决高并发环境中负载均衡的功能。不过这点我们可以利用zookeeper服务协调框架来实现简单基于thrift的RPC服务集群。最后,不管哪个框架都有好坏,自己根据自己的项目产品使用对应的框架和技术那样才好,不会人家叫你做一个仓库管理系统 你把单点、集群等都考虑在内吧?下面例子在附件可以对照着例子和官方文档自己多动动手!