目录

Thrift_quickstart

概述

什么是thrift

简单来说,是Facebook公布的一款开源跨语言的RPC510_框架。

rpc510_框架

https://gitee.com/lienhui68/picStore/raw/master/null/20200915192640.png

RPC全称为Remote Procedure Call,意为远程过程调用。

假设有两台服务器A,B。A服务器上部署着一个应用a,B服务器上部署着一个应用b,现在a希望能够调用b应用的某个函数(方法),但是二者不在同一个进程内,不能直接调用,就需要通过网络传输,在AB服务器之间建一条网络传输通道,a把参数传过去,b接收到参数调用自己的方法,得到结果,再通过网络传回给a,简单讲就是A通过网络来调用B的过程。这个过程要涉及的东西很多,比如多线程,Socket,序列化反序列化,网络I/O,很复杂,于是牛掰的程序员把这些封装起来做成一套510_框架,供大家使用,就是RPC510_框架。

跨语言

thrift通过一个中间语言IDL(接口定义语言)来定义RPC的数据类型和接口,这些内容写在以 .thrift 结尾的文件中,然后通过特殊的编译器来生成不同语言的代码,以满足不同需要的开发者,比如java开发者,就可以生成java代码,c++开发者可以生成c++代码,生成的代码中不但包含目标语言的接口定义,方法,数据类型,还包含有RPC协议层和传输层的实现代码

thrift协议栈结构

https://gitee.com/lienhui68/picStore/raw/master/null/20200915143030.png

thrift是一种c/s的架构体系。在最上层是用户自行实现的业务逻辑代码。

第二层是由thrift编译器自动生成的代码,主要用于结构化数据的解析,发送和接收。TServer主要任务是高效的接受客户端请求,并将请求转发给Processor处理。Processor负责对客户端的请求做出响应,包括RPC请求转发,调用参数解析和用户逻辑调用,返回值写回等处理。

TProtocol以下部分是thirft的传输协议和底层I/O通信。TProtocol是用于数据类型解析的,将结构化数据转化为字节流给TTransport进行传输。TTransport是与底层数据传输密切相关的传输层,负责以字节流方式接收和发送消息体,不关注是什么数据类型。底层IO负责实际的数据传输,包括socket、文件和压缩数据流等。

下载与安装

官网:http://thrift.apache.org/

1
2
3
$ brew install thrift
$ thrift -version
Thrift version 0.13.0

HelloWorld

  1. 创建一个服务Hello,创建文件Hello.thrift,代码如下:

    1
    2
    3
    4
    5
    
    namespace java com.eh.eden.thrift
       
    service Hello{
        string helloString(1:string para)
    }
    

    这里定义了一个名为helloString的方法,入参和返回值都是一个string类型的参数.

  2. 终端进入Hello.thrift所在目录,执行命令:

    1
    
    $thrift -r -gen java Hello.thrift
    

    发现在当前目录下多了一个gen-java的目录,进入到包路径对应的文件夹下,里面有一个Hello.java文件。这个java文件包含Hello服务的接口定义Hello.Iface,以及服务调用的底层通信细节,包括客户端的调用逻辑Hello.Client以及服务端的处理逻辑Hello.Processor

  3. 创建一个Maven管理的Java项目,pom.xml中添加相关的依赖,并将Hello.java文件复制到项目中:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    <dependencies>
        <dependency>
            <groupId>org.apache.thrift</groupId>
            <artifactId>libthrift</artifactId>
            <version>0.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.5</version>
        </dependency>
    </dependencies>
    
  4. 创建HelloServiceImpl实现Hello.Iface接口:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    package com.eh.eden.thrift;
       
    import org.apache.thrift.TException;
       
    public class HelloServiceImpl implements Hello.Iface {
        @Override
        public String helloString(String para) throws TException {
            return para + ", world.";
        }
    }
    
  5. 创建服务端实现代码HelloServer,把HelloServiceImpl作为一个具体的处理器传递给Thrift服务器:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
    package com.eh.eden.thrift;
       
    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;
       
    public class HelloServer {
        public static void main(String[] args) {
            try {
                // transport
                TServerSocket serverTransport = new TServerSocket(9898);
       
                // processor
                TProcessor tprocessor = new Hello.Processor<Hello.Iface>(new HelloServiceImpl());
                 
                 // 组装参数
                TServer.Args tArgs = new TServer.Args(serverTransport);
                tArgs.processor(tprocessor)
                        .protocolFactory(new TBinaryProtocol.Factory()); // 协议要与客户端一致
       
                // Server
                TServer server = new TSimpleServer(tArgs);
                System.out.println("服务端开启....");
                server.serve();
            } catch (TTransportException e) {
                e.printStackTrace();
            }
        }
    }
    

    服务端编码基本流程

    1. 创建TProcessor

      Processor封装了从输入数据流中读数据和向数据流中写数据的操作,与服务相关的Processor是由编译器编译IDL文件产生的。它的主要工作是:从连接中读取数据,把处理交给用户实现impl,最后把结果写到连接上。

    2. 创建serverTransport

      TServerSocket是ServerTransport的阻塞式IO的实现。它实现了监听端口的作用,accept到的Socket类型都是客户端的TSocket类型(阻塞式Socket)。

    3. 创建TProtocol

      TProtocol定义了基本的协议信息,包括传输什么数据,如何解析传输的数据。

    4. 创建server

      根据需要选择使用不同的服务模式,代码中为了演示只是用了最简单TSimpleServer

    5. 启动服务

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

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    
    package com.eh.eden.thrift;
       
    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;
       
    public class HelloClient {
        public static void main(String[] args) {
       
            TTransport transport = null;
            try {
                // 创建TTransport
                transport = new TSocket("localhost", 9898, 30000);
       
                // 创建TProtocol 协议要与服务端一致
                TProtocol protocol = new TBinaryProtocol(transport);
       
                // 创建client
                Hello.Client client = new Hello.Client(protocol);
       
                // 建立连接
                transport.open();
       
                // client调用server端方法
                System.out.println(client.helloString("hello"));
            } catch (TTransportException e) {
                e.printStackTrace();
            } catch (TException e) {
                e.printStackTrace();
            } finally {
                if (null != transport) {
                    transport.close(); // 请求结束,断开连接
                }
            }
        }
    }
    
  7. 全部工作完成后,下面来测试一下,先执行服务端main方法,在执行客户端main方法,会在客户端控制台打印出

    1
    
    hello, world.
    

