简介

Thrift是一个RPC框架,由facebook开发,07年四月开放源码,08年5月进入Apache孵化器。它支持可扩展且跨语言的服务的开发,它结合了功能强大的软件堆栈和代码生成引擎,以构建在C++、Java、Python、PHP、Ruby、Erlang、Perl、Haskell、C#、Cocoa、JavaScript、Node.js、Smalltalk and OCaml等等编程语言间无缝结合的、高效的服务。Thrift允许你定义一个简单的定义文件中的数据类型和服务接口。以作为输入文件,编译器生成代码用来方便地生成RPC客户端和服务器通信的无缝跨编程语言。

基本结构

  • Transport层:抽象了数据在网络中的传输。
  • Protocol层:定义了数据的序列化、反序列化方式。常用的格式有:二进制、压缩格式和json格式。
  • Processor层:Thrift中最关键的一层,它包括thrift文件生成的接口,以及这些接口应对的实现。
  • Server层:将所有这些(Transport、Protocol与Processor)封装在一起,对外提供服务。

例子

Thrift的安装

brew install thrift

项目结构

├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── thrift
│   │   │           ├── client
│   │   │           │   └── Client.java
│   │   │           ├── demo
│   │   │           │   ├── HelloWorldImpl.java
│   │   │           │   └── HelloWorldService.java
│   │   │           └── server
│   │   │               └── Server.java
│   │   ├── resources
│   │   └── thrift
│   │       └── HelloWorld.thrift
│   └── test
│       └── java

首先创建thrift文件夹及其路径下的HelloWorld.thrift,内容如下:

namespace java com.thrift.demo

service HelloWorldService {
    string sayHello(1:string usrename);
}

随后在thrift下启动终端并输入:

thrift --gen java HelloWorld.thrift

随后在原地生成com.thrift.demo.HelloWorldService.java,我们将其放到java路径下并新建两个包server与client。

感兴趣可以观察一下HelloWorldService.java的内容,竟然自动生成了近900行的代码。

随后在com.thrift.demo包下新建HelloWorldImpl,这个是我们实现业务逻辑的类。

package com.thrift.demo;

import org.apache.thrift.TException;

public class HelloWorldImpl implements HelloWorldService.Iface{

    public HelloWorldImpl() {}

    public String sayHello(String usrename) throws TException {
        return "Hi,"+usrename+"Welcome to my website";
    }
}

值得注意的是,这个类实现了生成的HelloWorldService下的一个子接口。随后将方法补全。

下面给出Server以及Client的实现逻辑,解释内容以注释的形式给出:

package com.thrift.server;

import com.thrift.demo.HelloWorldImpl;
import com.thrift.demo.HelloWorldService;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;

public class Server {

    public final static int SERVER_PORT = 7099;
    private static String SERVER_IP = "localhost";

    public static void startServer() {
        try {
            System.out.println("HelloWorld Server start...");

            // 定义ServerSocket
            TServerSocket serverSocket = new TServerSocket(SERVER_PORT);
            // 定义参数对象并绑定到ServerSocket
            TServer.Args args = new TServer.Args(serverSocket);
            /* HelloWorldService.Processor在定义时实现了org.apache.thrift.TProcessor,其构造函数需要传入一个iFace
             * 即我们的具体需求实现
             */
            TProcessor processor = new HelloWorldService.Processor(new HelloWorldImpl());
            // 定义一个处理二进制协议的工厂
            TBinaryProtocol.Factory portFactory = new TBinaryProtocol.Factory(true, true);
            // 参数绑定处理器以及工厂
            args.processor(processor);
            args.protocolFactory(portFactory);
            // 定义工厂,还有TThreadPoolServer的线程池版本
            TServer server = new TSimpleServer(args);
            // 启动服务
            server.serve();
        }
        catch (Exception e) {
            System.out.println("Server start error");
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server.startServer();
    }

}
package com.thrift.client;

import com.thrift.demo.HelloWorldService;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;

public class Client {

    public static final int SERVER_PORT = 7099;
    public static final String SERVER_IP = "localhost";

    public static void startClient(String username) {
        // 定义一个运输层实体
        TTransport tTransport = null;
        try {
            // 绑定ip和端口
            tTransport = new TSocket(SERVER_IP, SERVER_PORT);
            // 定义对应的协议
            TProtocol protocol = new TBinaryProtocol(tTransport);
            // 生成对应的客户端并绑定协议
            HelloWorldService.Client client = new HelloWorldService.Client(protocol);
            // 打开实体
            tTransport.open();
            // 进行远程调用
            String result = client.sayHello(username);
            System.out.println("Thrift client result=" + result);
            Thread.sleep(2000);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Client.startClient("zhouxiang");
    }

}

测试

先运行Server,再运行Client可从控制台观察到结果:

Thrift client result=Hi,zhouxiangWelcome to my website

Thrift支持的协议

Thrift可以让你选择客户端与服务端之间数据的传输通信协议,但要保证客户端和服务端的协议一致。在传输协议上总体上划分为文本和二进制传输协议,为节约带宽和提高传输效率,一般情况下使用二进制类型的传输协议较多,但有时会还是会使用基于文本类型的协议,这需要根据实际需求来看。

  • TBinaryProtocol:二进制编码格式进行数据传输
  • TCompactProtocol:这种协议非常有效,使用Variable-Length Quantity (VLQ) 编码对数据进行压缩
  • TJSONProtocol:使用JSON的数据编码协议进行数据传输
  • TSimpleJSONProtocol:这种节约只提供JSON只写的协议,适用于通过脚本语言解析
  • TDebugProtocol:在开发的过程中帮助开发人员调试用的,以文本的形式展现方便阅读

Thrift支持的服务端

Thrift在服务端提供了很多不同类型的选择:

  • TSimpleServer:TSimpleServer是单线程阻塞IO的实现,仅适用于demo
  • TThreadPoolServer:顾名思义,TThreadPoolServer内部使用一个线程池来处理客户端的请求。它使用一个专门的线程来接收请求,一旦接收到请求就会放入ThreadPoolExecutor中的一个线程池处理,性能表现优异
  • TNonblockingServer:单线程非阻塞IO的实现,通过java.nio.channels.Selector的select()接收连接请求,但是处理消息仍然是单线程,不可用于生产
  • THsHaServer:THsHaServer继承了TNonblockingServer,不同之处在于THsHaServer内部使用了一个线程池来处理请求。THsHaServer相比较于TNonblockingServer类似TThreadPoolServer相比较于TSimpleServer。另外,当使用TNonblockingServer或者THsHaServer时,必须使用TFramedTransport来封装一下原始的transport
  • TThreadedSelectorServer:是thrift 0.8引入的实现,处理请求也使用了线程池,比THsHaServer有更高的吞吐量和更低的时延