0
点赞
收藏
分享

微信扫一扫

Apache Thrift 官网学习 二 基础知识掌握

玩物励志老乐 2022-02-07 阅读 44

文章目录

二 基础知识掌握

2.1 基本结构

在这里插入图片描述

  • Thrift软件栈的定义非常的清晰, 使得各个组件能够松散的耦合, 针对不同的应用场景, 选择不同是方式去搭建服务。

  • Thrift软件栈分层从下向上分别为:传输层(Transport Layer)、协议层(Protocol Layer)、处理层(Processor Layer)和服务层(Server Layer)。

  • 传输层(Transport Layer):传输层负责直接从网络中读取写入数据,它定义了具体的网络传输协议;比如说TCP/IP传输等。

  • 协议层(Protocol Layer):协议层定义了数据传输格式,负责网络传输数据的序列化反序列化;比如说JSONXML二进制数据等。

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

  • 服务层(Server Layer):整合上述组件,提供具体的网络线程/IO服务模型,形成最终的服务。

  • 这就是计算机网络中的分层设计,可以是各个组件各司其职,向上提供服务,向下接受服务。

2.2 基本语法

2.2.1 基础数据类型

bool: A boolean value (true or false)
byte: An 8-bit signed integer
i16: A 16-bit signed integer
i32: A 32-bit signed integer
i64: A 64-bit signed integer
double: A 64-bit floating point number
string: A text string encoded using UTF-8 encoding

// 定义别名:将thrift基本数据类型别名为Java数据类型,方便使用
typedef i16 short
typedef i32 int
typedef i64 long
typedef string String
typedef bool  Boolean
typedef double Double
  • 基本上与Java语法差不多,理解起来很简单

2.2.2 复杂结构

list: An ordered list of elements. Translates to an STL vector, Java ArrayList, native arrays in scripting languages, etc.
set: An unordered set of unique elements. Translates to an STL set, Java HashSet, set in Python, etc. Note: PHP does not support sets, so it is treated similar to a List
map: A map of strictly unique keys to values. Translates to an STL map, Java HashMap, PHP associative array, Python/Ruby dictionary, etc. While defaults are provided, the type mappings are not explicitly fixed. Custom code generator directives have been added to allow substitution of custom types in various destination languages
list<t>:元素类型为t的有序表,容许元素重复。对应java的ArrayList
set<t>:元素类型为t的无序表,不容许元素重复。对应java的HashSet
map<t,t>:键类型为t,值类型为t的kv对,键不容许重复。对对应JavaHashMap
  • 其实就是容器,list set map

2.2.3 struct

  • 用来定义一个类。
  • struct 不能继承,但是可以嵌套,不能嵌套自己,其成员都是有明确类型。
  • 成员是被正整数编号过的,其中的编号使不能重复的,这个是为了在传输过程中编码使用。
  • 成员分割符可以是逗号(,)或是分号(;),而且可以混用,但是为了清晰期间,比如java学习者可以就使用逗号(;)。
  • 字段会有optional和required之分。
  • 每个字段可以设置默认值。
  • 同一文件可以定义多个struct,也可以定义在不同的文件,进行include引入。
// 定义一个struct:相当于Java中的类
struct person{
  	1: required string name; // 必须字段,很明确
    2: required i64 age;
    3: optional string addr; // 可选字段
    4: optional string defaultValue = "DEFAULT"; // 默认字段
    5: string otherValue; // 不是很明确!
}

2.2.4 service

  • 可以把他理解为方法的集合。
  • 服务的定义方法在语义上等同于面向对象语言中的接口。Thrift 编译器会产生执行这些接口的 client 和 server 存根(详情下一节会具体描述)。
// 定义一个异常
exception personexception{
1:optional int code;
2:optional String message;
}

// 定义一个服务集合
service personservice{

 // 通过用户名获取信息,并扔出异常
 person getPersonInfoByUserNmae(1:required String username) throws (1: personexception exce )

 // 通过用户名检查是否可用
 Boolean IsCheck(1:required String username)

}

2.4.5 exception

关于异常,在Thrift中就像定义 struct 一样,因为exception从概念上讲,也是一种class,所谓『万事万物皆对象』嘛。不过现在我们用『exception』这个关键字,也正好符合我前文所讲的,清晰的语义。

// 定义一个异常
exception personexception{
1:optional int code;
2:optional String message;
}

2.4.6 枚举

枚举这个东西,真的是太重要了,和前面的exception类似,它也不过是一种class而已。不过Thrift中只支持枚举 int 值,比较遗憾,其实很多时候,对枚举的要求,我们是很丰富的,比如支持 枚举 string。Thrift中枚举如下:

enum Operation { // 功能着实比较孱弱
  ADD = 1,
  SUBTRACT = 2,
  MULTIPLY = 3,
  DIVIDE = 4
}

