protobuf v3 中的字段默认值和空字段区分?

本贴最后更新于 1300 天前,其中的信息可能已经渤澥桑田

简介

protobuf 是 google 开源的序列化工具,在微服务架构中是常见的 dto 构建工具。

protobuf v3

在 protobuf3 中,字段的的默认规则都是 optional 的,正是因为如此,我们在 marshal 的时候字段是可选的,但是在 unmarshal 的时候,所有的字段都是会被设置值的,如在 marshal 未设置字段在 unmarshal 的时候会被默认值填充,这时我们是没有办法区分字段是 nullState 还是 defaulteValue

如下:

syntax="proto3"

package dto
message Request {
	string name = 1;
	int32 age = 2;
	string sex = 3;
}

message Response {

}

service Transport {
	rpc Send(Request) returns(Response)
}
import "dto"

func TestNullState() {
	req := &dto.Request{
		Name: "joe",
		Age:  23,
	}
	//假设拿到了grpc的client
	resp, err := client.Send(req)
}
package service

import "dto"

type TransportService{}

func (transport *TransportService) Send(ctx context.Context, req *dto.Request) (resp *dto.Response, err error) {
	//在这里我们拿到了dto.Requset,
	//那么,如何判断req.Sex是未设置,还是设置了""呢?
	//protobuf是不提共判断的,从v3开始。
}

判断 nullState 和 defaultValue

方案 1:使用特殊值判断

使用特殊值替代 nullState,如年龄字段,age 显然不能为负,所以负数都可以替代 nullState,这是表示层的(represent)的 nullState。 但是如果是 description,这样的字段,如何表示 未设置状态 呢?

其次就是特殊值的耦合很深,编码不灵活。

方案二:显式定义 boolean 字段(不建议)

显式定义 bool 字段,那么 message 的字段数加倍,并且排版难看。

message Request {
	string name = 1;
	in32 age = 2;
	string sex = 3;
	bool has_name = 4;
	bool has_age = 5
	bool has_sex = 6;
}


方案三:使用 oneof 黑科技

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}



oneof关键字和c语言中的union是一个意思,就是oneof中的所有字段只能同时set一个,set一个前一个将被抹去。

### 方案四:使用wrapper类型


使用wrapper类型来传递,是一个很好的方法,很nice

```go
message String {//wrapper string.
	string value = 1;
	bool flag = 2;
}

这样虽然和 显式增加判断字段 相似,但是好看不是吗?且可维护性和扩展性更好。

方案五:使用 bitset 来判断字段是否 set

添加一个 bitset 类型字段来实现存储字段是否存储。至于 bitset 如何实现,这不重要(使用一个 int64 基本就已经够用了)。

message Request {
	int64 fields_state = 1;
	string name = 2;
	int32 age = 3;
	string sex = 4;
}

//通过位移运算来判断字段是否被设置。

以字段索引为索引,通过位移运算来判断字段是否被设置。

方案六:使用 json 传递数据,在 protobuf 中开洞

在 protobuf 里面打个洞,传递 json 数据,也就是 string。

message Request {
	string content = 1;
}

在 grpc 后端,我们再对 content 字段进行 json.Unmarshal 来解析请求。

这样做是没有任何问题的,但是效率有点低。首先 grpc 是基于 htpp2 的,所以就需要经过 http 编解码和 protobuf 编解码,就是两次,再加上 json 的话就是 3 次,此时效率会降低。

Reference

[1] https://www.cnblogs.com/tohxyblog/p/8974763.html

[2] https://zhuanlan.zhihu.com/p/46603988

[3] https://stackoverflow.com/questions/42622015/how-to-define-an-optional-field-in-protobuf-3

  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    491 引用 • 1383 回帖 • 370 关注
  • Protobuf
    8 引用 • 1 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...