1. What is thrift?

Thrift是一个跨语言的服务部署框架,最初由Facebook于2007年开发,2008年进入Apache开源项目。跨平台通信中thrift可以作为二进制的高性能的通讯中间件,支持数据(对象)序列化和多种类型的RPC服务。

 

2. thrift为我们做了什么?

首先我们需要先了解下任何RPC的解决方案都包含如下几层实现:

· 服务层(service):RPC接口定义与实现

· 协议层(protocol):RPC报文格式和数据编码格式

· 传输层(transport):实现底层的通信(如 socket)以及系统相关的功能(如事件循环、多线程)

 

Thrift通过一个中间语言(IDL, 接口定义语言)来定义RPC的接口和数据类型,然后通过一个编译器生成不同语言的代码(目前支持C++,Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk等),并由生成的代码负责RPC协议层和传输层的实现。我们只需要去实现具体的接口实现就可以了。

 

Thrift实际上是实现了请求响应的C/S模式,通过代码生成工具将接口定义文件生成服务器端代码,从而实现服务端和客户端跨语言的支持。用户在Thirft描述文件中声明自己的服务,这些服务经过编译后会生成相应语言的代码文件,然后用户实现服务便可以了。

 

3. thrift基本概念

数据类型

     * Base Types:基本类型

     * Struct:结构体类型

     * Container:容器类型,即List、Set、Map

     * Exception:异常类型

     * Service: 定义对象的接口,和一系列方法

 

协议层类型

Thrift可以让你选择客户端与服务端之间传输通信协议的类别,在传输协议上总体上划分为文本(text)和二进制(binary)传输协议, 为节约带宽,提供传输效率,一般情况下

 

使用二进制类型的传输协议为多数,但有时会还是会使用基于文本类型的协议,这需要根据项目/产品中的实际需求:

    * TBinaryProtocol – 二进制编码格式进行数据传输。

    * TCompactProtocol – 这种协议非常有效的,使用Variable-Length Quantity (VLQ) 编码对数据进行压缩。

    * TJSONProtocol – 使用JSON的数据编码协议进行数据传输。

    * TSimpleJSONProtocol – 这种节约只提供JSON只写的协议,适用于通过脚本语言解析

    * TDebugProtocol – 在开发的过程中帮助开发人员调试用的,以文本的形式展现方便阅读。

 

传输层类型

    * TSocket – 使用堵塞式I/O进行传输,也是最常见的模式。

    * THttpTransport – 采用Http传输协议进行数据传输

    * TFileTransport – 顾名思义按照文件的方式进程传输,虽然这种方式不提供Java的实现,但是实现起来非常简单。

    * TZlibTransport – 使用执行zlib压缩,不提供Java的实现。

 

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

    TBufferedTransport – 对某个Transport对象操作的数据进行buffer,即从buffer中读取数据进行传输,或者将数据直接写入buffer

    TFramedTransport – 以frame为单位进行传输,非阻塞式服务中使用。同TBufferedTransport类似,也会对相关数据进行buffer,同时,它支持定长数据发送和接收。

    TMemoryBuffer – 从一个缓冲区中读写数据,使用内存I/O,就好比Java中的ByteArrayOutputStream实现。

 

服务端类型

    * TSimpleServer– 简单的单线程服务模型,常用于测试

    * TThreadedServer – 多线程服务模型,使用阻塞式IO,每个请求创建一个线程。

    * TThreadPoolServer – 线程池服务模型,使用标准的阻塞式IO,预先创建一组线程处理请求。

    * TNonblockingServer – 多线程服务模型,使用非阻塞式IO(需使用TFramedTransport数据传输方式)

 

4. Thrift 架构

Thrift 包含一个完整的堆栈结构用于构建客户端和服务器端。下图描绘了Thrift的整体架构。

 

thrift java 命令 java thrift框架_apache

thrift java 命令 java thrift框架_apache_02

 

如图所示,图中黄色部分是用户实现的业务逻辑,褐色部分是根据 Thrift 定义的服务接口描述文件生成的客户端和服务器端代码框架,红色部分是根据 Thrift 文件生成代码实现数据的读写操作。红色部分以下是 Thrift 的传输体系、协议以及底层 I/O 通信,使用 Thrift 可以很方便的定义一个服务并且选择不同的传输协议和传输层而不用重新生成代码。

 

Thrift 服务器包含用于绑定协议和传输层的基础架构,它提供阻塞、非阻塞、单线程和多线程的模式运行在服务器上,可以配合服务器 / 容器一起运行,可以和现有的 J2EE 服务器 /Web 容器无缝的结合。

 

5. 安装

