Thrift应用实践—基本篇

来源:互联网 发布:达内大数据培训多少钱 编辑:程序博客网 时间:2024/06/03 14:37

1.简介

    Thrift作为一个RPC框架,很好的解决了跨语言调用问题,并提供灵活高效的传输方式和Server模式。在实际应用中,个人认为Thrift更多的是用于系统的解耦,负责模块间的通信,使各模块可以独立开发、测试和发布等,一般情况下无需担心跨语言调用和传输性能问题;而对外调用还是偏向于更具通用性的HTTP REST。在本帖中,我们通过编写一个基本实例来了解Thrift的远程调用。

2.准备工作

    首先使用Manven创建一个Java Project,并加入Thrift依赖

<dependencies>    <dependency>       <groupId>org.apache.thrift</groupId>       <artifactId>libthrift</artifactId>       <version>0.9.3</version>    </dependency>    <dependency>       <groupId>org.slf4j</groupId>       <artifactId>slf4j-log4j12</artifactId>       <version>1.7.12</version>    </dependency></dependencies>

3.编写Thrift文件

    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实体使用。

图1-1
图3-1

MovieOperator类
    这个类的代码看上去很多,但结构简单,主要成员如图3-2所示。其中Iface是我们刚刚定义的接口,Client和Processor分别用于客户端和服务端,里面封装着远程调用的请求和接收方法,调用过程中会用到getMovie_args和getMovie_result方法来分别获取参数和返回值。最后3个Async方法用于异步处理,本例不作详细介绍。

这里写图片描述
图3-2

4.客户端实现

    现在正式编写代码,首先创建客户端的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();        }    }}

5.接口服务实现

    编写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<String, Boolean> map = new HashMap<String, Boolean>();        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;    }}

6.服务端实现

    最后编写服务端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对象。

这里写图片描述
图6-1

    我们继续查看Args的继承结构,如图6-2所示。

这里写图片描述
图6-2

    跟踪父类到TServer中,发现AbstractServerArgs中定义了Server所需的基本数据:Transport、Process和Protocol,也就是刚刚配置的参数属性,如图6-3所示。

这里写图片描述
图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

7.小结

    最后再回顾一下,首先我们使用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中的确不能这样使用。

这里写图片描述

    可以参照下面的两张图,改成高一点的版本就行啦。

这里写图片描述

这里写图片描述

2 0
原创粉丝点击