2.4.7 namespace

  • 定义生成文件的包名。

  • Thrift 中的命名空间类似于 java 中的 package,它们提供了一种组织(隔离)代码的简便方式。名字空间也可以用于解决类型定义中的名字冲突。

// 定义包名
namespace java com.shu.demo.thrift

2.4.8 typedef

将基本数据类型取别名

// 定义别名:将thrift基本数据类型别名为Java数据类型,方便使用
typedef i16 short
typedef i32 int
typedef i64 long
typedef string String
typedef bool  Boolean
typedef double Double

2.3 三大组件

Thrift 包含三个主要的组件:protocol,transport 和 server。

2.3.1 transport

  • TTransport 是所有 Transport类的父类,为上层提供了统一的接口而且通过 TTransport 即可访问各个子类不同实现,类似多态。

在这里插入图片描述

在这里插入图片描述

  • TSocket:阻塞型 socket,用于客户端,采用系统函数 readwrite进行读写数据。
  • TServerSocket:非阻塞型 socket,用于服务器端,accecpt 到的 socket 类型都是 TSocket(即阻塞型 socket)。
  • TBufferedTransportTFramedTransport都是有缓存的,均继承TBufferBase,调用下一层 TTransport 类进行读写操作吗,结构极为相似。其中 TFramedTransport 以帧为传输单位帧结构为:4个字节(int32_t)+传输字节串,头4个字节是存储后面字节串的长度,该字节串才是正确需要传输的数据,因此 TFramedTransport每传一帧要比 TBufferedTransportTSocket多传4个字节。
  • TMemoryBuffer继承 TBufferBase,用于程序内部通信用,不涉及任何网络I/O,可用于三种模式:(1)OBSERVE模式,不可写数据到缓存;(2)TAKE_OWNERSHIP模式,需负责释放缓存;(3)COPY模式,拷贝外面的内存块到TMemoryBuffer。
  • TFileTransport直接继承 TTransport,用于写数据到文件。对事件的形式写数据,主线程负责将事件入列,写线程将事件入列,并将事件里的数据写入磁盘。这里面用到了两个队列,类型为 TFileTransportBuffer,一个用于主线程写事件,另一个用于写线程读事件,这就避免了线程竞争。在读完队列事件后,就会进行队列交换,由于由两个指针指向这两个队列,交换只要交换指针即可。它还支持以 chunk(块)的形式写数据到文件。
  • TFDTransport是非常简单地写数据到文件和从文件读数据,它的 write 和 read 函数都是直接调用系统函数 write 和 read 进行写和读文件。
  • TSimpleFileTransport直接继承 TFDTransport,没有添加任何成员函数和成员变量,不同的是构造函数的参数和在 TSimpleFileTransport 构造函数里对父类进行了初始化(打开指定文件并将fd传给父类和设置父类的close_policy为CLOSE_ON_DESTROY)。
  • TZlibTransport跟 TBufferedTransport 和 TFramedTransport一样,调用下一层 TTransport 类进行读写操作。它采用<zlib.h>提供的 zlib 压缩和解压缩库函数来进行压解缩,写时先压缩再调用底层 TTransport 类发送数据,读时先调用 TTransport 类接收数据再进行解压,最后供上层处理。
  • TSSLSocket继承 TSocket,阻塞型 socket,用于客户端。采用 openssl 的接口进行读写数据。checkHandshake()函数调用 SSL_set_fd 将 fd 和 ssl 绑定在一起,之后就可以通过 ssl 的 SSL_read和SSL_write 接口进行读写网络数据。
  • TSSLServerSocket 继承 TServerSocket,非阻塞型 socket, 用于服务器端。accecpt 到的 socket 类型都是 TSSLSocket 类型。
  • THttpClientTHttpServe 是基于 Http1.1 协议的继承 Transport 类型,均继承 THttpTransport,其中 THttpClient 用于客户端,THttpServer 用于服务器端。两者都调用下一层 TTransport 类进行读写操作,均用到TMemoryBuffer 作为读写缓存,只有调用 flush() 函数才会将真正调用网络 I/O 接口发送数据。

2.3.2 protocol

Thrift 支持多种传输协议,用户可以根据实际需求选择合适的类型。Thrift 传输协议上总体可划分为文本 (text) 和二进制 (binary) 传输协议两大类,一般在生产环境中使用二进制类型的传输协议为多数(相对于文本和 JSON 具有更高的传输效率)。常用的协议包含:

  • TBinaryProtocol:是Thrift的默认协议,使用二进制编码格式进行数据传输,基本上直接发送原始数据
  • TCompactProtocol:压缩的、密集的数据传输协议,基于Variable-length quantity的zigzag 编码格式
  • TJSONProtocol:以JSON (JavaScript Object Notation)数据编码协议进行数据传输
  • TDebugProtocol:常常用以编码人员测试,以文本的形式展现方便阅读

