Thrift基本原理以及使用介绍

Thrift

初识Thrift

Thrift是一个跨语言通信的RPC软件,最初是由FaceBook开发的,现在是Apache的一个顶级项目。

Thrift概念:

Thrift 最初是由 Facebook 开发用做系统内各语言之间的 RPC 通信的一个可扩展且跨语言的软件框架,它结合了功能强大的软件堆栈和代码生成引擎,允许定义一个简单的定义文件中的数据类型和服务接口,以作为输入文件,编译器生成代码用来方便地生成RPC客户端和服务器通信的无缝跨编程语言。Thrift 是 IDL 描述性语言的一个具体实现,适用于程序对程序静态的数据交换,需要先确定好数据结构。Thrift 是完全静态化的,当数据结构发生变化时,必须重新编辑IDL文件、代码生成再编译载入的流程,跟其他IDL工具相比较可以视为是 Thrift 的弱项。Thrift 适用于搭建大型数据交换及存储的通用工具,在大型系统中的内部数据传输上相对于 JSON 和 XML 无论在性能、传输大小上有明显的优势。

什么是RPC

Remote Procedure Call Protocol 远程过程调用协议,我们 举个例子说明:

本地调用

public void invoke() {
  String param1="string1";
  String param2="string2";
  String res = getStr(param1,param2);
  System.out.println("res = " + res);
}

private String getStr(String str1, String str2) {
  return str1 + str2;
}

调用方和被调用方都在一个程序内部,属于进程内调用。CPU 在执行调用时切换去执行被调用函数,执行完后再切换回来执行后续的代码。对调用方而言,执行被调用函数时会阻塞(非异步情况下)直到调用函数执行完毕。

RPC调用

public void test() {
TestQry.Client client = getClient("192.168.4.222", 7800, 5000);
  String param1="string1";
  String param2="string2";
  String res = client.getStr(param1,param2);
  System.out.println("res = " + res);
}

这里进程间调用,但是调用方和被调用方不再同一个进程,甚至不同的服务器和机房。进程间调用需要通过网络来传输数据,调用方在执行 RPC 调用时会阻塞(非异步情况下)直到调用结果返回才继续执行后续代码。

小结

现在应该知道什么是RPC了,概括一下就是:RPC是一种通过网络从远程计算机程序上请求服务的方式,它使得开发包括网络分布式多程序在内的应用程序更加容易。

Thrift结构

  • 代码框架层

Thrift 定义的服务接口描述文件生成的客户端和服务器端代码框架。

  • 数据读写操作层

是根据Thrift 文件生成代码实现数据的读写操作。Thrift允许定义一个简单的定义文件中的数据类型和服务接口,以作为输入文件,编译器生成代码用来方便地生成RPC客户端和服务器通信的无缝跨编程语言。

Thrift通信

一张图说明服务端启动与提供服务的流程:

程序调用了 TThreadPoolServer 的 serve() 方法后,server 进入阻塞监听状态,其阻塞在 TServerSocket 的accept()方法上。当接收到来自客户端的消息后,服务器发起一个新线程处理这个消息请求,原线程再次进入阻塞状态。在新线程中,服务器通过 TBinaryProtocol 协议读取消息内容,调用 HelloServiceImpl 的helloVoid() 方法,并将结果写入 helloVoid_result 中传回客户端。

 再来一张图说明服务端启动服务以后,客户端请求服务的流程:

程序调用了 Hello.Client 的 helloVoid() 方法,在 helloVoid() 方法中,通过 send_helloVoid() 方法发送对服务的调用请求,通过 recv_helloVoid() 方法接收服务处理请求后返回的结果。

 

Thrift数据类型

它支持这几大数据结构:基本类型、结构体和异常类型、容器类型、服务类型。

基本类型

  • ​ bool: 布尔值(true or false)

  • ​ byte: 有符号字节

  • ​ i16: 16位有符号整型

  • ​ i32: 32位有符号整型

  • ​ i64: 64位有符号整型

  • ​ double:64位浮点型

  • ​ string: 二进制字符串

结构体类型

struck UserDemo{
    1: i32 id;
    2: string name;
    3: i32 age = 25;
    4: string phone
}

结构体有几点要注意:

1:在java中,结构体会被转换成对象的类;

2:其成员都有明确的类型;

3:成员都是被正整数编号过的,这个编号不能重复;

4:在结构体中,可以直接设置默认值;

服务类型

