Thrift 字段权限之谜+Go代码技巧

970 阅读3分钟

我正在参加「掘金·启航计划」

Thrift是一套包含序列化功能和支持服务通信的RPC框架,主要包含三大部分:代码生成序列化框架RPC框架,大致相当于protoc + protobuffer + grpc,并且支持大量语言,保证常用功能在跨语言间功能一致,是一套全栈式的RPC解决方案。

Thrift数据类型

即 IDL 中的基础类型有 10 种

  • bool 布尔型,false | true
  • byte byte
  • i8 int8——不推荐使用
  • i16 int16
  • i32 int32
  • i64 int64
  • double 双精度浮点型
  • string 字符串
  • binary 二进制 byte[]
  • slist

Thrift容器类型

// Map 简单的 key-value 对,key 不可以重复,`<keyType, valueType>` 用来指定 key 和 value 的类型
// 简单的 map 类型
map <string, i8>

// 嵌套 map 类型
map <map <string, i32>, i64>

// 更多的嵌套可能是这样的
map <string, map<string, string>>
map <string, set<i32>>

// Set 不重复的数据集合,`<type>` 指定集合中元素的类型
set <string>
set <map <string, bool>>

// List 类似数组,元素可重复, `<type>` 指定集合中元素的类型
list <i64>
list <set <string>>
list <map<string, set<i8>>>

字段权限

  • required:

    • 写:必须字段始终写入,并且应该设置这些字段。
    • 读:必须字段始终读取,并且它们将包含在输入流中
  • 注意:如果一个必须字段在读的时候丢失,则会抛出异常或返回错误,所以在版本控制的时候,要严格控制字段的必选和可选,必须字段如果被删或者改为可选,那将会造成版本不兼容。
  • optional:

    • 写:可选字段仅在设置时写入
    • 读:可选字段可能是也可能不是输入流的一部分
  • default:

    • 写:理论上总是写入,但是有一些特例
    • 读:跟optional一样
  • 默认类型是required和optional的结合,可选输入(读),必须输出(写)。

举个🌰

  1. 完整的thrift文件定义
struct ExampleRequestParam {
    1: optional i64 ReqOptional 
    2: required i64 ReqRequired
    3: i64 ReqNone
    4: optional Info ReqInfoOptional
    5: required Info ReqInfoRequired
    6: Info ReqInfoNone
}

struct Info {
    1: optional i64 InfoInsideOptional
    2: required i64 InfoInsideRequired
    3: i64 InfoInsideNone
}

struct ExampleResponse {
    1: optional i64 RespOptional 
    2: required i64 RespRequired
    3: i64 RespNone
    4: optional Info RespInfoOptional
    5: required Info RespInfoRequired
    6: Info RespInfoNone
}

// ------ service ------
service ExampleService {
    ExampleResponse Example(1: required ExampleRequestParam req)
}
  1. 请求参数(对应字段权限中的“读”):

蓝色:可以删除不写

黄色:必须写

{
    "ReqOptional": 1,        // 可以删除不写,也可以写
    "ReqRequired": 1,        // 必须写
    "ReqNone": 1,            // 可以删除不写,也可以写,跟OPtional保持一致
    "ReqInfoOptional": {..}, // 可以删除不写,也可以写
    "ReqInfoRequired":{      // 必须写该结构体
        "InfoInsideOptional": 2,
        "InfoInsideRequired": 2,
        "InfoInsideNone": 2
    },
    "ReqInfoNone": {..}      // 可以删除不写,也可以写, 跟OPtional保持一致
}
  1. 返回参数(对应字段权限中的“写”),以Golang为例:

蓝色:可以删除不写

黄色:必须写

type ExampleResponse struct {
   RespOptional              *int64   `thrift:"RespOptional,1,optional" json:"RespOptional,omitempty"`
   RespRequired              int64    `thrift:"RespRequired,2,required" json:"RespRequired"`
   RespNone                  int64    `thrift:"RespNone,3" json:"RespNone"`
   RespInfoOptional          *Info    `thrift:"RespInfoOptional,4" json:"RespInfoOptional,omitempty"`
   RespInfoRequired          *Info    `thrift:"RespInfoRequired,5,required" json:"RespInfoRequired"`
   RespInfoNone              *Info    `thrift:"RespInfoNone,6" json:"RespInfoNone"`
}

func Service(ctx context.Context ,req xx.ExampleRequestParam) (xx.ExampleResponse, error){
    resp
    resp := xx.ExampleResponse{
        RespOptional: 1,              // 可以不写,Optional,
        RespRequired: 1,              // 可以不写,基础类型在外层初始化时自动赋值为0
        RespNone:     1,              // 可以不写,基础类型在外层初始化时自动赋值为0,与Required保持一致
        RespInfoOptional: ..,         // 可以不写,Optional
        RespInfoRequired: &xx.Info{   // 必须写,初始化结构体,因为无默认值,被认为是nil,所以必须在这里初始化
            "InfoInsideOptional": 2,
            "InfoInsideRequired": 2,
            "InfoInsideNone": 2
        },
        RespInfoNone:&xx.Info{        // 必须写,与required保持一致
            "InfoInsideOptional": 2,
            "InfoInsideRequired": 2,
            "InfoInsideNone": 2
        },
    }
    ... // 业务逻辑处理
    return resp, nil
    
}

总结:

  1. 针对请求参数

    1. optional/default 可以不用写;
    2. required必须写;
  1. 针对(代码中)返回值

    1. optional 可以不用写;

    2. required分情况(由于Golang的代码特性)

      1. 基础类型,默认值不为nil,可以不用初始化,会自动默认赋值为默认值;
      2. 非基础类型,默认值为nil,必须初始化,否则在会识别不到有该返回值,导致报错。