Thrift IDL

Thrift 采用IDL(Interface Description Language)来定义通用的服务接口,并通过生成不同的语言代理实现来达到跨语言、平台的功能,本文对Thrift IDL文件的常用语法进行说明。

数据类型

基本类型(Base Types)

Thrift 不支持无符号整数类型,因为很多编程语言不存在无符号整数类型。

Type Desc GO
bool 布尔值 bool
byte 有符号的 8 位整数 int8
i16 有符号的 16 位整数 int16
i32 有符号的 32 位整数 int32
i64 有符号的 64 位整数 int64
double 64 位浮点数 float64
string 文本字符串(UTF-8编码格式) string

特殊类型(Special Types)

Type Desc GO
binary 未编码的字节序列,string 的一种特殊形式 []byte

集合容器(Containers)

Type Desc GO
list<T> 元素有序列表,允许重复 []T
set<T> 元素无序列表,不允许重复 []T
map<K,V> key-value结构数据,key 不允许重复 map[K]V

在使用容器类型时必须指定泛型,否则无法编译 IDL 文件。上述类型中,Go 语言中没有 set 类型,所以set<T>对应的是切片[]T。容器中元素可以除了 service 之外的任何类型,包括 exception。


Thrift 文件组成

Document   ::= Header* Definition*
Header     ::= Include | CppInclude | Namespace
Definition ::= Const | Typedef | Enum | Senum | Struct | Union | Exception | Service

一个 Thrift 文件就是一个 Document,而一个 thrift 文件又是由若干 Header 和 Definition 组成的,当然也可以是一个空文件。

命名空间(namespace)

Thrift 中的命名空间类似 Go 或者 Java 中的package,提供一种组织(隔离)代码的简便方式。名字空间也可以用于解决类型定义中的名字冲突。由于每种语言均有自己的命名空间定义方式,Thrift 允许开发者针对特定语言定义namespace

namespace go base
namespace py base 

struct Base {
  ...
}
// Go 语言转换为以下代码
package base

type Base struct {
    ...
}

include

在真正的业务开发中,我们不可能把所有的服务都定义到一个文件中,通常会根据业务模块进行拆分,然后将这些服务include到一个入口文件中。

// base.thrift
namespace go base

struct Base {
  ...
}
// example.thrift
include "base.thrift"  // 使用 include关键字 + Thrift文件路径,引用其它 thrift 文件

struct Example {
    // 当引用其它 Thrift 文件中的对象时,使用被引用的 Thrift 文件名作为前缀进行访问
    1: base.Base ExampleBase   
}

注释(comment)

Thrift 支持 C 多行风格和Java/C++单行风格,示例如下:

/**
 * 多行注释
 * 类似 C
 */

// C++/Java 当行注释

常量(const)

const 字段类型 名称标识 = 值 | 列表
// 常量定义
const i32 MALE_INT = 1
const map<i32, string> GENDER_MAP = {1: "male", 2: "female"}

类型别名(typedef)

typedef 原类型 自定义类型
// 某些数据类型比较长可以用别名简化
typedef i32 MyInteger
typedef map<i32, string> gmp

枚举类型(enum)

enum 名称标识 {
    字段标识 = int常量
}
// 枚举常量必须是 32 位的正整数,默认从 0 开始赋值
enum GenderEnum{
    UNKNOWN // 0
    MALE    // 1,前一个元素加 1
    FEMALE  // 2,前一个元素加 1 
}

enum Numbers {
    ONE = 1   // 1
    TWO       // 2,前一个元素加 1
    THREE     // 3,前一个元素加 1
    FIVE = 5  // 5
    SIX       // 6,前一个元素加 1
    EIGHT = 8 // 8
}

结构体类型(struct)

struct 是 Thrift 定义的一个通用对象,相当于面向对象语言中的类。格式定义如下:

struct 名称标识 {
    数字标识: (required|optional)? 类型 名称标识 (= 值)?
}