service querySrv {
    /** 根据名字和年龄来查找用户 */
    UserDemo qryUser(1:string name, 2:i32 age);
​
    /** 根据id查找对应的手机号 */
    string queryPhone(1:i32 id);
}

服务类型说明:服务类型语法等同于Java中的接口。

小结

除了上面所提到的四大类型外,Thrift还支持枚举和常量类型。当然,它也有不支持的类型,典型的就是Date类型不支持。

Thrit组件

下面我们将围绕这三个组件展开介绍,内容略显枯燥,但是是使用Thrift的关键。

Protocol:数据通信协议

定义了消息是怎样序列化的,主要有二进制、文本。

常见的有:

◆TBinaryProtocol:这是Thrift的默认协议,使用二进制编码格式进行数据传输。

◆TCompactProtocol:压缩格式。

◆TJSONProtocol:以JSON数据编码协议进行数据传输。

◆TDebugProtocol:常常用以编码人员测试,以文本的形式展现方便阅读。

用户可以根据自己的实际需求选择合适的类型,生产环境一般用二进制类型的多

Transport:传输方式

定义了消息是怎样在客户端和服务器端之间通信的。

常见的有:

◆TSocket::采用TCP Socket进行数据传输。

◆ TFileTransport:文件(日志)传输类,允许client将文件传给server,允许server 将收到的数据写到文件中。

◆ THttpTransport:采用Http传输协议进行数据传输

◆ TZlibTransport:压缩后对数据进行传输,或者将收到的数据解压

下面几个类主要是对上面几个类的修饰(采用装饰模式),提高传输效率

◆ TBufferedTransport:对某个Transport对象操作的数据进行buffer,即从buffer中读取数据进行传输,或者将数据直接写入buffer◆ TFramedTransport:以frame(帧)为单位进行传输,非阻塞式服务中使用。同 TBufferedTransport类似,也会对相关数据进行buffer,同时,它支持定长数据发送和接收。◆ TMemoryBuffer:从一个缓冲区中读写数据◆ TTransport是所有Transport类的父类,为上层提供了统一的接口而且通过 TTransport 即可访问各个子类不同实现,类似多态。

Server Type: 服务端类型

server 用于从 transport 接收序列化的消息,根据 protocol 反序列化之,调用用户定义的消息处理器,并序列化消息处理器(接口实现类)的响应,然后再将它们写回 transport。

常用的server实现有:

◆ TSimpleServer:接受一个连接,处理连接请求,直到客户端关闭了连接,它才回去接受一个新的连接。正因为它只在一个单独的线程中以阻塞 I/O 的方式完成这些工作,所以它只能服务一个客户端连接,其他所有客户端在被服务器端接受之前都只能等待。

◆ TSimpleServer:主要用于测试目的,不要在生产环境中使用它。

◆ TNonblockingServer:阻塞在多个连接上,而不是阻塞在单一的连接上。 server 可同时服务多个客户端。

缺点:在业务处理上还是采用单线程顺序来完成,在业务处理比较复杂、耗时的时候,例如某些接口函数需要读取数据库执行时间较长,此时该模式效率也不高,因为多个调用请求任务依然是顺序一个接一个执行。

◆ THsHaServer:是TNonblockingServer类的子类,主线程负责数据读取,然后引入一个线程池来专门进行业务处理,缓解TNonblockingServer类的缺点。

缺点:主线程需要完成对所有socket的监听以及数据读写的工作,当并发请求数较大时,且发送数据量较多时,监听socket上新连接请求不能被及时接受。

◆ TThreadPoolServer TThreadPoolServer模式采用阻塞socket方式工作,主线程负责阻塞式监听“监听socket”中是否有新socket到来,数据读取和业务处理都交由线程池完成,主线程只负责监听新连接。

缺点:线程池模式的处理能力受限于线程池的工作能力,当并发请求数大于线程池中的线程数时,新请求也只能排队等待。

◆ TThreadedSelectorServer: TThreadedSelectorServer模式是目前Thrift提供的最高级的模式。看图

 

TThreadedSelectorServer对于大部分应用场景性能都不会差,因此,如果实在不知道选择哪种工作模式,使用TThreadedSelectorServer就可以。

如何使用Thrift

Step1

使用IDL语言建立.thrift文件,示例:

/**
   文件名 TestQry.thrift
   实现的功能:创建一个查询结果struct和一个服务接口service。
   基于:thrift-0.9.2
*/
namespace java com.thrift
​
struct QryResult {
    /**
        返回码:1成功,0失败
    */
    1:i32 code;
    
