Thrift作为一个RPC框架,很好的解决了跨语言调用问题,并提供灵活高效的传输方式和Server模式。在实际应用中,个人认为Thrift更多的是用于系统的解耦,负责模块间的通信,使各模块可以独立开发、测试和发布等,一般情况下无需担心跨语言调用和传输性能问题;而对外调用还是偏向于更具通用性的HTTP REST。在本帖中,我们通过编写一个基本实例来了解Thrift的远程调用。
首先使用Manven创建一个Java Project,并加入Thrift依赖
<dependencies>
<dependency>
<groupId>org.apache.thriftgroupId>
<artifactId>libthriftartifactId>
<version>0.9.3version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.12version>
dependency>
dependencies>
Thrift为我们提供了 IDL(Interface Definition Language) 来编写通用的服务接口文件(后缀为thrift的文件),并编译生成对应语言的模板代码,从而解决跨语言调用问题。比如我们使用的是Java,则生成java文件,使用的是Node,那就生成js文件。
假如我们向一个影院信息系统查询一部电影的数据,那我们需要一个获取影片信息的接口,客户端传入查询影片的名称,服务端处理后返回该影片的详细数据。
首先我们需要一个电影实体,用于装载相关的数据,代码如下所示。
MovieModel.thrift
//定义java命名空间,对应package的值
namespace java com.thrift.simple.model
struct Movie {
1:string chName //中文名
2:string enName //英文名
3:string type //类型
4:string digital //数字种类
5:i64 releaseDate //上映日期
6:i32 duration //时长
7:i32 price //售价
8:map<string, bool> screenings //场次信息,是否有票
}
上述代码涉及到 IDL 支持的数据类型,有兴趣的读者可以到 http://thrift.apache.org/docs/types 简单学习一下,我们这里没有添加 required 或 optional 声明,Thrift 默认视为 required 处理,两者的区别是 required 声明的类型一定会序列化,如果你没有给它赋值,生成的 java 代码也会设置一个默认值,optional 未赋值则不会序列化。此外 IDL 只支持 java 的基本类型,像 Date 等类型是不支持的,需在代码中转换。
接下来需要一个查询电影信息的服务接口,代码如下所示。
MovieOperator.thrift
namespace java com.thrift.simple.service
//引用另一个Thrift文件,并通过“文件名.数据类型”的形式调用。
include "MovieModel.thrift"
service MovieOperator {
MovieModel.Movie getMovie(1:string chName)
}
这样我们需要的Thrift文件就准备好了,输入如下命令即可生成对应的java文件。
//加上-r选项,同时生成include文件
thrift -r --gen java MovieOperator.thrift
将生成的java文件放入工程对应的package下,就可以开始使用了,先简单分析一下生成的Movie和MovieOperator类。
Movie类
图3-1是Movie类的部分method,可以看到除了本身用于传输的read和write方法外,set和get,Object的equals和hashcode甚至compareTo方法都帮你写好了,在本例中我们可以直接作为Movie实体使用。
MovieOperator类
这个类的代码看上去很多,但结构简单,主要成员如图3-2所示。其中Iface是我们刚刚定义的接口,Client和Processor分别用于客户端和服务端,里面封装着远程调用的请求和接收方法,调用过程中会用到getMovie_args和getMovie_result方法来分别获取参数和返回值。最后3个Async方法用于异步处理,本例不作详细介绍。
现在正式编写代码,首先创建客户端的ThriftClient类,代码如下所示。
public class ThriftClient {
public static void main(String[] args) {
//对java的socket进行封装,定义传输的基本方法
TTransport baseTrans = new TSocket("localhost", 6060, 2000);
//进一步封装,使用按帧传输的方式,服务端必须使用非阻塞类型
TTransport frameTrans = new TFramedTransport(baseTrans);
//采用二进制协议,服务端协议须保持一致
TProtocol protocol = new TBinaryProtocol(frameTrans);
//将配置传入Client对象
MovieOperator.Client client = new MovieOperator.Client(protocol);
try {
//TSocket初始化并创建Socket连接
frameTrans.open();
//远程调用getMovie方法并获取返回值
Movie movie = client.getMovie("谍影重重5");
System.out.println("get remote info ----> " + movie.toString());
} catch (TException e) {
e.printStackTrace();
} finally {
//别忘了关闭连接
frameTrans.close();
}
}
}
编写Iface接口的实现类,也就是客户端来找服务端干的具体事情,代码如下所示。
public class MovieOperatorImpl implements MovieOperator.Iface {
@Override
public Movie getMovie(String chName) throws TException {
//创建实体对象
Movie movie = new Movie();
//本例就不从数据库中获取了,直接塞入一份测试数据
movie.setEnName("Jason Bourne");
movie.setChName(chName);
movie.setPrice(49);
movie.setDigital("原版3D");
movie.setDuration(123);
movie.setReleaseDate(dateTrans("2016-08-23"));
movie.setType("英语");
Map map = new HashMap();
map.put("18:15", false);
map.put("22:30", true);
movie.setScreenings(map);
return movie;
}
private long dateTrans(String date) {
long defaultTime = 0;
DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
try {
defaultTime = format.parse(date).getTime();
} catch (ParseException e) {
e.printStackTrace();
}
return defaultTime;
}
}
最后编写服务端ThriftServer类,代码如下所示。
public class ThriftServer {
public static final int servePort = 6060;
public static void main(String[] args) {
MovieOperator.Iface operator = new MovieOperatorImpl();
//将实现类的对象放入processor中维护,在客户端请求时,会映射到具体的执行方法
TProcessor processor = new MovieOperator.Processor<>(operator);
try {
//创建非阻塞的传输对象,对ServerSocket进行了封装
TNonblockingServerTransport nioSocket = new TNonblockingServerSocket(servePort);
TNonblockingServer.Args nonBlockingArgs = new TNonblockingServer.Args(nioSocket);
//放入process并设置transport和protocol的类型,注意与客户端对应
nonBlockingArgs.processor(processor);
nonBlockingArgs.transportFactory(new TFramedTransport.Factory());
nonBlockingArgs.protocolFactory(new TBinaryProtocol.Factory());
//实例化Server对象
TServer server = new TNonblockingServer(nonBlockingArgs);
System.out.println("service start at " + servePort);
//启动并监听
server.serve();
} catch (TException e) {
e.printStackTrace();
}
}
}
上述代码唯一存在疑惑的地方,就是TNonblockingServer的内部类Args,为什么不直接对TNonblockingServer实例化,而是借助该类配置所需的参数呢?
我们来看图6-1,TNonblockingServer只提供了一个以AbstractNonblockingServerArgs为参数的构造函数,因此必须先创建它的静态内部类Args对象。
我们继续查看Args的继承结构,如图6-2所示。
跟踪父类到TServer中,发现AbstractServerArgs中定义了Server所需的基本数据:Transport、Process和Protocol,也就是刚刚配置的参数属性,如图6-3所示。
看到这里,我们已经明白大体的设计方式了,TServer作为所有Server类型的父类,除了定义基础属性和操作标准外,还提供了一个抽象的Args类用于参数的配置和管理,子类根据需要继承并扩展该类即可,如经常使用的TThreadPoolServer就是这样干的。因此在实例化其他Server时,只需创建并配置对应的Args对象即可,体现了代码的封装性和扩展性。
到此代码就完成了,我们看下运行的结果,运行结果如下。
get remote info ----> Movie(chName:谍影重重5, enName:Jason Bourne, type:英语, digital:原版3D, releaseDate:1471881600000, duration:123, price:49, screenings:{18:15=false, 22:30=true})
Process finished with exit code 0
最后再回顾一下,首先我们使用Thrift提供的IDL编写了两个Thrift文件,一个是Movie实体,另一个是MovieOperator提供服务接口,通过命令生成java代码。客户端指定好传输方式和传输协议后,调用MovieOperator的Client与服务端交互。服务端这边先实现了MovieOperator的Iface接口,编写好具体的实现代码MovieOperatorImpl,接着把MovieOperatorImpl放入MovieOperator Processor中管理,然后指定与客户端对应的Server类型、传输方式和传输协议并实例化Server对象,最后启动服务并监听指定端口。
本例源码下载 http://download.csdn.net/detail/roderick2015/9613104
Tips:
如果把Thrift生成的代码放入工程后,出现如下图所示的问题,说明你的JDK编译版本太低,在1.5中的确不能这样使用。
可以参照下面的两张图,改成高一点的版本就行啦。