写在前面
前两天,我司开发环境Zeppelin在跑一个统计任务时,挂了,报错信息如下:
Thrift Server通信失败了,具体原因没有深究。然而,Thrift这个看似熟悉却又陌生的词汇吸引了我的注意力。碰巧今天在学习《Spark内核剖析》第10章,Spark SQL连接Hive时,又一次提到了Thrift,于是便有了此文。
本文主要回答以下问题
- Thrift是什么、有什么用
- Spark Thrift Server是什么、有什么用
什么是Thrift
Thrift是一种接口描述语言和二进制通讯协议,可以用来定义跨语言的服务。跨语言服务通信协议并不是什么稀奇玩意儿,有基于JSON格式的RESTful服务,基于XML格式的SOAP Web Service等。Thrift由Facebook开源,谷歌也开源了类似的技术,叫做Protobuf。它们带来最主要的好处是提供了一种跨语言通信的机制,其次是支持二进制格式压缩消息,进行IO优化,从而提升了数据传输性能。
举个例子,HBase是Java语言编写的,它提供了Java API接口,但其他语言的客户端怎么办呢?这时候HBase就可以用Thrift实现一套通信接口,从而其他编程语言也可以使用HBase的各种客户端API。
一个Thrift服务的基本框架如下图所示,由以下几个关键部分组成
- 接口定义,定义接口字段和数据格式,采用Interface Definition Language(IDL)编写,例如下图中的Hello.java
- 公共文件, 利用代码生成工具,由接口定义生成特定编程语言的框架代码和库依赖文件
- 客户端实现,自行实现客户端通信逻辑
- 服务端实现,自行实现服务端通信逻辑
这也就表明了使用Thrift的基本步骤:1. 定义接口 2. 生成代码框架 3. 实现服务端和客户端逻辑。
例如下面的Hello.thrift
,定义了服务 Hello 的五个方法,每个方法包含一个方法名,参数列表和返回类型。每个参数包括参数序号,参数类型以及参数名。使用 Thrift 工具编译 Hello.thrift,就会生成相应的 Hello.java 文件。该文件包含了在 Hello.thrift 文件中描述的服务 Hello 的接口定义,即 Hello.Iface 接口,以及服务调用的底层通信细节。
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()
}
Thrift基本结构
Thrift的基本结构如下图所示
SeriviceClient和ServiceProcessor这一层,以及write()/read()这一层,是由代码生成器产生。
协议层Protocol、传输层Transport和底层IO由Thrift框架提供,我们不需要过度关心。
我们展开介绍下协议层TProtocol和传输层TTransport。
协议层
协议层定义的是传输什么内容,回答的是What的问题。
- TBinaryProtocol 二进制编码格式
- TCompactProtocol 密集的二进制编码格式
- TJSONProtocol JSON 数据编码格式
- TSimpleJSONProtocol 简化版JSON协议,只支持写入的协议
- TDebugProtocol 使用人类可读的txt格式,辅助debug
传输层
传输层定义的是传输方式,回答的是How的问题。
TTransport这一层负责抽象出操作系统无关的通信协议;支持的传输协议包括:
- TSocket - 使用阻塞式 I/O 进行传输,是最常见的模式
- TFramedTransport - 使用非阻塞方式,按块的大小进行传输,类似于 Java 中的 NIO
- TNonblockingTransport - 使用非阻塞方式,用于构建异步客户端
- TMemoryTransport - 使用内存IO
- TZlibTransport - 使用zlib进行压缩
数据类型
- 基本类型,包括bool,byte,int16,int32,int64,double,string
- 结构体,参考C语言struct
- 枚举
- 容器,包括List,Set,Map等
- 异常
- 服务
不支持的数据类型:
- 循环结构体 - 结构体只能包含在它之前声明过的结构体。结构体不能包含自身
- 结构体继承- 应该使用结构体组合代替继承
- 多态- 因为没有继承,所以同样不支持多态
- 重载- 一个服务中的方法必须不重名
- 异构容器 - 所有容器中的元素必须具有相同类型
- 返回null - 函数不能直接返回null,应该使用包装结构体或者标记值代替null。
Spark Thrift Sever
Spark Thrift Server是做什么的呢?
我们要交互式地使用Spark SQL执行各种操作:读入数据,执行统计分析,写出数据等等。这个交互的过程就是由Spark Thrift Server提供服务端支持的。Spark项目利用Thrift Server对外提供了一个分布式的SQL接口。
Spark Thrift的客户端和服务端交互中,两个重要的概念是会话Session和操作Operation。会话就是一次访问周期,从客户端发起访问开始,到服务端或客户端关闭时结束。操作就是客户端向服务端发起的各种动作。通常情况下服务端同时管理着多个会话,每个会话对应一个客户端。每个会话、每个操作都有一个引用称作句柄,分别叫做会话句柄SessionHandle和操作句柄OperationHandle。会话句柄是在客户端持久化服务端信息的结构体,主要内容就是服务端的ID。操作句柄是客户端的引用,它指向服务端异步运行的一个任务。它们的定义见sql/hive-thriftserver/if/TCLIService.thrift
:
// Client-side handle to persistent
// session information on the server-side.
struct TSessionHandle {
1: required THandleIdentifier sessionId
}
// Client-side reference to a task running
// asynchronously on the server.
struct TOperationHandle {
1: required THandleIdentifier operationId
2: required TOperationType operationType
3: required bool hasResultSet
4