Thritf 学习笔记

本文为学习记录,如有侵权,请联系删除

为何需要使用Thrift协议?

首先,Thrift是基于socket的,通过tcp协议来实现,即应用层协议

其次,现在大多使用分布式来进行架构,而实现分布式架构的方式主要是远程过程调用(Remote Procedure Call,RPC)。

简化版分布式架构:

简化版分布式架构

rpc主要需要解决的是:

  • 传输什么样的数据,用哪种协议

  • 哪种方式数据交换的效率好

  • 服务端如何处理请求

一个rpc协议一般可以分为 传输协议序列化协议。 传输协议一般是指 元信息 (我这里理解为类似与报文头),而序列化协议是指将 参数序列化(我这里理解为将报文体序列化,方便传输)

thrift协议被设计成既包含传输协议也包含序列化协议,可以很好的解决上述问题。

Thrift使用原理

结构图:

thrift 其实是包括了框架部分和协议部分。整体架构图如下:

thrift架构图.png

从上图我们可以看到,在一个层级中可以选择自己想要的,最后组合出来一个按照我们意愿的产物,也即 支持面很广

thrift核心组件:

  • TProtocol 协议和编解码组件(主要是对报文进行编码、序列化)

  • TTransport 传输组件

  • TProcessor 服务调用组件

  • TServer,Client服务器和客户端组件

  • IDL 接口定义

使用流程:

Thrift操作使用流程图

步骤如上:

  1. 创建对应的IDL文件,即(xxx.thrift)

  2. 使用IDL编译工具,将IDL文件生成对应的代码

  3. 创建对应的服务端和客户端代码(也可以在步骤2中一起生成)

Thrift 特性:

1. 基于二进制的高性能的编解码框架

  • 二进制:二进制协议比文本形式的协议,发送的数据量肯定是更小,传输效率更高的。

  • 编解码框架:作为rpc框架,一般都是封装了方法名、方法参数这些方法调用的方式。客户端发送消息。服务端接收到客户端的消息之后,解码消息,然后通过方法调用模型来完成实际服务器端业务方法的调用。

横向对比 :对比HTTP性能

从文章的测试数据可以看到,thrift的性能要远高于http。但是thrift协议的设计是比较复杂的,也不是不加判断的就使用thrift协议。基于简单程度可以选择http,基于性能可以选择thrift协议

产生这种现象的原因,一部分原因是因为非阻塞IO的机制,另一部分是http是无状态无连接的协议,而thrift协议使用前需要先建立连接,在多次传输中占有优势。

纵向对比:同比gRPC

gRPC框架的传输层是使用的http2,对比thrift主要是有更加多、更健全的文档,更优秀的鉴权功能等。但是支持广度、性能还是thrift要更胜一筹。

2. 基于NIO的底层通信

NIO,全称是NoneBlocking IO,非阻塞IO,和BIO(Blocking IO,阻塞IO)相对应。具体的解释和实现机制,不是这篇文章的重点,有兴趣可以自行查阅一下。可以看看篇文章

3. 相对简单的服务调用模型

4. 使用IDL支持跨平台调用

小结:

总结来说就是高性能、传输速度快、支持面广,可以跨平台使用

协议结构

Thrift请求相应模型

thrift请求响应模型

可以将这里的message和struct理解为报文头和报文体的关系。注意图中的1、2不是发送了两次请求,应该理解为一组字节流,2紧跟在1后面,4紧跟在3后面。

Message

Message中主要包含Name,Message Type,Sequence ID等数据。

  1. Name:为调用的方法名

  2. Message Type:有Call, OneWay, Reply, Exception四种,在实际传递的时候,传递的是Type ID,这四种Type对应的Type ID如下

     Call      ---> 1
     OneWay    ---> 2
     Reply     ---> 3
     Exception ---> 4

其中Call、OneWay用于Request, Reply、 Exception用于Response中。 四者的含义如下:

  • Call: 调用远程方法,并且期待对方发送响应。

  • OneWay: 调用远程方法,不期待响应。即没有步骤3,4。

  • Reply: 表明处理完成,响应正常返回。

  • Exception:表明出理出错。

  1. Sequence ID : 序列号, 有符号的四字节整数。在一个传输层的连接上所有未完成的请求必须有唯一的序列号,客户端使用序列号来处理响应的失序到达,实现请求和响应的匹配。服务端不需要检查该序列号,也不能对序列号有任何的逻辑依赖,只需要响应的时候将其原样返回即可。可以类比于tcp滑动窗口中的序列号来理解。

Struct

在上面的Thrift请求响应模型中,有两种Struct:

  1. Request Struct

  2. Response Struct

这两种Struct的结构是一样的,都是由多个Field组成

具体可参考这里

序列化协议

序列化本质是把 rpc的参数 转换成 一段连续的二进制流,进一步还可以对参数进行压缩(可选),使传输的流量更少。

Thrift支持多种序列化协议,常用的有: Binary、Compact、JSON。常用的是Binary和Compact。前者基本就是将参数顺序的写入连续的二进制流,后者对参数进行了一定的压缩。本文主要分析Binary,Compact有兴趣可自行查阅。

Binary序列化

binary序列化是一种二进制的序列化方式,不可读。

Message的序列化

Message的序列化分为两种,strict encoding和old encoding。 在有些实现中,会通过 检查Thrift消息的第一个bit来判断使用了那种encoding:

  • 1 —-> strict encoding (会带上Version信息,默认模式)

  • 0 —-> old encoding

官网上面是这样写的:

Thrift Protocols are stream oriented by design. There is no need for any explicit framing. For instance, it is not necessary to know the length of a string or the number of items in a list before we start serializing them. Some of the protocols available for majority of the Thrift-supported languages are:
 
 - binary: Fairly simple binary encoding – the length and type of a field are encoded as bytes followed by the actual value of the field.
 - compact: Described in [THRIFT-110](https://issues.apache.org/jira/browse/THRIFT-110)
 - json

图片来源

猜想使用三个bit的目的是为了方便后续扩展。

Struct的序列化

Struct装的是Thrift通信的实际参数,一个Struct由很多基本类型组合而成,要了解Struct怎么序列化的必须知道这些基本类型的序列化。

Binary序列化中各种字段类型的类型标记和占用大小:

数据类型 类型标志(一个字节)
bool 2 一个字节
byte 3 一个字节
double 4 八个字节
i16 6 两个字节值
i32 8 四个字节值
i64 10 八个字节值
string 11 四个字节数据长度+数据的值
binary 11 四个字节数据长度+数据的值
struct 12 多个连续的field数据+一个字节停止符(0)
map 13 一个字节的key类型标志+一个字节的val类型标志+四个字节的数据长度+数据的值(key+val)
set 14 一个字节的val类型标志+四个字节的数据长度+数据的值
list 15 一个字节的val类型标志+四个字节的数据长度+数据的值

序列化后的二进制流格式

[id(2字节) + 类型标志(1字节) + 值] +  [id(2字节) + 类型标志(1字节) + 值] + .....

由于二进制流中存在 id序号 和 类型flag, 当server收到协议后,会根据服务端注册的idl, 根据 id序号到二进制流中找对应的buffer, 并会对类型做进一步比对。

Compact

主要是对Binary进行了一点压缩

Thrift IDL使用方法

看参考Thrift IDL 使用语法

参考文章

Thrift官网

HTTP性能对比测试

同比gRPC

NIO相关基础

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容