Thrift介绍

由于我们组要求使用的是Thrift0.8.0版本,下面以这个版本为例来讲解如何安装配置Thrift。

一、安装

下载地址:http://archive.apache.org/dist/thrift/0.8.0/thrift-0.8.0.tar.gz

tar -zxvf thrift-0.8.0.tar.gz
cd thrift-0.8.0
./configure --prefix=/usr/local/ --with-boost=/usr/local --with-libevent=/usr/local --without-csharp --without-erlang --without-go --without-haskell --without-ruby --without-cpp --without-perl --without-php --without-php_extension --without-python
make
sudo make install
# 检查是否安装成功,出现下面的情况说明已经正确安装
thrift -version
Thrift version 0.8.0

二、Thrift简介

Thrift是一个轻量级的、跨语言的远程服务调用框架。Thrift支持不同的编程语言,包括C++、Java、Python、PHP、Ruby等。

三、Thrift软件栈

Thrift软件栈从下到上依次是:传输层、协议层、处理层和服务层。

  • 传输层:TTransport,负责直接从网络读取和写入数据,定义了具体的网络传输协议,比如TCP/IP等;

  • 协议层:TProtocal,定义了数据传输格式,负责网络传输数据的序列化和反序列化,比如说json、XML、二进制数据等;

  • 处理层:TProcessor,处理层是由具体的IDL(接口描述语言)生成的,封装了具体的底层网络传输和序列化方式,并委托给用户实现的handler进行处理;

  • 服务层:TServer,整合上述组件,提供具体的网络线程/IO模型,形成最终的服务,然后接收Client的请求,并转到某个TProcessor上进行请求处理。

Thrift的传输层

创建的传输层有以下几种:

  1. TSocket:使用阻塞式IO进行传输,是最常见的模式;

  2. TNonblockingTransport:使用非阻塞方式,用于构建异步客户端

  3. TFramedTransport:使用非阻塞方式,按块的大小进行传输,类似于Java中的NIO

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

  5. THttpTransport:采用Http传输协议进行数据传输。

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

  7. TZlibTransport:与其他的TTransport配合使用,压缩后对数据进行传输,或者将收到的数据解压

Thrift的服务端类型

  1. TSimpleServer:单线程服务器端,使用标准的阻塞式I/O,只在演示的时候用,实际工作中很少用到;

  2. TThreadPoolServer:多线程服务器端,使用标准的阻塞式I/O,主线程负责监听新连接,其他处理交给单独的线程池;

  3. TNonblockingServer:单线程服务器端,使用非阻塞式I/O,一个线程循环处理监听socket,处理连接,读,写请求;

  4. THsHaServer:半同步半异步服务器端,基于非阻塞式IO读写和多线程工作任务处理,对TNonblockingServer的优化,读请求以及后续的业务处理交给单独的线程池来做;

  5. TThreadedSelectorServer:多线程选择器服务器端,对THsHaServer异步IO模型上进行增强,最高级的模式,大部分场景下性能都不会差,如果不知道选哪个就选这个。

四、Thrift数据类型

Thrift主要有下面5种类型:

1. 基本类型:

  • bool: 布尔值

  • byte: 8位有符号整数

  • i16: 16位有符号整数

  • i32: 32位有符号整数

  • i64: 64位有符号整数

  • double: 64位浮点数

  • string: UTF-8编码的字符串

  • binary: 二进制串

注意:Thrift不支持无符号整型,因为很多编程语言本身就不存在无符号整型,如java。

2. 结构体类型:

  • struct: 定义的结构体对象

3. 容器类型:

  • list: 有序元素列表

  • set: 无序无重复元素集合

  • map: 有序的key/value集合

4. 异常类型:

  • exception: 异常类型

5. 服务类型:

  • service: 具体对应服务的类

此外thrift还支持常量和枚举。

五、Thrift实战

5.1 编写IDL文件

hello.thrift

service HelloWorldService {
  string say(1: string username)
}

5.2 编译IDL文件生成Java文件

thrift -gen java hello.thrift