// 示例
struct User {
    1: required string name  // 该字段必填
    2: optional i32 age = 0  // 该字段选填
    3: bool gender           // 默认类型为 required 和 optional 的结合
}
  • struct 不能继承,但是可以嵌套,包括引用自己;
  • 成员字段是强类型,即明确指定类型,字段不允许重复;
  • 成员字段前使用正整数+冒号进行编号(如1:),编号不允许重复,可以不连续;
  • 成员字段间分隔符可以使用,;,可以混用也可均不用,为方便阅读,建议统一;
  • 成员字段可以使用 optional 和 required 修饰,也可以不填,使用默认类型。区别在于,optional 修饰的字段只有在被传值时才会被序列化;required 修饰的字段在远程调用时必须传值,也必须被序列化;默认类型字段是 required 和 optional 的结合,可以不传值,但是必须被序列化;
  • 成员字段可以设置默认值。

☕️ 示例代码

// guild.thrift
namespace go io.buman.guild

struct Guild {
    10: i32 id      // 公会 ID
    20: string name // 公会名称
}
// player.thrift
include "guild.thrift"  // 引用 guild.thrift 文件
namespace go io.buman.player

// 变量默认赋值从 0 开始
enum GenderEnum{
    UNKNOWN   // 0
    MALE      // 1
    FEMALE    // 2
}

// 第一个变量赋值为 1,后面的变量一次递增
enum RoleEnum{
    WARIOR = 1  // 战吊 1
    MAGE        // 法爷 2
    WARLOCK     // 术士 3
    PRIEST      // 牧师 4
    DRUID       // 德鲁伊 5
}

struct Player {
    1: i32 id
    2: required string name
    3: required RoleEnum role
    4: required GenderEnum gender = GenderEnum.UNKNOWN
    5: Player cp  // 可以自己嵌套自己
    // 当引用其它 Thrift 文件中的对象时,使用被引用的 Thrift 文件名作为前缀进行访问
    6: guild.Guild guild
}

异常(exception)

Thrift 支持在服务里面使用 exception 关键字自定义异常,结构上等同于结构体,便于客户端更好的识别和处理服务的各种异常状况。

exception 名称标识 {
    数字标识: (required|optional)? 类型 名称标识 (= 值)?
}
exception PlayerNotFoundException {
    1: i32 code = 400,
    2: string msg
}

服务(service)

service 名称标识 (extends 名称标识)? {
    (oneway)? 返回类型 名称标识(字段列表) throws(字段列表)?
}
service PlayerService {
    // 玩家注册
    Player signIn(1: Player player)

    // 查询所有玩家
    list<Player> queryAllPlayer()

    // 注册公会
    guild.Guild registerGuild(1: guild.Guild guild)

    // 查询所有公会
    list<guild.Guild> queryAllGuild()

    // 为玩家添加cp
    Player addCp(1: i32 pid, 2: Player player) throws (1: PlayerNotFoundException e)

    // 玩家加入公会
    Player joinGuild(1: i32 pid, 2: i32 gid) throws (1: PlayerNotFoundException e)
}

service ExampleService {
    oneway void GetName(1: string UserId
    void GetAge(1: string UserId) throws (1: Error err)
}
  • 命名函数具有参数列表和返回值类型,参数列表可以为空,没有返回值使用 void 关键字修饰;
  • 参数列表类似于 struct 中的成员字段定义,每个参数前都需要正整数+冒号编号,编号可以不连续;
  • 参数列表分隔符可以使用,;,可以混用也可均不用,习惯使用,
  • 抛出的异常 exception 也需要被编号;
  • 默认情况下,非oneway修饰的函数是应答式,即req-resp,客户端发请求,服务端返回响应;被oneway修饰后的函数,客户端只是发起请求,无须关注返回,服务端也不会响应,与 void 的区别在于 void 类型的函数还可以返回异常。

参考

  1. Thrift入门基础知识-thrift文件(IDL)说明和生成目标语言源代码
  2. Thrift之IDL
  3. Thrift & IDL 介绍
  4. https://thrift.apache.org/docs/idl
posted @ 2022-06-13 20:38  呵呵233  阅读(505)  评论(0编辑  收藏  举报