第03篇 Thrift 是什么

前面我们介绍了什么是 RPC,并且也了解 RPC 的分层原理,下面我们就开始针对 Thrift 这一个具体的 RPC 来深入研究一下。

整个过程我们先介绍下核心的 Apache Thrift,再介绍下 swift 包,以及我们自己的封装。

1. Apache Thrift

我们常说的 Thrift 就是指 Apache Thrift,它最初由 Facebook 于 2007 年开发,在 2008 年进入 Apache 开源项目。

Apache Thrift 通过 IDL(接口定义语言)来定义 RPC 的接口和参数,并将定义的接口内容写在以 .thrift 结尾的文件中,然后通过 Thrift 提供的编译器根据我们需要,去生成对应语言的代码。这里生成的代码就是我们前面提到过的 RPC 的代理类、管理类和编解码器。

1.1 基本架构

下面我们先看一下结构分层,其中浅蓝部分是用户自己写的代码,深蓝部分是我们通过 IDL 语言定义后自动生成的代码,绿色和红色部分则是 Apache Thrift 中的代码。

Thrift 分层结构图

主要分为这几部分:

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

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

  • 传输层 TTransport
    是与底层数据传输密切相关的传输层,负责以字节流方式接收和发送消息体,不关注是什么数据类型。底层IO负责实际的数据传输,包括socket、文件和压缩数据流等。

与我们前面所讲的 RPC 的结构对比,明显缺少了“编解码”这一层。这是因为 Apache Thrift 与其它 RPC 框架相比,为我们每个接口类都定制化的生成一个接口代理类。而 Apache Thrift 可以这么做的原因,就是下面我们要讲的 Thrift 接口定义语言 —— IDL。

1.2 通过 IDL 定义接口

所以使用 Apache Thrift 开发时,首先要通过 IDL 定义接口,然后再使用 Thrift 的编译器将其编译成对应语言的接口及其代理类。我们使用 Java 开发就编译成 Java 语言的接口及代理类,这也是 Apache Thrift 能跨语言开发的原因。

1)编写 IDL 文件

首先创建一个以“.thrift”作为后缀的文件,然后在文件的第一行加上 namespace 命名空间(在 Java 中就是包名),一个简单的接口如下所示:

namespace java com.test.service

service TestThriftService {
    /** value 中存放两个字符串拼接之后的字符串 */
    string add(1:string str1, 2:string str2),

    i32 add(1:i32 i1, 2:i32 i2)
}

上面例子中定义的接口比较简单,唯一要注意的是,每个参数的前面都有一个数字。下面我们就了解一下 IDL 的详细语法,然后再来定义一个复杂的接口。

2)IDL支持的数据类型

  • string:字符串类型,对应 java 中的 String 类型;
  • i16:16位整型,对应 java 中的 short 类型;
  • i32:32位整型,对应 java 中的 int 类型;
  • i64:64位整型,对应 java 中的 long 类型;
  • byte:字符类型,对应 java中的 byte 类型;
  • bool:布尔类型,对应 java中的 boolean 类型;
  • double:双精度浮点类型,对应 java 中的 double 类型;
  • map:map类型,对应 java 中的 map 类型;
  • set:集合类型,对应 java 中的 set 类型;
  • list:链表类型,对应 java 中的 list 类型;
  • void:空类型,对应 java 中的 void 类型;

3)通过 IDL 自定义数据类型

我们可以自己定义一个枚举类型

enum Numberz
{
  ONE = 1,
  TWO,
  THREE,
  FIVE = 5,
  SIX,
  EIGHT = 8
}

定义一个结构体类型,这里的结构体对应的就是 Java 中的类

struct TestV1 {
       1: i32 begin_in_both,
       3: string old_string,
       12: i32 end_in_both
}

注意,在struct定义结构体时需要对每个结构体成员用序号标识:“序号: ”。

我们可以将枚举或结构体单独写在一个文件中,然后可以通过 include 将其引入到其它需要使用的文件中。并且我们可以 typedef 为我们自定义的类型或 Thrift 原本支持的类型去设置一个别名。

namespace java com.test.service

include "datatype.thrift"

typedef i32 Integer 

service TestThriftService {
    datatype.TestV1 handle(1:string str, 2:Integer i),
}

然后就可以通过 thrift 编译生成接口及代理类了,生成的类包括5部分(待完善)

接口类型,默认名称都是Iface。这个接口类型被服务器和客户端共同使用。服务器端使用它来做顶层接口,编写实现类。客户端代码使用它作为生成代理的服务接口。 会自动生成同步调用和异步调用的两个接口。 客户端类型,一个同步调用的客户端Client,一个异步调用的客户端AsyncClient

Processor,用来支持方法调用,每个服务的实现类都要使用Processor来注册,这样最后服务器端调用接口实现时能定位到具体的实现类。

方法参数的封装类,以"方法名_args"命名

方法返回值的封装类,以"方法名_result"命名

2. Facebook Swift

每次我们新增或改动接口时,都首先要编写对应的 IDL 文件,然后在编译生成我们对应的 Java 代码。这个过程而且让人感到“不爽”,因为步骤比较繁琐,而且编译生成后的代码量比较大,看起来比较臃肿也不太美观。另外我们一般也不会做跨语言调用,几乎都是 Java 后端系统之间的相互调用,这样就显得 Thrift 的 IDL 有些鸡肋和难用。

所以后来 facebook 又为 Thrift 提供了一种让人眼前一亮的开发方式,通过引入 swift 包,让我们可以使用注解去声明接口及参数,再也不需要使用 IDL 了。这样以来,我们的接口就变得非常清爽,开发起来也更加舒适。

(待完善)

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