Thrift应用实践—基本篇

1.简介

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

2.准备工作

    首先使用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>

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

Thrift应用实践—基本篇_第1张图片
图3-1

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

Thrift应用实践—基本篇_第2张图片
图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 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;
    }
}

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对象。

Thrift应用实践—基本篇_第3张图片
图6-1

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

Thrift应用实践—基本篇_第4张图片
图6-2

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

Thrift应用实践—基本篇_第5张图片
图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中的确不能这样使用。

Thrift应用实践—基本篇_第6张图片

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

Thrift应用实践—基本篇_第7张图片

Thrift应用实践—基本篇_第8张图片

你可能感兴趣的:(RPC)