基础知识

Thrift使用一种中间语言IDL,来进行接口的定义,下面来具体讲一下IDL可定义的几种常用数据类型和关键字。

常用数据类型及关键字

基本类型

thrift不支持无符号的类型,像java的基本数据类型都是有符号的类型。

1
2
3
4
5
byte:有符号字节
i32:32位有符号整数,此外还有i16,i64
double:64位浮点数
string:二进制字符串
bool:布尔值 true或false

结构体类型

类似于c语言的结构体定义,在java中会被转化为javabean类

1
2
3
4
5
6
struct User {
  1: i32 id;
  2: string name;
  3: double salary;
  4: bool hasCar;
}

服务类型

service:对应服务的接口,内部可定义各种方法,相当于java中创建interface一样,创建的service经过代码生成命令会生成客户端和服务端的510_框架代码。

1
2
3
4
5
6
7
service Hello{
  string helloString(1:string s);
  i32 helloInt(1:i32 i);
  bool helloBoolean(1:bool b);
  void helloVoid();
  string helloNull();
}

异常类型

1
2
3
4
exception RequestException {
  1:i32 code;
  2:string detail;
}

容器类型

集合中的元素可以是除了service之外的任意类型

1
2
3
list<T>:有序列表,元素可重复
set<T>:无需集合,元素不可重复
map<K,V>:键值对集合

枚举类型

1
2
3
4
enum Color{
    RED,
    BLUE
  }

命名空间

可以理解成java中的packet,用于避免一些代码冲突,每种语言都有属于自己的命名空间的方式,比如java语言,就可以使用java语言的格式

1
namespace java com.eh.eden.thrift

此外还有一些语言特性和关键字就不一一介绍了,比如可选参数和必选参数,required和optional,定义常量const,引入文件include等

传输协议

Thrift支持多种传输协议,我们可以根据自己的需要来选择合适的类型,总体上来说,分为文本传输和二进制传输,由于二进制传输在传输速率和节省带宽上有优势,所以大部分情况下使用二进制传输是比较好的选择。

  • TBinaryProtocol:使用二进制编码格式传输,是thrift的默认传输协议
  • TCompactProtocol:使用压缩格式传输
  • TJSONProtocol :使用JSON格式传输
  • TDebugProtocol – 使用易懂可读的文本格式进行传输,以便于debug
  • TSimpleJSONProtocol – 提供JSON只写的协议,适用于通过脚本语言解析

传输模式

Thrift封装了一层传输层来支持底层的网络通信,在Thrift中称为Transport,不仅提供open,close,flush等方法,还有一些read/write方法。

  • TSocket:阻塞式IO的Transport实现,用在客户端
  • TServerSocket:阻塞式Socket,用于服务器端,用于监听TSocket
  • TNonblockingSocket/TNonblockingServerSocket:非阻塞式IO的实现
  • TMemoryInputTransport:封装了一个字节数组byte[]来做输入流的封装
  • TFramedTransport:同样使用非阻塞方式,按块的大小进行传输,输入流封装了TMemoryInputTransport  

服务模型

  • TSimpleServer

    这种工作模式只有一个线程,循环监听传过来的请求并对其进行处理,处理完才能接受下一个请求,是一种阻塞式IO的实现。因为效率比较低,实际线上环境一般用不到,一般用于开发时候演示工作流程时使用。

  • TNonblockingServer

    这种模式与TsimpleServer最大的区别就是使用NIO,也就是非阻塞是IO的方式实现IO的多路复用,它可以同时监听多个socket的变化,但因为业务处理上还是单线程模式,所以在一些业务处理比较复杂耗时的时候效率还是不高,因为多个请求任务依然需要排队一个一个进行处理。

  • TThreadPoolServer

    这种模式引入了线程池,主线程只负责accept,即监听Socket,当有新的请求(客户端Socket)来时,就会在线程池里起一个线程来处理业务逻辑,这样在并发量比较大的时候(但不超过线程池的数量)每个请求都能及时被处理,效率比较高,但一旦并发量很大的时候(超过线程池数量),后面来的请求也只能排队等待。

    TThreadPoolServer这个服务器实现使用的是原始的OIO,在一个专门的线程上运行连接。这使得它在处理少量的连 接时表现不错;然而,使用 OIO,当你的服务器需要处理大量的并发连接时,你将很容易遇到 伸缩性问题。

  • TThreadedSelectorServer

    这是一种多线程半同步半异步的服务模型,是Thrift提供的最复杂最高级的服务模型,内部有一个专门负责处理监听Socket的线程,有多个专门处理业务中网络IO的线程,有一个专门负责决定将新Socket连接分配给哪一个线程处理的起负载均衡作用的线程,还有一个工作线程池。这种模型既可以响应大量并发连接的请求又可以快速对wangluoIO进行读写,能适配很多场景,因此是一种使用比较高频的服务模型。

    https://gitee.com/lienhui68/picStore/raw/master/null/20200915193300.png

    图片来源:https://blog.csdn.net/chikanzhu8527/article/details/100854924

    参考:https://blog.csdn.net/u013725455/article/details/62427963