Thrift学习

Apache Thrift是一种高效的远程服务调用框架。

简介

官方介绍:The Apache Thrift software framework, for scalable cross-language services development, combines a software stack with a code generation engine to build services that work efficiently and seamlessly between C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi and other languages.

由 Facebook 开发的远程服务调用框架 Apache Thrift,它采用接口描述语言定义并创建服务,支持可扩展的跨语言服务开发,所包含的代码生成引擎可以在多种语言中,如 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk 等创建高效的、无缝的服务,其传输数据采用二进制格式,相对 XML 和 JSON 体积更小,对于高并发、大数据量和多语言的环境更有优势。

RPC:远程过程调用。两个应用A和B部署在不同机器上,应用A需要调用应用B的方法/函数,由于无法在同一内存空间,无法直接调用,需要通过网络来表达调用的语义和传达调用的数据。 问题:

  1. 通讯问题:两台机器之间主要使用TCP连接进行通讯。可以长连接,可以通讯完毕立即断掉连接,可以多个共享同一个TCP连接;

  2. 寻址:通讯需要知道地址(如ip)、端口号、方法名等信息;

  3. 序列化和反序列化:由于底层TCP连接使用二进制来传输,因此需要序列化和反序列化。

需要RPC的原因:项目过大,多个服务或应用无法在一个进程上运行,甚至无法在同一机器完成本地调用,因此需要将应用部署到不同机器,方法/函数内部写好网络通讯过程,但调用方感受不到。

Demo

1.首先编写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()
}

每个方法包含一个方法名,参数列表和返回类型。每个参数包括参数序号,参数类型以及参数名。

2.使用Thrift工具编译Hello.thrift生成Hello.java

thrift -r --gen java Hello.thrift

3.创建maven项目 在pom.xml中加上如下依赖

    <dependency>
      <groupId>org.apache.thrift</groupId>
      <artifactId>libthrift</artifactId>
      <version>0.8.0</version>
    </dependency>

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.5</version>
    </dependency>

4.编写HelloServiceImpl.java 先将Hello.java复制到maven项目中 编写HelloServiceImpl.java实现Hello.Iface接口

package com.zlw.test;

/**
 * Desc:
 * ------------------------------------
 * Author:zhanglinwei02@meituan.com
 * Date:2018/7/16
 * Time:19:20
 */

import org.apache.thrift.TException;

public class HelloServiceImpl implements Hello.Iface {
    @Override
    public String helloString(String 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 boolean helloBoolean(boolean para) throws TException {
        return para;
    }

    @Override
    public void helloVoid() throws TException {
        System.out.println("Hello World");
    }

    @Override
    public String helloNull() throws TException {
        return null;
    }
}

5.编写服务端代码HelloServiceServer.java

package com.zlw.server;

import com.zlw.test.Hello;
import com.zlw.test.HelloServiceImpl;
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;
import org.apache.thrift.transport.TTransportException;

/**
 * Desc: 创建服务端实现代码HelloServiceServer,把HelloServiceImpl作为一个具体的处理器传递给Thrift服务器
 * ------------------------------------
 * Author:zhanglinwei02@meituan.com
 * Date:2018/7/16
 * Time:19:20
 */
public class HelloServiceServer {
    /**
     * 启动thrift服务器
     * @param args
     */
    public static void main(String[] args) {
        try {
            // 关联处理器与 Hello 服务的实现
            TProcessor tprocessor = new Hello.Processor<Hello.Iface>(new HelloServiceImpl());
            // 设置服务端口为9898
            TServerSocket serverTransport = new TServerSocket(9898);
            TServer.Args tArgs = new TServer.Args(serverTransport);
            tArgs.processor(tprocessor);
            tArgs.protocolFactory(new TBinaryProtocol.Factory());
            TServer server = new TSimpleServer(tArgs);
            System.out.println("Start server on port 9898...");
            server.serve();
        }catch (TTransportException e) {
            e.printStackTrace();
        }
    }
}

6.编写客户端代码HelloServiceClient.java

package com.zlw.client;

import com.zlw.test.Hello;
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;

/**
 * Desc: 创建客户端实现代码HelloServiceClient,调用Hello.client访问服务端的逻辑实现
 * ------------------------------------
 * Author:zhanglinwei02@meituan.com
 * Date:2018/7/17
 * Time:10:47
 */
public class HelloServiceClient {
    public static void main(String[] args) {
        System.out.println("客户端启动....");
        TTransport transport = null;
        try {
            // 设置调用的服务地址为本地,端口为 7911
            transport = new TSocket("localhost", 9898, 30000);
            // 协议要和服务端一致
            TProtocol protocol = new TBinaryProtocol(transport);
            Hello.Client client = new Hello.Client(protocol);
            transport.open();
            // 调用服务端helloString()方法
            String result = client.helloString("哈哈");
            System.out.println(result);
        } catch (TTransportException e) {
            e.printStackTrace();
        } catch (TException e) {
            e.printStackTrace();
        } finally {
            if (null != transport) {
                transport.close();
            }
        }
    }
}

Thrift架构

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

client端调用细节:client端调用了 Hello.Client 的 helloVoid 方法,在 helloVoid 方法中,通过 send_helloVoid 方法发送对服务的调用请求,通过 recv_helloVoid 方法接收服务处理请求后返回的结果。

数据类型

Thrift 脚本可定义的数据类型包括以下几种类型: 基本类型:

  • 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:对应服务的类

协议

传输协议上总体划分为文本 (text) 和二进制 (binary) 传输协议。 常用协议:

  • TBinaryProtocol —— 二进制编码格式进行数据传输

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

  • TJSONProtocol —— 使用 JSON 的数据编码协议进行数据传输

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

传输层

常见传输层:

  • TSocket —— 使用阻塞式 I/O 进行传输,是最常见的模式

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

  • TNonblockingTransport —— 使用非阻塞方式,用于构建异步客户端

服务端类型 常见的服务端类型:

  • TSimpleServer —— 单线程服务器端使用标准的阻塞式 I/O

  • TThreadPoolServer —— 多线程服务器端使用标准的阻塞式 I/O

  • TNonblockingServer —— 多线程服务器端使用非阻塞式 I/O

参考资料

http://thrift.apache.org/tutorial/java https://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/index.html https://www.cnblogs.com/fingerboy/p/6424248.html

Last updated