    /**
       响应信息
    */
    2:string msg;
}
​
service TestQry {
    /**
    测试查询的接口,当qryCode值为1时候返回”成功”的响应信息,qryCode值为其他值时返回”失败”
    */
    QryResult qryTest(1:i32 qryCode)
}

Step2

利用Thrift代码生成程序生成相应的java代码

1:将我们建立的TestQry.thrift文件与thrift-0.9.2.exe放在同一目录,如下:

2:执行命令,生成相应的java代码: thrift.exe -gen java TestQry.thrift

Step3

生成的java代码导入到你的maven项目中,添加相应的依赖包

Step4

创建QueryImp.java实现TestQry.Iface接口,这里写业务。

/**
   文件名 TestQry.thrift
   实现的功能:创建一个查询结果struct和一个服务接口service。
   基于:thrift-0.9.2
*/
namespace java com.thrift
​
struct QryResult {
    /**
        返回码:1成功,0失败
    */
    1:i32 code;
    
    /**
       响应信息
    */
    2:string msg;
}
​
service TestQry {
    /**
    测试查询的接口,当qryCode值为1时候返回”成功”的响应信息,qryCode值为其他值时返回”失败”
    */
    QryResult qryTest(1:i32 qryCode)
}

Step5

创建ThriftServerDemo实现服务端,这里用TNonblockingServerSocket

public class ThriftServerDemo {
    private final static int DEFAULT_PORT = 30001;
    private static TServer server = null;
    
    public static void main(String[] args) {
        try {
            TNonblockingServerSocket socket = new TNonblockingServerSocket(DEFAULT_PORT);
            //1. 基于Handler创建Processer
            TestQry.Processor processor = new TestQry.Processor(new QueryImp());
            TNonblockingServer.Args arg = new TNonblockingServer.Args(socket);
            //2.创建protocol
            arg.protocolFactory(new TBinaryProtocol.Factory());
            //3.创建tansport
            arg.transportFactory(new TFramedTransport.Factory());
            arg.processorFactory(new TProcessorFactory(processor));
            //4.基于三者创建server
            server = new TNonblockingServer(arg);
            //5.运行server
            server.serve();
        } catch (TTransportException e) {
            e.printStackTrace();
        }
    }
}

Step6

public class ThriftClientDemo {
    private final static int DEFAULT_QRY_CODE = 1;
    public static void main(String[] args) {
        //1.创建transport
        TTransport tTransport = getTTransport();
        //2.创建protocol
        TProtocol protocol = new TBinaryProtocol(tTransport);
        //3.创建client
        TestQry.Client client = new TestQry.Client(protocol);
        QryResult result = null;
        
        try {
            //运行client的方法
            result = client.qryTest(DEFAULT_QRY_CODE);
        } catch (TException e) {
            e.printStackTrace();
        }
        System.out.println("code="+result.code+" "+"msg="+result.msg);
    }
​
    public static TTransport getTTransport() {
        TTransport tTransport = getTTransport("127.0.0.1", 30001, 5000);
        if (!tTransport.isOpen()){
            try {
                tTransport.open();
            } catch (TTransportException e) {
                e.printStackTrace();
            }
        }
        return tTransport;
    }
​
    private static TTransport getTTransport(String host, int port, int timeout) {
        final TSocket tSocket = new TSocket(host, port, timeout);
        final TTransport transport = new TFramedTransport(tSocket);
        return transport;
    }
}

Step7

所有准备工作都已经做好了,接下来我们就来进行 Client 和 Server 的通信。

先运行 ThriftServerDemo 启动 Server,然后运行 ThriftClientDemo.java 创建 Client进行调用,以上步骤都是我自己写过的,可以得到结果。

结语

Thrift有什么优势?

Thrift是一个跨语言通信的服务框架,不同语言开发的程序可以通过Thrift来进行通信:通过编译一个后缀名为.thrift的文件来生成指定语言的代码,通过生成的代码我们就可以编写出跨语言通信的代码了:如服务端是用Thrift生成的Java代码,客户端使用Thrift生成的C++/C#代码,用Thtift可以完成C++代码到Java代码的调用,而不需要关心其他如网络通信等内容,可以让开发人员专注于业务实现。

Thrift为什么这么好用?

网络编程需要关注很多数据传输中的细节,比如数据如何序列化、如何在字节数组里建立结构、如何在两端解析字节数组、如何处理Handler里的事件状态、如何把多个Handler按顺序串起来,Thrift掩盖了数据传输这件事情,开发者使用的时候就是纯纯的RPC的使用感受。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章