半夏的博客

liangbogopher's blog

说说facebook thrift

Apache Thrift 是 Facebook 实现的一种高效的、支持多种编程语言的远程服务调用的框架。

前言

目前流行的服务调用方式有很多种,例如基于 SOAP 消息格式的 Web Service,基于 JSON 消息格式的 RESTful 服务等。其中所用到的数据传输方式包括 XML,JSON 等,然而 XML 相对体积太大,传输效率低,JSON 体积较小,新颖,但还不够完善。本文将介绍由 Facebook 开发的远程服务调用框架 Apache Thrift,它采用接口描述语言定义并创建服务,支持可扩展的跨语言服务开发,所包含的代码生成引擎可以在多种语言中,如 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk 等创建高效的、无缝的服务,其传输数据采用二进制格式,相对 XML 和 JSON 体积更小,对于高并发、大数据量和多语言的环境更有优势。本文将详细介绍 Thrift 的使用,并且提供丰富的实例代码加以解释说明,帮助使用者快速构建服务。

安装

在Mac上通过终端先安装brew,再在终端输入brew命令行来安装thrift的,当然还有其他的方式,大家可以去网上了解一下,而我用brew是对其偏爱,是因为brew作为Mac OSX上的软件包管理工具,能在Mac中方便的安装软件或者卸载软件, 只需要一个命令, 而不用麻烦的终端命令,非常方便,另外brew 又叫Homebrew。

brew install thrift

安装成功后,使用如下命令查看:

thrift -version

之前的版本为0.9.3,使用 brew upgrade thrift 更新,之后的版本为:0.10.0

Thrift的组成

1、类型系统以及 IDL(interface definition language) 编译器:负责由用户给定的 IDL 文件生成相应语言的接口代码
2、TProtocol:实现 RPC 的协议层,可以选择多种不同的对象串行化方式,如 JSON, Binary。
3、TTransport:实现 RPC 的传输层,同样可以选择不同的传输层实现,如socket, 非阻塞的 socket, MemoryBuffer 等。
4、TProcessor:作为协议层和用户提供的服务实现之间的纽带,负责调用服务实现的接口。
5、TServer:聚合 TProtocol, TTransport 和 TProcessor 几个对象。

Thrift架构

图中前面3个部分是:

1、你通过Thrift脚本文件生成的代码

2、图中的褐色框部分是你根据生成代码构建的客户端和处理器的代码

3、图中红色的部分是两端产生的计算结果

从TProtocol下面3个部分是Thrift的传输体系和传输协议以及底层I/O通信,Thrift并且提供 堵塞、非阻塞,单线程、多线程的模式运行在服务器上,还可以配合服务器/容器一起运行,可以和现有JEE服务器/Web容器无缝的结合。

数据类型

基本类型

  • bool:布尔值,true 或 false,对应 Java 的 boolean
  • byte:8 位有符号整数,对应 Java 的 byte
  • i16:16 位有符号整数,对应 Java 的 short
  • i32:32 位有符号整数,对应 Java 的 int
  • i64:64 位有符号整数,对应 Java 的 long
  • double:64 位浮点数,对应 Java 的 double
  • string:未知编码文本或二进制字符串,对应 Java 的 String

结构体类型

struct:定义公共的对象,类似于 C 语言中的结构体定义,在 Java 中是一个 JavaBean

容器类型

  • list:对应 Java 的 ArrayList
  • set:对应 Java 的 HashSet
  • map:对应 Java 的 HashMap

异常类型

  • exception:对应 Java 的 Exception

服务类型

  • service:对应服务的类

协议

Thrift 可以让用户选择客户端与服务端之间传输通信协议的类别,在传输协议上总体划分为文本 (text) 和二进制 (binary) 传输协议,为节约带宽,提高传输效率,一般情况下使用二进制类型的传输协议为多数,有时还会使用基于文本类型的协议,这需要根据项目 / 产品中的实际需求。常用协议有以下几种:

1、TBinaryProtocol – 二进制编码格式进行数据传输

TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory();

2、TCompactProtocol – 高效率的、密集的二进制编码格式进行数据传输

TCompactProtocol.Factory protocolFactory = new TCompactProtocol.Factory();

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

TJSONProtocol.Factory protocolFactory = new TJSONProtocol.Factory();

4、TTupleProtocol - 继承于TCompactProtocol,Struct的编解码时使用更省空间