2.3.3 server

  • Thrift 模块化的结构使得它能提供各种 server 实现。下面列出了 Java 中可用的 server 实现
TSimpleServer
TNonblockingServer
THsHaServer
TThreadedSelectorServer
TThreadPoolServer
  • TSimplerServer接受一个连接,处理连接请求,直到客户端关闭了连接,它才回去接受一个新的连接。正因为它只在一个单独的线程中以阻塞 I/O 的方式完成这些工作,所以它只能服务一个客户端连接,其他所有客户端在被服务器端接受之前都只能等待。

  • TNonblockingServer使用非阻塞的 I/O 解决了 TSimpleServer一个客户端阻塞其他所有客户端的问题。它使用了java.nio.channels.Selector,通过调用 select(),它使得你阻塞在多个连接上,而不是阻塞在单一的连接上。当一或多个连接准备好被接受/读/写时,select() 调用便会返回。TNonblockingServer 处理这些连接的时候,要么接受它,要么从它那读数据,要么把数据写到它那里,然后再次调用 select() 来等待下一个可用的连接。通用这种方式,server 可同时服务多个客户端,而不会出现一个客户端把其他客户端全部“饿死”的情况。然而,还有个棘手的问题:所有消息是被调用 select() 方法的同一个线程处理的。假设有10个客户端,处理每条消息所需时间为100毫秒,那么,latency 和吞吐量分别是多少?当一条消息被处理的时候,其他9个客户端就等着被 select,所以客户端需要等待1秒钟才能从服务器端得到回应,吞吐量就是10个请求/秒。如果可以同时处理多条消息的话,会很不错吧?

  • THsHaServer(半同步/半异步的 server)就应运而生了。它使用一个单独的线程来处理网络I/O,一个独立的 worker 线程池来处理消息。这样,只要有空闲的 worker 线程,消息就会被立即处理,因此多条消息能被并行处理。用上面的例子来说,现在的 latency 就是100毫秒,而吞吐量就是100个请求/秒。

  • TThreadedSelectorServer。它与 THsHaServer 的主要区别在于,TThreadedSelectorServer 允许你用多个线程来处理网络 I/O。它维护了两个线程池,一个用来处理网络 I/O,另一个用来进行请求的处理。当网络 I/O 是瓶颈的时候,TThreadedSelectorServer 比 THsHaServer 的表现要好。为了展现它们的区别进行一个测试,令其消息处理器在不做任何工作的情况下立即返回,以衡量在不同客户端数量的情况下的平均 latency 和吞吐量。对 THsHaServer,使用32个 worker 线程;对 TThreadedSelectorServer,使用16个 worker 线程和16个 selector 线程。

  • TThreadPoolServer与其他三种 server不同的是:

    有一个专用的线程用来接受连接``一旦接受了一个连接,它就会被放入 ThreadPoolExecutor 中的一个 worker 线程里处理。``worker 线程被绑定到特定的客户端连接上,直到它关闭。一旦连接关闭,该 worker 线程就又回到了线程池中。``你可以配置线程池的最小、最大线程数,默认值分别是``5``(最小)和 Integer.MAX_VALUE(最大)。
    

    这意味着,如果有1万个并发的客户端连接,你就需要运行1万个线程。所以它对系统资源的消耗不像其他类型的 server 一样那么友好。此外,如果客户端数量超过了线程池中的最大线程数,在有一个 worker线程可用之前,请求将被一直阻塞在那里。

    我们已经说过,TThreadPoolServer 的表现非常优异。在我正在使用的计算机上,它可以支持1万个并发连接而没有任何问题。如果你提前知道了将要连接到你服务器上的客户端数量,并且你不介意运行大量线程的话,TThreadPoolServer对你可能是个很好的选择。

2.4 基本流程

2.4.1 server调用流程

  • HelloServiceServer 启动的过程,以及服务被客户端调用时服务器的响应过程。
  • 我们可以看到,程序调用了 TThreadPoolServer 的 serve() 方法后,server 进入阻塞监听状态,其阻塞在 TServerSocket 的 accept()方法上。
  • 当接收到来自客户端的消息后,服务器发起一个新线程处理这个消息请求,原线程再次进入阻塞状态。
  • 在新线程中,服务器通过 TBinaryProtocol 协议读取消息内容,调用 HelloServiceImpl 的 helloVoid() 方法,并将结果写入 helloVoid_result 中传回客户端。

在这里插入图片描述

2.4.2 client调用流程

在这里插入图片描述

  • 上图展示的是 HelloServiceClient 调用服务的过程,以及接收到服务器端的返回值后处理结果的过程。
  • 我们可以看到,程序调用了 Hello.Client 的 helloVoid() 方法,在 helloVoid() 方法中,通过 send_helloVoid() 方法发送对服务的调用请求,通过 recv_helloVoid() 方法接收服务处理请求后返回的结果。
举报

相关推荐

0 条评论