http://thrift.apache.org/ 官网下载,需要用ant编译成lib,最终实际项目中只需要加入如下jar包:

* libthrift.jar

* slf4j-api-1.5.8.jar

* slf4j-log4j12-1.5.8.jar

* log4j-1.2.15.jar

 

6. 简单实例

创建一个简单的Helloworld。首先根据Thrift的语法规范编写脚本文件Hello.thrift,代码如下:

 

namespace java service.demo 
 service Hello{ 
  string helloString(1:string para) 
  i32 helloInt(1:i32 para) 
  bool helloBoolean(1:bool para) 
  void helloVoid() 
  string helloNull() 
 }

 

其中定义了服务 Hello 的五个方法,每个方法包含一个方法名,参数列表和返回类型。每个参数包括参数序号,参数类型以及参数名。

 

使用thrift.exe -gen java Hello.thrift生成两个gen-java文件夹,里面就有我们生成的服务器端接口Hello.java了。

 

创建 HelloServiceImpl.java 文件并实现 Hello.java 文件中的 Hello.Iface 接口,代码如下:

 

package service.demo; 
 import org.apache.thrift.TException; 
 public class HelloServiceImpl implements Hello.Iface { 
    @Override 
    public boolean helloBoolean(boolean para) throws TException { 
        return para; 
    } 
    @Override 
    public int helloInt(int para) throws TException { 
        try { 
            Thread.sleep(20000); 
        } catch (InterruptedException e) { 
            e.printStackTrace(); 
        } 
        return para; 
    } 
    @Override 
    public String helloNull() throws TException { 
        return null; 
    } 
    @Override 
    public String helloString(String para) throws TException { 
        return para; 
    } 
    @Override 
    public void helloVoid() throws TException { 
        System.out.println("Hello World"); 
    } 
 }

 

创建服务器端实现代码,将 HelloServiceImpl 作为具体的处理器传递给 Thrift 服务器,代码如下:

 

 

package service.server; 
 import org.apache.thrift.TProcessor; 
 import org.apache.thrift.protocol.TBinaryProtocol; 
 import org.apache.thrift.protocol.TBinaryProtocol.Factory; 
 import org.apache.thrift.server.TServer; 
 import org.apache.thrift.server.TThreadPoolServer; 
 import org.apache.thrift.transport.TServerSocket; 
 import org.apache.thrift.transport.TTransportException; 
 import service.demo.Hello; 
 import service.demo.HelloServiceImpl; 
 
 public class HelloServiceServer { 
    /** 
     * 启动 Thrift 服务器
     * @param args 
     */ 
    public static void main(String[] args) { 
        try { 
            // 设置服务端口为 7911 
            TServerSocket serverTransport = new TServerSocket(7911); 
            // 设置协议工厂为 TBinaryProtocol.Factory 
            Factory proFactory = new TBinaryProtocol.Factory(); 
            // 关联处理器与 Hello 服务的实现
            TProcessor processor = new Hello.Processor(new HelloServiceImpl()); 
            TServer server = new TThreadPoolServer(processor, serverTransport, 
                    proFactory); 
            System.out.println("Start server on port 7911..."); 
            server.serve(); 
        } catch (TTransportException e) { 
            e.printStackTrace(); 
        } 
    } 
 }

创建客户端实现代码,调用 Hello.client 访问服务端的逻辑实现,代码如下:

 

package service.client; 
 import org.apache.thrift.TException; 
 import org.apache.thrift.protocol.TBinaryProtocol; 
 import org.apache.thrift.protocol.TProtocol; 
 import org.apache.thrift.transport.TSocket; 
 import org.apache.thrift.transport.TTransport; 
 import org.apache.thrift.transport.TTransportException; 
 import service.demo.Hello; 
 
 public class HelloServiceClient { 
 /** 
     * 调用 Hello 服务
     * @param args 
     */ 
    public static void main(String[] args) { 
        try { 
            // 设置调用的服务地址为本地,端口为 7911 
            TTransport transport = new TSocket("localhost", 7911); 
            transport.open(); 
            // 设置传输协议为 TBinaryProtocol 
            TProtocol protocol = new TBinaryProtocol(transport); 
            Hello.Client client = new Hello.Client(protocol); 
            // 调用服务的 helloVoid 方法
            client.helloVoid(); 
            transport.close(); 
        } catch (TTransportException e) { 
            e.printStackTrace(); 
        } catch (TException e) { 
            e.printStackTrace(); 
        } 
    } 
 }

 

代码编写完后运行服务器,再启动客户端调用服务 Hello 的方法 helloVoid,在服务器端的控制台窗口输出“Hello World”。