5、TSimpleJSONProtocol – 只提供JSON只写的协议,适用于通过脚本语言解析

Transport传输层

1、Transport

  • TSocket - 使用阻塞式I/O进行传输,也是最常见的模式。使用经典的JDK Blocking IO的Transport实现。
  • TNonblockingSocket - 使用JDK NIO的Transport实现,读写的byte[]会每次被wrap成一个ByteBuffer

2、WrapperTransport 包裹一个底层的Transport,并利用自己的Buffer进行额外的操作。

  • TFramedTransport- 使用非阻塞方式,按块的大小,进行传输,类似于Java中的NIO。
  • TFastFramedTransport 与TFramedTransport相比,始终使用相同的Buffer,提高了内存的使用率。
  • TSaslClientTransport与TSaslServerTransport, 提供SSL校验
  • TZlibTransport- 使用执行zlib压缩,不提供Java的实现。

Processor层

1、TBaseProcessor
2、TMultiplexedProcessor:支持一个Server支持部署多个Service的情况

服务端类型

  • TSimpleServer - 单线程服务器端使用标准的阻塞式I/O。
  • TThreadPoolServer - 多线程服务器端使用标准的阻塞式I/O。
  • TNonblockingServer - 多线程服务器端使用非阻塞式I/O,并且实现了Java中的NIO通道。
  • TThreadedSelectorServer - 多线程半同步半异步模型。

编写thrift文件

namespace java me.ilbba.example.thrift.bean

struct Person {
    1: string name,
    2: i32 age
}

service HelloService {
    void sayHello(1: Person person)
}
thrift --gen java hello_service.thrift

执行如上命令,会生成gen-java的文件夹,里面会生成Person.java和HelloService.java。

编写HelloService.Iface实现类

public class HelloServiceImpl implements HelloService.Iface {

    @Override
    public void sayHello(Person person) throws TException {
        System.out.println("hello: " + person.getName() + ", your age: " + person.getAge());
    }
}

server端代码

@Slf4j
public class ThriftServiceServer {

    private Thread serverThread;

    private TServer server;

    public void initialize() throws Exception {
        log.info("Start thrift server begin...");
        //传输通道 - 非阻塞方式
        TNonblockingServerTransport serverTransport = new TNonblockingServerSocket(8090);
        //异步IO,需要使用TFramedTransport,它将分块缓存读取。
        TTransportFactory transportFactory = new TFramedTransport.Factory();
        //使用高密度二进制协议
        TProtocolFactory proFactory = new TCompactProtocol.Factory();
        //设置处理器
        TMultiplexedProcessor processor = new TMultiplexedProcessor();
        processor.registerProcessor("HelloService", new HelloService.Processor<HelloService.Iface>(new HelloServiceImpl()));
        //创建服务器
        server = new TThreadedSelectorServer(
                new TThreadedSelectorServer.Args(serverTransport)
                        .protocolFactory(proFactory)
                        .transportFactory(transportFactory)
                        .processor(processor).workerThreads(2 * Runtime.getRuntime().availableProcessors())
        );

        serverThread = new Thread(() -> {
            if (server != null && !server.isServing()) {
                server.serve();
            }
        });
        serverThread.start();
    }

    public void close() {
        log.info("Stop thrift server ...");
        if (serverThread != null && serverThread.isAlive()) {
            serverThread.interrupt();
        }
        if (server != null && server.isServing()) {
            server.stop();
        }
    }

    public static void main(String[] args) throws Exception {
        ThriftServiceServer server = new ThriftServiceServer();
        server.initialize();
    }
}

client端代码

public class HelloServiceClient {

    public void startClient() {
        TTransport transport;
        try {
            transport = new TSocket("localhost", 8090);
            TMultiplexedProtocol multiplexedProtocol = new TMultiplexedProtocol(
                    new TCompactProtocol(new TFramedTransport(transport)), "HelloService");

            HelloService.Client client = new HelloService.Client(multiplexedProtocol);
            transport.open();

            Person person = new Person();
            person.setName("bob");
            person.setAge(25);
            client.sayHello(person);

            transport.close();
        } catch (TTransportException e) {
            e.printStackTrace();
        } catch (TException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        HelloServiceClient client = new HelloServiceClient();
        client.startClient();
    }
}

这时启动ThriftServiceServer后,每次调用HelloServiceClient,查看server端日志输出。

代码可以参考github

Top