于未指定代码生成的目标目录,生成的类文件默认存放在gen-java目录下。这里会生成4个重要的内部接口/类:

  • Iface服务端通过实现HelloWorldService.Iface接口,向客户端的提供具体的同步业务逻辑。

  • AsyncIface服务端通过实现HelloWorldService.Iface接口,向客户端的提供具体的异步业务逻辑。

  • Client客户端通过HelloWorldService.Client的实例对象,以同步的方式访问服务端提供的服务方法。

  • AsyncClient客户端通过HelloWorldService.AsyncClient的实例对象,以异步的方式访问服务端提供的服务方法。

5.3 代码实现

新建maven工程,pom中添加依赖

<dependencies>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.6.6</version>
  </dependency>
  <dependency>
    <groupId>org.apache.thrift</groupId>
    <artifactId>libthrift</artifactId>
    <version>0.8.0</version>
  </dependency>
</dependencies>

将生成的HelloWorldService.java源文件拷贝进项目源文件目录中,并实现HelloWorldService.Iface的定义的say()方法。

HelloWorldServiceImpl.java

import org.apache.thrift.TException;

public class HelloWorldServiceImpl implements HelloWorldService.Iface {
    @Override
    public String say(String username) throws TException {
        return "Hello " + username;
    }
}

服务端实现:SimpleServer.java

import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;

import java.net.ServerSocket;

public class SimpleServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(9999);
        TServerSocket serverTransport = new TServerSocket(serverSocket);
        HelloWorldService.Processor processor =
                new HelloWorldService.Processor<HelloWorldService.Iface>(new HelloWorldServiceImpl());

        TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory();
        TSimpleServer.Args tArgs = new TSimpleServer.Args(serverTransport);
        tArgs.processor(processor);
        tArgs.protocolFactory(protocolFactory);

        // 简单的单线程服务模型 一般用于测试
        TServer tServer = new TSimpleServer(tArgs);
        System.out.println("Running Simple Server");
        tServer.serve();
    }
}

客户端实现:SimpleClient.java

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;

public class SimpleClient {
    public static void main(String[] args) {
        TTransport transport = null;
        try {
            transport = new TSocket("127.0.0.1" , 9999, 5000);
            TProtocol protocol = new TBinaryProtocol(transport);
            HelloWorldService.Client client = new HelloWorldService.Client(protocol);
            transport.open();

            String result = client.say("nik");
            System.out.println("Result =: " + result);
        } catch (TException e) {
            e.printStackTrace();
        } finally {
            if (null != transport) {
                transport.close();
            }
        }
    }
}

依次运行服务端和客户端会出现以下结果:

Running Simple Server

Result =: Hello nik

5.4 总结

从使用thrift的过程中可以总结thrift实现服务端和客户端逻辑的基本思路。

Thrift实现服务端:

  • 实现服务处理接口impl

  • 创建TProcessor

  • 创建TServerTransport

  • 创建TProtocol

  • 创建TServer

  • 启动Server

Thrift实现客户端:

  • 创建Transport

  • 创建TProtocol

  • 基于TTransport和TProtocol创建Client

六、遇到的问题

1. 运行时报错Class JavaLaunchHelper is implemented in both...

【错误描述】

idea run服务端的时候报错:objc[79886]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home/bin/java (0x106b914c0) and /Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x108e7e4e0). One of the two will be used. Which one is undefined.

【问题解决】

这是Mac版idea的一个bug,jdk1.8及以上已经修复了,因为项目要求是jdk1.7,并且这个错误不影响正常使用可以忽略不计了。

2. 启动server时报错SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".

【错误描述】

启动server的时候报错:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

【问题解决】

上面的意思是,在运行时,你没有做日志的实现(或者说日志的绑定),所以slf4j简简单单的使用了一个什么也不会做的空实现。 为了看到正确的输出,你应该尝试使用一个简单(simple)的实现,这个实现根本不需要任何配置!只要回到pom.xml然后添加如下配置:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.6.6</version>
</dependency>

参考:

【thrift概述与入门】https://juejin.im/post/6844903622380093447

【thrift基本原理】https://www.jianshu.com/p/5e3a4ab838a3

Last updated