Thrift

月伴飞鱼 2024-11-11 19:31:25
框架相关
支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!

ThriftFacebook的一款开源跨语言的RPC框架。

跨语言特型

Thrift通过一个中间语言IDL(接口定义语言)来定义RPC的数据类型和接口。

这些内容写在以.thrift结尾的文件中,然后通过特殊的编译器来生成不同语言的代码,以满足不同需要的开发者。

  • 生成的代码中不但包含目标语言的接口定义,方法,数据类型,还包含有RPC协议层和传输层的实现代码。

Thrift服务开发流程

1、定义IDL文件(xxx.thrift文件)。

2、用xxx.thrift文件生成Java代码(服务接口文件)。

3、服务端实现(创建服务):实现服务接口,开启服务器。

4、客户端实现(服务消费):引入接口,进行远程调用。

基本类型

Thrift不支持无符号的类型,只能表示正数的类型

  • byte:有符号字节

  • i32:32位有符号整数,此外还有i16,i64

  • double:64位浮点数

  • string:二进制字符串

  • bool:布尔值,truefalse

结构体类型

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

服务类型

对应服务的接口,内部可定义各种方法,相当于java中创建interface一样。

  • 创建的service经过代码生成命令会生成客户端,服务端的框架代码。
service Hello{
  string helloString(1:string s);
  i32 helloInt(1:i32 i);
  bool helloBoolean(1:bool b);
  void helloVoid();
  string helloNull();
}

异常类型

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

容器类型

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

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

枚举类型

enum Color{
    RED,
    BLUE
}

命名空间

用于避免一些代码冲突,每种语言都有属于自己的命名空间的方式。

namespace java com.project

传输协议

Thrift支持多种传输协议,可以根据自己的需要来选择合适的类型。

总体上来说,分为文本传输和二进制传输,由于二进制传输在传输速率和节省带宽上有优势。

  • 所以大部分情况下使用二进制传输是比较好的选择.

TBinaryProtocol:使用二进制编码格式传输,是thrift的默认传输协议

TCompactProtocol:使用压缩格式传输

TJSONProtocol:使用JSON格式传输

TDebugProtocol:使用易懂可读的文本格式进行传输,以便于debug

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

传输模式

Thrift封装了一层传输层来支持底层的网络通信,在Thrift中称为Transport

  • TSocket:阻塞式IO的Transport实现,用在客户端

  • TServerSocket:非阻塞式Socket,用于服务器端,用于监听TSocket

  • TNonblockingSocket:非阻塞式IO的实现

  • TMemoryInputTransport: 封装了一个字节数组byte[]来做输入流的封装

  • TFramedTransport:同样使用非阻塞方式,按块的大小进行传输,输入流封装了TMemoryInputTransport

服务模型

TSimpleServer:

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

TNonblockingServer:

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

TThreadPoolServer:

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

TThreadedSelectorServer:

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

协议栈结构

TServer

  • 高效的接受客户端请求,并将请求转发给Processor处理。

Processor

  • 负责对客户端的请求做出响应,包括RPC请求转发,调用参数解析和用户逻辑调用,返回值写回等处理。

  • TProtocol以下部分是Thirft的传输协议和底层I/O通信。

TProtocol:

  • 用于数据类型解析的,将结构化数据转化为字节流给TTransport进行传输。

TTransport:

  • 与底层数据传输密切相关的传输层,负责以字节流方式接收和发送消息体,不关注是什么数据类型。

  • 底层IO负责实际的数据传输,包括Socket、文件和压缩数据流等。

img

简单案例

创建一个服务Hello,创建文件Hello.thrift

  • 这里定义了一个名为helloString的方法,入参和返回值都是一个string类型的参数。
namespace java service.demo
service Hello{
    string helloString(1:string para)
}

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

thrift -r -gen java Hello.thrift

在当前目录下多了一个gen-java的目录,里面的有一个Hello.java的文件。

这个文件包含Hello服务的接口定义Hello.Iface,以及服务调用的底层通信细节:

  • 包括客户端的调用逻辑Hello.Client以及服务端的处理逻辑Hello.Processor

创建一个项目,pom.xml中添加相关的依赖,并将Hello.java文件复制到项目中。

    <dependency>
      <groupId>org.apache.thrift</groupId>
      <artifactId>libthrift</artifactId>
      <version>0.10.0</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.5</version>
    </dependency>

创建HelloServiceImpl实现Hello.Iface接口。

package service.demo;
import org.apache.thrift.TException;

public class HelloServiceImpl implements Hello.Iface {
    public String helloString(String para) throws TException {
        return "result:"+para;
    }
}

创建服务端实现代码HelloServiceServer,把HelloServiceImpl作为一个具体的处理器传递给Thrift服务器。

package service.demo;
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 HelloServiceServer {
    /**
     * 启动thrift服务器
     * @param args
     */
    public static void main(String[] args) {
        try {
            System.out.println("服务端开启....");
            TProcessor tprocessor = new Hello.Processor<Hello.Iface>(new HelloServiceImpl());
            // 简单的单线程服务模型
            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);
            server.serve();
            }catch (TTransportException e) {
            e.printStackTrace();
        }
    }
}

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

package service.demo;

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 HelloServiceClient {

    public static void main(String[] args) {
        System.out.println("客户端启动....");
        TTransport transport = null;
        try {
            transport = new TSocket("localhost", 9898, 30000);
            // 协议要和服务端一致
            TProtocol protocol = new TBinaryProtocol(transport);
            Hello.Client client = new Hello.Client(protocol);
            transport.open();
            String result = client.helloString("哈哈");
            System.out.println(result);
        } catch (TTransportException e) {
            e.printStackTrace();
        } catch (TException e) {
            e.printStackTrace();
        } finally {
            if (null != transport) {
                transport.close();
            }
        }
    }
}
支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!